From ebdc94b4bda89d77b37a7cdcf8053a72a2b07527 Mon Sep 17 00:00:00 2001 From: Nathan Panchout Date: Tue, 30 Jun 2026 16:06:11 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20export=20the=20presentati?= =?UTF-8?q?on=20as=20a=20PDF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Render the slides off-screen as A4 landscape pages and print them via the browser, one slide per page with the watermark. Add a "Download PDF" action to the floating bar. Closes #2446 --- .../__tests__/PresenterPrintDocument.spec.tsx | 80 +++++++++ .../components/PresenterFloatingBar.tsx | 13 +- .../components/PresenterOverlay.tsx | 17 ++ .../components/PresenterPrintDocument.tsx | 169 ++++++++++++++++++ .../docs/doc-presenter/utils_print.tsx | 150 ++++++++++++++++ 5 files changed, 428 insertions(+), 1 deletion(-) create mode 100644 src/frontend/apps/impress/src/features/docs/doc-presenter/__tests__/PresenterPrintDocument.spec.tsx create mode 100644 src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterPrintDocument.tsx create mode 100644 src/frontend/apps/impress/src/features/docs/doc-presenter/utils_print.tsx diff --git a/src/frontend/apps/impress/src/features/docs/doc-presenter/__tests__/PresenterPrintDocument.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-presenter/__tests__/PresenterPrintDocument.spec.tsx new file mode 100644 index 000000000..cd9499cba --- /dev/null +++ b/src/frontend/apps/impress/src/features/docs/doc-presenter/__tests__/PresenterPrintDocument.spec.tsx @@ -0,0 +1,80 @@ +import { render } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +import { + PRESENTER_PRINT_CONTENT_CLASS, + PRESENTER_PRINT_CSS, + PRESENTER_PRINT_LOGO_CLASS, + PRESENTER_PRINT_PAGE_CLASS, + PRESENTER_PRINT_PAGE_SELECTOR, + PRESENTER_PRINT_TITLE_CLASS, + PresenterPrintDocument, +} from '../components/PresenterPrintDocument'; +import type { PresenterBlock } from '../types'; + +const para = (text: string): PresenterBlock => ({ + children: [], + content: [{ type: 'text', text, styles: {} }], + id: 'paragraph', + props: { + backgroundColor: 'default', + textAlignment: 'left', + textColor: 'default', + }, + type: 'paragraph', +}); + +describe('PresenterPrintDocument', () => { + beforeEach(() => { + Object.defineProperty(window, 'matchMedia', { + configurable: true, + writable: true, + value: vi.fn().mockImplementation((query: string) => ({ + addEventListener: vi.fn(), + addListener: vi.fn(), + dispatchEvent: vi.fn(), + matches: false, + media: query, + onchange: null, + removeEventListener: vi.fn(), + removeListener: vi.fn(), + })), + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test('renders one print page per slide', () => { + const { container } = render( + , + ); + + expect( + container.querySelectorAll(PRESENTER_PRINT_PAGE_SELECTOR), + ).toHaveLength(4); + expect( + container.querySelector(`.${PRESENTER_PRINT_TITLE_CLASS}`), + ).toHaveTextContent('Deck title'); + expect( + container.querySelectorAll(`.${PRESENTER_PRINT_LOGO_CLASS}`), + ).toHaveLength(4); + }); + + test('centers short slide content vertically in print pages', () => { + expect(PRESENTER_PRINT_CSS).toContain(`.${PRESENTER_PRINT_PAGE_CLASS} {`); + expect(PRESENTER_PRINT_CSS).toContain('justify-content: safe center;'); + expect(PRESENTER_PRINT_CSS).toContain( + `.${PRESENTER_PRINT_CONTENT_CLASS} {`, + ); + expect(PRESENTER_PRINT_CSS).toContain('max-height: 100%;'); + }); +}); diff --git a/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterFloatingBar.tsx b/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterFloatingBar.tsx index b4421eff9..e8f73f076 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterFloatingBar.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-presenter/components/PresenterFloatingBar.tsx @@ -3,6 +3,7 @@ import { DropdownMenu, DropdownMenuItem } from '@gouvfr-lasuite/ui-kit'; import { ChevronLeft, ChevronRight, + Download, Link, Maximize, Minimize, @@ -22,8 +23,10 @@ interface PresenterFloatingBarProps { onPrev: () => void; onNext: () => void; onCopyLink: () => void; + onExportPdf: () => void; onToggleFullscreen: () => void; onClose: () => void; + isExportingPdf: boolean; } const barCss = css` @@ -80,8 +83,10 @@ export const PresenterFloatingBar = ({ onPrev, onNext, onCopyLink, + onExportPdf, onToggleFullscreen, onClose, + isExportingPdf, }: PresenterFloatingBarProps) => { const { t } = useTranslation(); const isFirst = index <= 0; @@ -118,8 +123,14 @@ export const PresenterFloatingBar = ({ icon: , callback: onCopyLink, }, + { + label: t('Download PDF'), + icon: