From c3456adc2bb8746bdb079e524beaa97beb92ae0f Mon Sep 17 00:00:00 2001 From: Reece Browne <74901996+reecebrowne@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:56:28 +0000 Subject: [PATCH] Print with embed (#5109) --- frontend/package-lock.json | 75 +++++++++---------- frontend/package.json | 1 + .../public/locales/en-GB/translation.toml | 1 + .../core/components/viewer/LocalEmbedPDF.tsx | 6 ++ .../core/components/viewer/PrintAPIBridge.tsx | 25 +++++++ .../viewer/useViewerRightRailButtons.tsx | 14 +++- frontend/src/core/contexts/ViewerContext.tsx | 4 + .../src/core/contexts/viewer/viewerActions.ts | 13 ++++ .../src/core/contexts/viewer/viewerBridges.ts | 7 ++ 9 files changed, 104 insertions(+), 42 deletions(-) create mode 100644 frontend/src/core/components/viewer/PrintAPIBridge.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bbbe0dc9e..2dfd2c9af 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,6 +20,7 @@ "@embedpdf/plugin-interaction-manager": "^1.4.1", "@embedpdf/plugin-loader": "^1.4.1", "@embedpdf/plugin-pan": "^1.4.1", + "@embedpdf/plugin-print": "^1.4.1", "@embedpdf/plugin-render": "^1.4.1", "@embedpdf/plugin-rotate": "^1.4.1", "@embedpdf/plugin-scroll": "^1.4.1", @@ -455,7 +456,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -499,7 +499,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -580,7 +579,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.4.1.tgz", "integrity": "sha512-TGpxn2CvAKRnOJWJ3bsK+dKBiCp75ehxftRUmv7wAmPomhnG5XrDfoWJungvO+zbbqAwso6PocdeXINVt3hlAw==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/engines": "1.4.1", "@embedpdf/models": "1.4.1" @@ -680,7 +678,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.4.1.tgz", "integrity": "sha512-5WLDiNMH6tACkLGGv/lJtNsDeozOhSbrh0mjD1btHun8u7Yscu/Vf8tdJRUOsd+nULivo2nQ2NFNKu0OTbVo8w==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.4.1" }, @@ -697,7 +694,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.4.1.tgz", "integrity": "sha512-Ng02S9SFIAi9JZS5rI+NXSnZZ1Yk9YYRw4MlN2pig49qOyivZdz0oScZaYxQPewo8ccJkLeghjdeWswOBW/6cA==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.4.1" }, @@ -715,7 +711,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.4.1.tgz", "integrity": "sha512-m3ZOk8JygsLxoa4cZ+0BVB5pfRWuBCg2/gPqjhoFZNKTqAFw4J6HGUrhYKg94GRYe+w1cTJl/NbTBYuU5DOrsA==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.4.1" }, @@ -747,12 +742,28 @@ "vue": ">=3.2.0" } }, + "node_modules/@embedpdf/plugin-print": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-1.4.1.tgz", + "integrity": "sha512-YEjU6rQVW8wb125JXl1wma95+JISwADZpfqZZOtvPBRABp6ce4byblDTNjWVmTYWSgKUZrlXCL3Ff3Ig+bjbjw==", + "license": "MIT", + "dependencies": { + "@embedpdf/models": "1.4.1" + }, + "peerDependencies": { + "@embedpdf/core": "1.4.1", + "preact": "^10.26.4", + "react": ">=18.0.0", + "react-dom": ">=18.0.0", + "svelte": ">=5 <6", + "vue": ">=3.2.0" + } + }, "node_modules/@embedpdf/plugin-render": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.4.1.tgz", "integrity": "sha512-gKCdNKw6WBHBEpTc2DLBWIWOxzsNnaNbpfeY6C4f2Bum0EO+XW3Hl2oIx1uaRHjIhhnXso1J3QweqelsPwDGwg==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.4.1" }, @@ -787,7 +798,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.4.1.tgz", "integrity": "sha512-Y9O+matB4j4fLim5s/jn7qIi+lMC9vmDJRpJhiWe8bvD9oYLP2xfD/DdhFgAjRKcNhPoxC+j8q8QN5BMeGAv2Q==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.4.1" }, @@ -824,7 +834,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.4.1.tgz", "integrity": "sha512-lo5Ytk1PH0PrRKv6zKVupm4t02VGsqIrnSIeP6NO8Ujx0wfqEhj//sqIuO/EwfFVJD8lcQIP9UUo9y8baCrEog==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.4.1" }, @@ -900,7 +909,6 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.4.1.tgz", "integrity": "sha512-+TgFHKPCLTBiDYe2DdsmTS37hwQgcZ3dYIc7bE0l5cp+GVwouu1h0MTmjL+90loizeWwCiu10E/zXR6hz+CUaQ==", "license": "MIT", - "peer": true, "dependencies": { "@embedpdf/models": "1.4.1" }, @@ -1056,7 +1064,6 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -1100,7 +1107,6 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -2131,7 +2137,6 @@ "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.6.tgz", "integrity": "sha512-paTl+0x+O/QtgMtqVJaG8maD8sfiOdgPmLOyG485FmeGZ1L3KMdEkhxZtmdGlDFsLXhmMGQ57ducT90bvhXX5A==", "license": "MIT", - "peer": true, "dependencies": { "@floating-ui/react": "^0.27.16", "clsx": "^2.1.1", @@ -2182,7 +2187,6 @@ "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.6.tgz", "integrity": "sha512-liHfaWXHAkLjJy+Bkr29UsCwAoDQ/a64WrM67lksx8F0qqyjR5RQH8zVlhuOjdpQnwtlUkE/YiTvbJiPcoI0bw==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "^18.x || ^19.x" } @@ -2250,7 +2254,6 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz", "integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4", "@mui/core-downloads-tracker": "^7.3.5", @@ -3183,7 +3186,6 @@ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.9.0.tgz", "integrity": "sha512-ggs5k+/0FUJcIgNY08aZTqpBTtbExkJMYMLSMwyucrhtWexVOEY1KJmhBsxf+E/Q15f5rbwBpj+t0t2AW2oCsQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=12.16" } @@ -3302,6 +3304,7 @@ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz", "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", "license": "MIT", + "peer": true, "peerDependencies": { "acorn": "^8.9.0" } @@ -4078,7 +4081,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -4407,7 +4409,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4418,7 +4419,6 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4488,7 +4488,6 @@ "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -5202,6 +5201,7 @@ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz", "integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==", "license": "MIT", + "peer": true, "dependencies": { "@vue/shared": "3.5.24" } @@ -5211,6 +5211,7 @@ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz", "integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==", "license": "MIT", + "peer": true, "dependencies": { "@vue/reactivity": "3.5.24", "@vue/shared": "3.5.24" @@ -5221,6 +5222,7 @@ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz", "integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==", "license": "MIT", + "peer": true, "dependencies": { "@vue/reactivity": "3.5.24", "@vue/runtime-core": "3.5.24", @@ -5233,6 +5235,7 @@ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz", "integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-ssr": "3.5.24", "@vue/shared": "3.5.24" @@ -5259,7 +5262,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5667,6 +5669,7 @@ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">= 0.4" } @@ -5943,7 +5946,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -6991,8 +6993,7 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz", "integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/dezalgo": { "version": "1.0.4", @@ -7387,7 +7388,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7558,7 +7558,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7725,7 +7724,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/espree": { "version": "10.4.0", @@ -7790,6 +7790,7 @@ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz", "integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } @@ -8880,7 +8881,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.27.6" }, @@ -9357,6 +9357,7 @@ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.6" } @@ -9677,7 +9678,6 @@ "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.19", "@asamuzakjp/dom-selector": "^6.7.3", @@ -10264,7 +10264,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/locate-path": { "version": "6.0.0", @@ -11410,7 +11411,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11690,7 +11690,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -12073,7 +12072,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12083,7 +12081,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -13595,6 +13592,7 @@ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">= 0.4" } @@ -13803,7 +13801,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -14105,7 +14102,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14187,7 +14183,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -14392,7 +14387,6 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -14544,7 +14538,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -14558,7 +14551,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -15170,7 +15162,8 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/zod": { "version": "3.25.76", diff --git a/frontend/package.json b/frontend/package.json index b75c5109a..0656fcc79 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ "@embedpdf/plugin-interaction-manager": "^1.4.1", "@embedpdf/plugin-loader": "^1.4.1", "@embedpdf/plugin-pan": "^1.4.1", + "@embedpdf/plugin-print": "^1.4.1", "@embedpdf/plugin-render": "^1.4.1", "@embedpdf/plugin-rotate": "^1.4.1", "@embedpdf/plugin-scroll": "^1.4.1", diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml index 487c8d4cf..bb0b7a814 100644 --- a/frontend/public/locales/en-GB/translation.toml +++ b/frontend/public/locales/en-GB/translation.toml @@ -3899,6 +3899,7 @@ toggleSidebar = "Toggle Sidebar" exportSelected = "Export Selected Pages" toggleAnnotations = "Toggle Annotations Visibility" annotationMode = "Toggle Annotation Mode" +print = "Print PDF" draw = "Draw" save = "Save" saveChanges = "Save Changes" diff --git a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx index adef8c4fb..b953b2205 100644 --- a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx +++ b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx @@ -20,6 +20,7 @@ import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react'; import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react'; import { ExportPluginPackage } from '@embedpdf/plugin-export/react'; import { BookmarkPluginPackage } from '@embedpdf/plugin-bookmark'; +import { PrintPluginPackage } from '@embedpdf/plugin-print/react'; // Import annotation plugins import { HistoryPluginPackage } from '@embedpdf/plugin-history/react'; @@ -41,6 +42,7 @@ import { HistoryAPIBridge } from '@app/components/viewer/HistoryAPIBridge'; import type { SignatureAPI, HistoryAPI } from '@app/components/viewer/viewerTypes'; import { ExportAPIBridge } from '@app/components/viewer/ExportAPIBridge'; import { BookmarkAPIBridge } from '@app/components/viewer/BookmarkAPIBridge'; +import { PrintAPIBridge } from '@app/components/viewer/PrintAPIBridge'; import { isPdfFile } from '@app/utils/fileUtils'; import { useTranslation } from 'react-i18next'; import { LinkLayer } from '@app/components/viewer/LinkLayer'; @@ -156,6 +158,9 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur createPluginRegistration(ExportPluginPackage, { defaultFileName: 'document.pdf', }), + + // Register print plugin for printing PDFs + createPluginRegistration(PrintPluginPackage), ]; }, [pdfUrl]); @@ -301,6 +306,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur {enableAnnotations && } + { + if (print) { + // Register this bridge with ViewerContext + registerBridge('print', { + state: {}, + api: { + print: () => print.print(), + } + }); + } + }, [print, registerBridge]); + + return null; +} diff --git a/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx b/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx index c02de9278..995d7d095 100644 --- a/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx +++ b/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx @@ -24,6 +24,7 @@ export function useViewerRightRailButtons() { const rotateRightLabel = t('rightRail.rotateRight', 'Rotate Right'); const sidebarLabel = t('rightRail.toggleSidebar', 'Toggle Sidebar'); const bookmarkLabel = t('rightRail.toggleBookmarks', 'Toggle Bookmarks'); + const printLabel = t('rightRail.print', 'Print PDF'); const viewerButtons = useMemo(() => { return [ @@ -127,6 +128,17 @@ export function useViewerRightRailButtons() { viewer.toggleBookmarkSidebar(); } }, + { + id: 'viewer-print', + icon: , + tooltip: printLabel, + ariaLabel: printLabel, + section: 'top' as const, + order: 56, + onClick: () => { + viewer.printActions.print(); + } + }, { id: 'viewer-annotation-controls', section: 'top' as const, @@ -136,7 +148,7 @@ export function useViewerRightRailButtons() { ) } ]; - }, [t, i18n.language, viewer, isPanning, searchLabel, panLabel, rotateLeftLabel, rotateRightLabel, sidebarLabel, bookmarkLabel, tooltipPosition]); + }, [t, i18n.language, viewer, isPanning, searchLabel, panLabel, rotateLeftLabel, rotateRightLabel, sidebarLabel, bookmarkLabel, printLabel, tooltipPosition]); useRightRailButtons(viewerButtons); } diff --git a/frontend/src/core/contexts/ViewerContext.tsx b/frontend/src/core/contexts/ViewerContext.tsx index fc1eaf346..937c6067a 100644 --- a/frontend/src/core/contexts/ViewerContext.tsx +++ b/frontend/src/core/contexts/ViewerContext.tsx @@ -18,6 +18,7 @@ import { SearchActions, ExportActions, BookmarkActions, + PrintActions, } from '@app/contexts/viewer/viewerActions'; import { BridgeRef, @@ -125,6 +126,7 @@ interface ViewerContextType { searchActions: SearchActions; exportActions: ExportActions; bookmarkActions: BookmarkActions; + printActions: PrintActions; // Bridge registration - internal use by bridges registerBridge: ( @@ -277,6 +279,7 @@ export const ViewerProvider: React.FC = ({ children }) => { searchActions, exportActions, bookmarkActions, + printActions, } = createViewerActions({ registry: bridgeRefs, getScrollState, @@ -333,6 +336,7 @@ export const ViewerProvider: React.FC = ({ children }) => { searchActions, exportActions, bookmarkActions, + printActions, // Bridge registration registerBridge, diff --git a/frontend/src/core/contexts/viewer/viewerActions.ts b/frontend/src/core/contexts/viewer/viewerActions.ts index 05a38440a..d32c5077c 100644 --- a/frontend/src/core/contexts/viewer/viewerActions.ts +++ b/frontend/src/core/contexts/viewer/viewerActions.ts @@ -65,6 +65,10 @@ export interface BookmarkActions { setLocalBookmarks: (bookmarks: PdfBookmarkObject[] | null, error?: string | null) => void; } +export interface PrintActions { + print: () => void; +} + export interface ViewerActionsBundle { scrollActions: ScrollActions; zoomActions: ZoomActions; @@ -75,6 +79,7 @@ export interface ViewerActionsBundle { searchActions: SearchActions; exportActions: ExportActions; bookmarkActions: BookmarkActions; + printActions: PrintActions; } interface ViewerActionDependencies { @@ -332,5 +337,13 @@ export function createViewerActions({ api?.setLocalBookmarks?.(bookmarks ?? null, error); }, }, + printActions: { + print: () => { + const api = registry.current.print?.api; + if (api?.print) { + api.print(); + } + }, + }, }; } diff --git a/frontend/src/core/contexts/viewer/viewerBridges.ts b/frontend/src/core/contexts/viewer/viewerBridges.ts index e7f1ebf3c..66eb5ada0 100644 --- a/frontend/src/core/contexts/viewer/viewerBridges.ts +++ b/frontend/src/core/contexts/viewer/viewerBridges.ts @@ -49,6 +49,10 @@ export interface SearchAPIWrapper { goToResult: (index: number) => void; } +export interface PrintAPIWrapper { + print: () => void; +} + export interface ThumbnailAPIWrapper { renderThumb: (pageIndex: number, scale: number) => { toPromise: () => Promise; @@ -132,6 +136,7 @@ export interface BridgeStateMap { thumbnail: unknown; export: ExportState; bookmark: BookmarkState; + print: unknown; } export interface BridgeApiMap { @@ -145,6 +150,7 @@ export interface BridgeApiMap { thumbnail: ThumbnailAPIWrapper; export: ExportAPIWrapper; bookmark: BookmarkAPIWrapper; + print: PrintAPIWrapper; } export type BridgeKey = keyof BridgeStateMap; @@ -164,6 +170,7 @@ export const createBridgeRegistry = (): ViewerBridgeRegistry => ({ thumbnail: null, export: null, bookmark: null, + print: null, }); export function registerBridge(