From 0a00f7b4d83b1baf22544be01d81b9d7d2795a14 Mon Sep 17 00:00:00 2001 From: Alessandro Bortolin Date: Mon, 25 Nov 2024 23:45:54 +0100 Subject: [PATCH] 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 f291da44..a712c9e1 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 b7f7a90b..b5fc4380 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 1d627e1c..0c5ed783 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 c8bb1ffc..d35f2c91 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 be9f29d0..2f1e57d0 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"