From 855af334691300237d863d8d42f098fe4b5f2194 Mon Sep 17 00:00:00 2001 From: Alessandro Bortolin Date: Mon, 25 Nov 2024 20:54:50 +0100 Subject: [PATCH 1/3] Minor fixes --- src/cli/firebase/import.ts | 2 ++ src/web/teacher/table.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cli/firebase/import.ts b/src/cli/firebase/import.ts index 38de0c3..237411b 100644 --- a/src/cli/firebase/import.ts +++ b/src/cli/firebase/import.ts @@ -225,6 +225,7 @@ async function importUsers(users: User[], customClaims: object, options: ImportO let user = await auth.getUserByEmail(record.email).catch(noop); if (!user) { user = await auth.createUser({ + uid: record.id, email: record.email, emailVerified: true, password: record.password, @@ -305,6 +306,7 @@ async function importVariantMappings(db: Firestore, options: ImportOptions) { } const userSchema = z.object({ + id: z.string().optional(), name: z.string(), email: z.string().email(), password: z.string(), diff --git a/src/web/teacher/table.tsx b/src/web/teacher/table.tsx index 5abe44b..ee7e659 100644 --- a/src/web/teacher/table.tsx +++ b/src/web/teacher/table.tsx @@ -113,13 +113,13 @@ const FinalizeModal = forwardRef(function FinalizeModal( // Generate a list of string that can uniquely identify a student. Multiple // strings are generated to prevent possible errors during data entry. function normalize(student: Student) { - const info = student.userData!; + const info = student.userData; const orderings = [ ["name", "surname", "classYear", "classSection"], ["surname", "name", "classYear", "classSection"], ]; return orderings.map((fields) => { - return deburr(fields.map((field) => info[field]).join("\n")) + return deburr(fields.map((field) => info?.[field] ?? "").join("\n")) .toLowerCase() .replaceAll(/[^\w\n]/g, ""); }); @@ -247,7 +247,7 @@ const DeleteModal = forwardRef(function DeleteModal( Seleziona tutti” come filtro.

- +
close("0")}>Annulla Continua @@ -382,7 +382,7 @@ function Table() { singleClickEdit={true} suppressClickEdit={frozen} readOnlyEdit={true} - rowSelection="single" + rowSelection={{ mode: "singleRow" }} onCellEditRequest={onCellEditRequest} enableBrowserTooltips={true} localeText={AG_GRID_LOCALE_IT} From 95e0c89369eaa2d015d1f2b0d5e8785368d59100 Mon Sep 17 00:00:00 2001 From: Alessandro Bortolin Date: Mon, 25 Nov 2024 23:45:54 +0100 Subject: [PATCH 2/3] Allow downloading statements as ZIP file --- package.json | 1 + src/web/firebase/teacher-login.tsx | 17 +++++++++++++---- src/web/teacher/dashboard.tsx | 27 +++++++++++++++++++++++++-- src/web/teacher/provider.tsx | 4 ++-- yarn.lock | 5 +++++ 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index f291da4..a712c9e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@vitejs/plugin-react-swc": "^3.3", "acorn": "^8.10", "blockly": "^11.1", + "client-zip": "^2.4", "clsx": "^2.1", "commander": "^12.0", "date-fns": "^3.0", diff --git a/src/web/firebase/teacher-login.tsx b/src/web/firebase/teacher-login.tsx index b7f7a90..b5fc438 100644 --- a/src/web/firebase/teacher-login.tsx +++ b/src/web/firebase/teacher-login.tsx @@ -152,14 +152,23 @@ async function setParticipation( } } -function getPdfStatements(db: Firestore, statementVersion: number, variantIds: string[]) { +async function getPdfStatements( + db: Firestore, + statementVersion: number, + variantIds: string[], +): Promise> { const storage = getStorage(db.app); - return Promise.all( - variantIds.map((id) => - getBytes(ref(storage, `statements/${id}/statement-${statementVersion}.pdf`)), + const files = await Promise.all( + variantIds.map( + async (id) => + [ + id, + await getBytes(ref(storage, `statements/${id}/statement-${statementVersion}.pdf`)), + ] as const, ), ); + return Object.fromEntries(files); } function useStudents(participationId: string) { diff --git a/src/web/teacher/dashboard.tsx b/src/web/teacher/dashboard.tsx index 1d627e1..0c5ed78 100644 --- a/src/web/teacher/dashboard.tsx +++ b/src/web/teacher/dashboard.tsx @@ -12,6 +12,7 @@ import { SubmitButton, WithinTimeRange, } from "@olinfo/react-components"; +import { downloadZip } from "client-zip"; import { addMinutes, addSeconds, isSameDay, roundToNearestMinutes, subMinutes } from "date-fns"; import { saveAs } from "file-saver"; import { range } from "lodash-es"; @@ -171,6 +172,7 @@ export default function TeacherDashboard() { {contest.hasPdf && ( + )} @@ -234,7 +236,7 @@ function DownloadPdfButton() { const { PDFDocument } = await import("@cantoo/pdf-lib"); const pdf = await PDFDocument.create(); - for (const statement of statements) { + for (const statement of Object.values(statements)) { const otherPdf = await PDFDocument.load(statement); const toCopy = range(otherPdf.getPages().length); const pages = await pdf.copyPages(otherPdf, toCopy); @@ -257,7 +259,28 @@ function DownloadPdfButton() { return ( + ); +} + +function DownloadZipButton() { + const { participation, getPdfStatements } = useTeacher(); + + const onClick = async () => { + const statements = await getPdfStatements(); + + const files = Object.entries(statements).map( + ([name, data]) => new File([data], `${name}.pdf`, { type: "application/pdf" }), + ); + const zip = await downloadZip(files).blob(); + + saveAs(zip, `${participation.id}.zip`); + }; + + return ( + ); } diff --git a/src/web/teacher/provider.tsx b/src/web/teacher/provider.tsx index c8bb1ff..d35f2c9 100644 --- a/src/web/teacher/provider.tsx +++ b/src/web/teacher/provider.tsx @@ -26,7 +26,7 @@ type TeacherContextProps = { /** Funzione per effettuare il logout */ logout: () => Promise; /** Funzione per ottenere i pdf dei testi */ - getPdfStatements: () => Promise<(Uint8Array | ArrayBuffer)[]>; + getPdfStatements: () => Promise>; /** Hook per ottenere gli studenti di una scuola */ useStudents: ( participationId: string, @@ -54,7 +54,7 @@ type TeacherProviderProps = { getPdfStatements: ( statementVersion: number, variantIds: string[], - ) => Promise<(Uint8Array | ArrayBuffer)[]>; + ) => Promise>; useStudents: ( participationId: string, ) => readonly [Student[], (student: Student) => Promise]; diff --git a/yarn.lock b/yarn.lock index be9f29d..2f1e57d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1860,6 +1860,11 @@ client-only@^0.0.1: resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== +client-zip@^2.4: + version "2.4.6" + resolved "https://registry.yarnpkg.com/client-zip/-/client-zip-2.4.6.tgz#c797c29d9463b17eca4b623339ccf06e620b2794" + integrity sha512-e7t1u14h/yT0A12qBwFsaus8UZZ8+MCaNAEn/z53mrukLq/LFcKX7TkbntAppGu8he2p8pz9vc5NEGE/h4ohlw== + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" From a1b26b40ea0609ff8dda120ec03ce73ff213e3b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 22:58:12 +0000 Subject: [PATCH 3/3] Bump vite from 5.4.2 to 5.4.11 in /docs Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.2 to 5.4.11. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.4.11/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.4.11/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: indirect ... Signed-off-by: dependabot[bot] --- docs/yarn.lock | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/yarn.lock b/docs/yarn.lock index be9a7f7..a542cf2 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -1344,6 +1344,11 @@ picocolors@^1.0.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + pkg-types@^1.0.3, pkg-types@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.2.0.tgz#d0268e894e93acff11a6279de147e83354ebd42d" @@ -1366,7 +1371,7 @@ points-on-path@^0.2.1: path-data-parser "0.1.0" points-on-curve "0.2.0" -postcss@^8.4.40, postcss@^8.4.41: +postcss@^8.4.40: version "8.4.44" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.44.tgz#d56834ef6508610ba224bb22b2457b2169ed0480" integrity sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw== @@ -1375,6 +1380,15 @@ postcss@^8.4.40, postcss@^8.4.41: picocolors "^1.0.1" source-map-js "^1.2.0" +postcss@^8.4.43: + version "8.4.49" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.1" + source-map-js "^1.2.1" + preact@^10.0.0: version "10.23.2" resolved "https://registry.yarnpkg.com/preact/-/preact-10.23.2.tgz#52deec92796ae0f0cc6b034d9c66e0fbc1b837dc" @@ -1449,6 +1463,11 @@ source-map-js@^1.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + speakingurl@^14.0.1: version "14.0.1" resolved "https://registry.yarnpkg.com/speakingurl/-/speakingurl-14.0.1.tgz#f37ec8ddc4ab98e9600c1c9ec324a8c48d772a53" @@ -1497,12 +1516,12 @@ uuid@^9.0.1: integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== vite@^5.4.1: - version "5.4.2" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.2.tgz#8acb6ec4bfab823cdfc1cb2d6c53ed311bc4e47e" - integrity sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA== + version "5.4.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5" + integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q== dependencies: esbuild "^0.21.3" - postcss "^8.4.41" + postcss "^8.4.43" rollup "^4.20.0" optionalDependencies: fsevents "~2.3.3"