mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
deps(embedPDF): Bump codebase to embedPDF v2.3.0 and adjust codebase for new features (#5567)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Signed-off-by: Balázs Szücs <[email protected]> Co-authored-by: Anthony Stirling <[email protected]>
This commit is contained in:
co-authored by
Anthony Stirling
parent
7fc6ec5fe1
commit
5c7d675960
Generated
+220
-178
@@ -11,27 +11,28 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@embedpdf/core": "^1.5.0",
|
"@embedpdf/core": "^2.3.0",
|
||||||
"@embedpdf/engines": "^1.5.0",
|
"@embedpdf/engines": "^2.3.0",
|
||||||
"@embedpdf/plugin-annotation": "^1.5.0",
|
"@embedpdf/models": "^2.3.0",
|
||||||
"@embedpdf/plugin-bookmark": "^1.5.0",
|
"@embedpdf/plugin-annotation": "^2.3.0",
|
||||||
"@embedpdf/plugin-export": "^1.5.0",
|
"@embedpdf/plugin-bookmark": "^2.3.0",
|
||||||
"@embedpdf/plugin-history": "^1.5.0",
|
"@embedpdf/plugin-document-manager": "^2.3.0",
|
||||||
"@embedpdf/plugin-interaction-manager": "^1.5.0",
|
"@embedpdf/plugin-export": "^2.3.0",
|
||||||
"@embedpdf/plugin-loader": "^1.5.0",
|
"@embedpdf/plugin-history": "^2.3.0",
|
||||||
"@embedpdf/plugin-pan": "^1.5.0",
|
"@embedpdf/plugin-interaction-manager": "^2.3.0",
|
||||||
"@embedpdf/plugin-print": "^1.5.0",
|
"@embedpdf/plugin-pan": "^2.3.0",
|
||||||
"@embedpdf/plugin-redaction": "^1.5.0",
|
"@embedpdf/plugin-print": "^2.3.0",
|
||||||
"@embedpdf/plugin-render": "^1.5.0",
|
"@embedpdf/plugin-redaction": "^2.3.0",
|
||||||
"@embedpdf/plugin-rotate": "^1.5.0",
|
"@embedpdf/plugin-render": "^2.3.0",
|
||||||
"@embedpdf/plugin-scroll": "^1.5.0",
|
"@embedpdf/plugin-rotate": "^2.3.0",
|
||||||
"@embedpdf/plugin-search": "^1.5.0",
|
"@embedpdf/plugin-scroll": "^2.3.0",
|
||||||
"@embedpdf/plugin-selection": "^1.5.0",
|
"@embedpdf/plugin-search": "^2.3.0",
|
||||||
"@embedpdf/plugin-spread": "^1.5.0",
|
"@embedpdf/plugin-selection": "^2.3.0",
|
||||||
"@embedpdf/plugin-thumbnail": "^1.5.0",
|
"@embedpdf/plugin-spread": "^2.3.0",
|
||||||
"@embedpdf/plugin-tiling": "^1.5.0",
|
"@embedpdf/plugin-thumbnail": "^2.3.0",
|
||||||
"@embedpdf/plugin-viewport": "^1.5.0",
|
"@embedpdf/plugin-tiling": "^2.3.0",
|
||||||
"@embedpdf/plugin-zoom": "^1.5.0",
|
"@embedpdf/plugin-viewport": "^2.3.0",
|
||||||
|
"@embedpdf/plugin-zoom": "^2.3.0",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@iconify/react": "^6.0.2",
|
"@iconify/react": "^6.0.2",
|
||||||
@@ -552,13 +553,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/core": {
|
"node_modules/@embedpdf/core": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-2.3.0.tgz",
|
||||||
"integrity": "sha512-Yrh9XoVaT8cUgzgqpJ7hx5wg6BqQrCFirqqlSwVb+Ly9oNn4fZbR9GycIWmzJOU5XBnaOJjXfQSaDyoNP0woNA==",
|
"integrity": "sha512-aPD7lNSCOLc5Nos9xGA3qAT5jFZdrTT7IVcpxtM1BOKa1FI0XmotJ8vgzcRxH/FLwUASC4xwR9QxzTKp2aLsZQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/engines": "1.5.0",
|
"@embedpdf/engines": "2.3.0",
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
@@ -569,13 +570,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/engines": {
|
"node_modules/@embedpdf/engines": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-2.3.0.tgz",
|
||||||
"integrity": "sha512-/GzhjHFHWfOaX7vjgFJX/pyq668wYjoda1bZ9MpwF/EF000Wwy2Q0AOhprjldPFz8ASKjwKwqsXmaqrK99yOAQ==",
|
"integrity": "sha512-QxNY58E2HgNgnbsTt5TnDUNvKoyabkf5IniGsiN5+rx6f4SFDpCnz3h1VJxNReWDyn9e16QlkUfgXX0qQWd3iQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0",
|
"@embedpdf/fonts-arabic": "1.0.0",
|
||||||
"@embedpdf/pdfium": "1.5.0"
|
"@embedpdf/fonts-hebrew": "1.0.0",
|
||||||
|
"@embedpdf/fonts-jp": "1.0.0",
|
||||||
|
"@embedpdf/fonts-kr": "1.0.0",
|
||||||
|
"@embedpdf/fonts-latin": "1.0.0",
|
||||||
|
"@embedpdf/fonts-sc": "1.0.0",
|
||||||
|
"@embedpdf/fonts-tc": "1.0.0",
|
||||||
|
"@embedpdf/models": "2.3.0",
|
||||||
|
"@embedpdf/pdfium": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
@@ -585,64 +593,125 @@
|
|||||||
"vue": ">=3.2.0"
|
"vue": ">=3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@embedpdf/fonts-arabic": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-arabic/-/fonts-arabic-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-SnGvQb+LwPZQO2WjjvlmXrJZolJUfLYbLZQSaYUw1vrQyMyJKT4LewvJGG+hZ+Yz2fz7OMIQ+4Gc98mGODZtOg==",
|
||||||
|
"license": "OFL-1.1"
|
||||||
|
},
|
||||||
|
"node_modules/@embedpdf/fonts-hebrew": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-hebrew/-/fonts-hebrew-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-5HVAKGL7VqPeTxxADDrSqAFBxfmAXdP8fIqrPwJIKkqdK2643bOer8CqnnpO3/nPoFhkzxhttWMB9BGiqSW62w==",
|
||||||
|
"license": "OFL-1.1"
|
||||||
|
},
|
||||||
|
"node_modules/@embedpdf/fonts-jp": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-jp/-/fonts-jp-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-BY2tv/mcICUUKf+M/bizf3RU65PMqKClJ/e5o9mgMibxyML0OQvEDwYMRPODQkKgJKXCO3ScHmVvcmXp6kt+fA==",
|
||||||
|
"license": "OFL-1.1"
|
||||||
|
},
|
||||||
|
"node_modules/@embedpdf/fonts-kr": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-kr/-/fonts-kr-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-bh88HXSvOBS581kgmihWY7Ijp9hBsvlmXogFG5LSNx9UBAobRcakZiFMGieRBc06hUSkpo7WhjaFM/z/SfQ8dQ==",
|
||||||
|
"license": "OFL-1.1"
|
||||||
|
},
|
||||||
|
"node_modules/@embedpdf/fonts-latin": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-latin/-/fonts-latin-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-LLYysdr8O6sRNzhmW3PbF3AeA8xnqvOi4XLFfIfNlW5uEZ+qsJdcfd78Q78sFJMhlaOAYFMziMMsnOzmx463rA==",
|
||||||
|
"license": "OFL-1.1"
|
||||||
|
},
|
||||||
|
"node_modules/@embedpdf/fonts-sc": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-sc/-/fonts-sc-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ETXl7XCwaQLSSvMO3EUDwMNqtL64kX2LlFxarTRi/NsIGGOIxUurGfKtrkmtnKHrWy1jAJSt6oxK2uJhvdvQIw==",
|
||||||
|
"license": "OFL-1.1"
|
||||||
|
},
|
||||||
|
"node_modules/@embedpdf/fonts-tc": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@embedpdf/fonts-tc/-/fonts-tc-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-rGZJbVD6DYS5BbXdpEMnWkpVF0Knar+bsiyb2o3+YRx7O8eyFubEBQUSUInirQk69HA6fc3GhYCg7TyC/oD76Q==",
|
||||||
|
"license": "OFL-1.1"
|
||||||
|
},
|
||||||
"node_modules/@embedpdf/models": {
|
"node_modules/@embedpdf/models": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-2.3.0.tgz",
|
||||||
"integrity": "sha512-x/1li3jdag+IzfZkcfRLKLqASLep4v6dgVi3z0JArwaicFra8k1IY2xaVTrwcZyx7pRb/rxvoO9yLHW0Y34NFw==",
|
"integrity": "sha512-YAH3YdXl/UOhVcvMPd6mtU+tJ3veh24Q5swRDfuWUsJ3L2CcAG2P+4pjj4EAwvWUQcmN/HlVOjVQL0PkbkytKw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/pdfium": {
|
"node_modules/@embedpdf/pdfium": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-2.3.0.tgz",
|
||||||
"integrity": "sha512-PI32t2U4ThZC907n2Iwr8E5WqmC574G83u3V9ysNFl29N9kasrY9RiLSzU4W/yQvXPjIbpQHBsbMKXLjCFBI9w==",
|
"integrity": "sha512-AIWHDDG24we1r8sWVO9Uae6V2ISXji2gIkZS3+CjtYowaBCpMTSu4QEQRnjQam2EWrEMVIJOXwBfx11TZKrxWA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-annotation": {
|
"node_modules/@embedpdf/plugin-annotation": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-2.3.0.tgz",
|
||||||
"integrity": "sha512-mxEPI6xYwOGaf9fYfoywuj6nwA10eHFPBuN066MzwphDk6DOHJGZ3Vq8zNQBXh20c/Lb25PL718D7MZWxZLUHg==",
|
"integrity": "sha512-TIN/OiDTg5tCNsebp1SWnS6aa7nnDvRrrZe3jx7Sg5IMEiZc6P3z+0aOjJtvoz0cp3Xi7Bb0PQsTLwo+bdfpVg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0",
|
"@embedpdf/models": "2.3.0",
|
||||||
"@embedpdf/utils": "1.5.0"
|
"@embedpdf/utils": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"@embedpdf/plugin-history": "1.5.0",
|
"@embedpdf/plugin-history": "2.3.0",
|
||||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
"@embedpdf/plugin-interaction-manager": "2.3.0",
|
||||||
"@embedpdf/plugin-selection": "1.5.0",
|
"@embedpdf/plugin-selection": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
|
"svelte": ">=5 <6",
|
||||||
"vue": ">=3.2.0"
|
"vue": ">=3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-bookmark": {
|
"node_modules/@embedpdf/plugin-bookmark": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-2.3.0.tgz",
|
||||||
"integrity": "sha512-s3C9PtVesy5X8Ds/C9TEElFiqfKGRklG/uNPTROpNoolfpi0h7qX2xqqh/9+FzKH2nHjVcPB7Pp432v16h7eRA==",
|
"integrity": "sha512-7XO2NntgRb/Jk1XN/EOf7+yVaOPVVFvBuF0xlCqnz2BGAnMNrTn8QE73FtluJBgNhuK9LwDT2C4W+BTD2gd59Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
|
"svelte": ">=5 <6",
|
||||||
|
"vue": ">=3.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@embedpdf/plugin-document-manager": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-document-manager/-/plugin-document-manager-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-hdKaWU1sjlLgXo2iWF4N734lklCfSO5Tj1xqk+0omxOpnVL1Ed5fzFO2N584pMkfFn1xo9Y2JPHSUtCdzF7/EQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@embedpdf/models": "2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@embedpdf/core": "2.3.0",
|
||||||
|
"preact": "^10.26.4",
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0",
|
||||||
|
"svelte": ">=5 <6",
|
||||||
"vue": ">=3.2.0"
|
"vue": ">=3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-export": {
|
"node_modules/@embedpdf/plugin-export": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-2.3.0.tgz",
|
||||||
"integrity": "sha512-luk68mNW9l2X31qk4b02phKaqDl9aDXUAgHVz1EWrgwXQ3Oz9WEdu60utYARYDiepDo3Caadll8RwctYSf/anA==",
|
"integrity": "sha512-Xa048lKnc1jehWbaWv5qER1RVIHhHqt+JhgzAlqFSURXmzowbUzVEDBZ7fYImXRkpqp+ZeyBhWfZ60DBNE55Cw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -651,31 +720,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-history": {
|
"node_modules/@embedpdf/plugin-history": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-2.3.0.tgz",
|
||||||
"integrity": "sha512-p7PTNNaIr4gH3jLwX+eLJe1DeUXgi21kVGN6SRx/pocH8esg4jqoOeD/YiRRZoZnPOiy0jBXVhkPkwSmY7a2hQ==",
|
"integrity": "sha512-+fr/kjK2Z9BiC53IMlUZvWjkD6iilcI3XCUKQPXRgS5MDAuwpVlgdAtc+3VAMlG3IddElxVFdvvxRO9R89k5Mg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
|
||||||
"react": ">=16.8.0",
|
|
||||||
"react-dom": ">=16.8.0",
|
|
||||||
"vue": ">=3.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@embedpdf/plugin-interaction-manager": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-ckHgTfvkW6c5Ta7Mc+Dl9C2foVnvEpqEJ84wyBnqrU0OWbe/jsiPhyKBVeartMGqNI/kVfaQTXupyrKhekAVmg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@embedpdf/models": "1.5.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@embedpdf/core": "1.5.0",
|
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -683,16 +736,16 @@
|
|||||||
"vue": ">=3.2.0"
|
"vue": ">=3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-loader": {
|
"node_modules/@embedpdf/plugin-interaction-manager": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-2.3.0.tgz",
|
||||||
"integrity": "sha512-P4YpIZfaW69etYIjphyaL4cGl2pB14h3OdTE0tRQ2pZYZHFLTvlt4q9B3PVSdhlSrHK5nob7jfLGon2U7xCslg==",
|
"integrity": "sha512-1/tDLPoQm6skNe/WOd6QD7SA0XRKphbJHi/s9XY4fhGgBvlD5XHFrYxtmrsaheYjqIBFtAWWZ3m5lAXRaO/igA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -701,17 +754,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-pan": {
|
"node_modules/@embedpdf/plugin-pan": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-2.3.0.tgz",
|
||||||
"integrity": "sha512-EMQ08dHqLkZmFVuLOO6h3AAinFPQoA1r6OlL9z+p0sswq31JAgd4X7+xjYIpI01z/V3+cTzPHzp7qwob5E4tbA==",
|
"integrity": "sha512-5yGxLpn28PHKCYx3tjzeVir7D5vHZ0Fk9HJRJr4K+Uqbg8pYFavb9tseXzPE4FcqpejqZo2DZyfo54ErQFXEyQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
"@embedpdf/plugin-interaction-manager": "2.3.0",
|
||||||
"@embedpdf/plugin-viewport": "1.5.0",
|
"@embedpdf/plugin-viewport": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -720,15 +773,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-print": {
|
"node_modules/@embedpdf/plugin-print": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-2.3.0.tgz",
|
||||||
"integrity": "sha512-rjorvNxAZfO9X4cFZVU9fHnldMWqMceJGmr3mH+yj7KdHePvNDDP+omyZyZKtxlUZENaeDI2h6k5z0GbhBz6sQ==",
|
"integrity": "sha512-LNxvXm3rZkRXXC41IArBDiwPLzSflmBmxxi+L+91xvw8n/FWUeXfWwQn7oQEAGq9Ha/3pEVHTls48QSFZN0mhg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=18.0.0",
|
"react": ">=18.0.0",
|
||||||
"react-dom": ">=18.0.0",
|
"react-dom": ">=18.0.0",
|
||||||
@@ -737,34 +790,35 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-redaction": {
|
"node_modules/@embedpdf/plugin-redaction": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-2.3.0.tgz",
|
||||||
"integrity": "sha512-txiukr5UKAGvJzl6dVBmmIT1v3r/t4e2qYm1hqU2faGgNCa2dwk79x9mDBlvWwxlJXCDFuFE+7Ps9/nU6qmU2w==",
|
"integrity": "sha512-un6AQL5Pqcm9v1tCV9Mb3NeowsGUtlCT/198k4nd+SWOMWNsbuFqI+rWOGV3auqXRGSzKj0gnt29t8aaeLpLeA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0",
|
"@embedpdf/models": "2.3.0",
|
||||||
"@embedpdf/utils": "1.5.0"
|
"@embedpdf/utils": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
"@embedpdf/plugin-interaction-manager": "2.3.0",
|
||||||
"@embedpdf/plugin-selection": "1.5.0",
|
"@embedpdf/plugin-selection": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
|
"svelte": ">=5 <6",
|
||||||
"vue": ">=3.2.0"
|
"vue": ">=3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-render": {
|
"node_modules/@embedpdf/plugin-render": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-2.3.0.tgz",
|
||||||
"integrity": "sha512-ywwSj0ByrlkvrJIHKRzqxARkOZriki8VJUC+T4MV8fGyF4CzvCRJyKlPktahFz+VxhoodqTh7lBCib68dH+GvA==",
|
"integrity": "sha512-UyQncK5NTokuEVISUcxPOXpZP4SItn4MjfeEaPsTXJkSRjHL4g3mU3iWy0nXJMCOT10OB+5m7qQ0/KkF4f+b5w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -773,15 +827,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-rotate": {
|
"node_modules/@embedpdf/plugin-rotate": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-2.3.0.tgz",
|
||||||
"integrity": "sha512-5EmBCsq0VfrE3xWY6ofuVm8S6aK95EbAycRIk1wczcmTdvpsuXZ6P2ZaECUgYMcpZ6uAg4/kGf8X8VVZuCihSQ==",
|
"integrity": "sha512-vibDXHA0L2LlMrmkSuanmdtUpc2JPBuQybiGwf9F4wlleKN3f7uSWxZsHdVAxWdzsaG+/26QTGl75otZLnVuig==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -790,16 +844,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-scroll": {
|
"node_modules/@embedpdf/plugin-scroll": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-2.3.0.tgz",
|
||||||
"integrity": "sha512-RNmTZCZ8X1mA8cw9M7TMDuhO9GtkOalGha2bBL3En3D1IlDRS7PzNNMSMV7eqT7OQICSTltlpJ8p8Qi5esvL/Q==",
|
"integrity": "sha512-8pdaSY9QuqdX22Ykw2jKn07Rx6FIsDdj/O0+mlbccY/ISofj9WEFNeQgnOY64OUTDyurJYqpYvq6QqvgbGLs+A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"@embedpdf/plugin-viewport": "1.5.0",
|
"@embedpdf/plugin-viewport": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -808,16 +862,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-search": {
|
"node_modules/@embedpdf/plugin-search": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-2.3.0.tgz",
|
||||||
"integrity": "sha512-TB5b0H8Iobx/azVUBIlG2ClaKtf0y3/Xi3E/iB8BwvkIE2+g6EGfp8IMXIn8WDXST6bbvJEP31Ab0Ilp6SVkiw==",
|
"integrity": "sha512-VNXmNf7fIIRWGVwf2kIUeUeLkUTJlq9AGjUO2TyuYJTWTsmfT4LEqPDDpwC6NDVFhzWE6xwbb3bxvY/9bqBMzw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"@embedpdf/plugin-loader": "1.5.0",
|
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -826,17 +879,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-selection": {
|
"node_modules/@embedpdf/plugin-selection": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-2.3.0.tgz",
|
||||||
"integrity": "sha512-zrxLBAZQoPswDuf9q9DrYaQc6B0Ysc2U1hueTjNH/4+ydfl0BFXZkKR63C2e3YmWtXvKjkoIj0GyPzsiBORLUw==",
|
"integrity": "sha512-+emaY4vff3ynAf5C3PfCOlleQIqiImbBpb6zkG5SVUa9Vn5x0SfYGT4Jumtbzq8XBknC1QIRKVlplC9BcnjcmQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0",
|
||||||
|
"@embedpdf/utils": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
"@embedpdf/plugin-interaction-manager": "2.3.0",
|
||||||
"@embedpdf/plugin-viewport": "1.5.0",
|
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -845,16 +898,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-spread": {
|
"node_modules/@embedpdf/plugin-spread": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-2.3.0.tgz",
|
||||||
"integrity": "sha512-3EU5Cp+fPQSiMjvMR/P2kXxXry/RlnxHLs4JeskAaH95QcqWW3VD+DrHkWSiLFkdhI18rNNGNlMc5RvDGvbXGQ==",
|
"integrity": "sha512-sFqYKwzKGPaCXn6hAyv6GHdVTlL2vg3poxRNd2W5kLQo07YtHlSjXr/XAhaGT/a4GtR9rtbSJ4hWNJjzIcwE0g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"@embedpdf/plugin-loader": "1.5.0",
|
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -863,16 +915,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-thumbnail": {
|
"node_modules/@embedpdf/plugin-thumbnail": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-2.3.0.tgz",
|
||||||
"integrity": "sha512-Z2qpyyr5s2M6460KDGu1Vk6rdbQFIoCpnyFAT6e7UaTIKkqJSNpmjqMsBU5PosYCFu/cClpHPvS7tg9/IKAk6g==",
|
"integrity": "sha512-CAOnipeBtdKSHGBuIm5420GykUw7k2rB7Z9GwouTbbycS7Cw+kiaGpOfHfenoKPTlWMkHYAwFcZiWKV3XG/nRQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"@embedpdf/plugin-render": "1.5.0",
|
"@embedpdf/plugin-render": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -881,18 +933,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-tiling": {
|
"node_modules/@embedpdf/plugin-tiling": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-2.3.0.tgz",
|
||||||
"integrity": "sha512-0Vx9elHNpMM+zv8hEoZXBEm8Q0+4kU52LxOlTYRr1A5FskF836sUct6g1ngwK1bmfbAfpz+62PnYI2EeilDZig==",
|
"integrity": "sha512-6VJ042WksIyZVWyvXq1nf0Ct+U4Pl6+QUDy1ThJefwk/HKDfWU2zEr/+1STJKVWgfUx5QRdipf6Jghd+HnOg3Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"@embedpdf/plugin-render": "1.5.0",
|
"@embedpdf/plugin-render": "2.3.0",
|
||||||
"@embedpdf/plugin-scroll": "1.5.0",
|
"@embedpdf/plugin-scroll": "2.3.0",
|
||||||
"@embedpdf/plugin-viewport": "1.5.0",
|
"@embedpdf/plugin-viewport": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -901,15 +953,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-viewport": {
|
"node_modules/@embedpdf/plugin-viewport": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-2.3.0.tgz",
|
||||||
"integrity": "sha512-G8GDyYRhfehw72+r4qKkydnA5+AU8qH67g01Y12b0DzI0VIzymh/05Z4dK8DsY3jyWPXJfw2hlg5+KDHaMBHgQ==",
|
"integrity": "sha512-3NQp3hVfRF7DMUPNAVOfZsqQQrugEfY0voRUrQI90eyi16GFntN3CP9Mc5cOp2jnUICMYlirQ/om+KCseMHS2Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0"
|
"@embedpdf/models": "2.3.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -918,19 +970,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/plugin-zoom": {
|
"node_modules/@embedpdf/plugin-zoom": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-2.3.0.tgz",
|
||||||
"integrity": "sha512-LiDkCd5/IXg2CRORl1Yikan2op+AYXSxhHzCFatyBdwzVj+n4y9I74OwCI62Mar8WDAIMyXZDCQxGPToSm+zDw==",
|
"integrity": "sha512-wnBqK02ku0zCViqQfSD1Vohy+aBUogXrqUTwo1/1QFEphmgnCHnHbEUduh9M0ghcT4s26pBgbJqCrShpGYAdvQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@embedpdf/models": "1.5.0",
|
"@embedpdf/models": "2.3.0"
|
||||||
"hammerjs": "^2.0.8"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@embedpdf/core": "1.5.0",
|
"@embedpdf/core": "2.3.0",
|
||||||
"@embedpdf/plugin-interaction-manager": "1.5.0",
|
"@embedpdf/plugin-scroll": "2.3.0",
|
||||||
"@embedpdf/plugin-scroll": "1.5.0",
|
"@embedpdf/plugin-viewport": "2.3.0",
|
||||||
"@embedpdf/plugin-viewport": "1.5.0",
|
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
@@ -939,14 +989,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@embedpdf/utils": {
|
"node_modules/@embedpdf/utils": {
|
||||||
"version": "1.5.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-2.3.0.tgz",
|
||||||
"integrity": "sha512-L6jsAPQPGM8ne+MMFAd5gqXb1RNEgNyh16VvVUVKcVnJlBhwil59nVeEQ0cwPhjF5qVeY6MQDIOjBzJqkgXOYg==",
|
"integrity": "sha512-9DV+tu+GsnijchNSG/NzslnxTGIUH6j2MxBR8QOoZLsWETEVaMLkHtbvzXPyMOx/5RlvBn8wR0jNKTNptOCnXQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"preact": "^10.26.4",
|
"preact": "^10.26.4",
|
||||||
"react": ">=16.8.0",
|
"react": ">=16.8.0",
|
||||||
"react-dom": ">=16.8.0",
|
"react-dom": ">=16.8.0",
|
||||||
|
"svelte": ">=5 <6",
|
||||||
"vue": ">=3.2.0"
|
"vue": ">=3.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -8929,15 +8980,6 @@
|
|||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/hammerjs": {
|
|
||||||
"version": "2.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
|
|
||||||
"integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/has-bigints": {
|
"node_modules/has-bigints": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||||
|
|||||||
+22
-21
@@ -7,27 +7,28 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@embedpdf/core": "^1.5.0",
|
"@embedpdf/core": "^2.3.0",
|
||||||
"@embedpdf/engines": "^1.5.0",
|
"@embedpdf/engines": "^2.3.0",
|
||||||
"@embedpdf/plugin-annotation": "^1.5.0",
|
"@embedpdf/models": "^2.3.0",
|
||||||
"@embedpdf/plugin-bookmark": "^1.5.0",
|
"@embedpdf/plugin-annotation": "^2.3.0",
|
||||||
"@embedpdf/plugin-export": "^1.5.0",
|
"@embedpdf/plugin-bookmark": "^2.3.0",
|
||||||
"@embedpdf/plugin-history": "^1.5.0",
|
"@embedpdf/plugin-export": "^2.3.0",
|
||||||
"@embedpdf/plugin-interaction-manager": "^1.5.0",
|
"@embedpdf/plugin-history": "^2.3.0",
|
||||||
"@embedpdf/plugin-loader": "^1.5.0",
|
"@embedpdf/plugin-document-manager": "^2.3.0",
|
||||||
"@embedpdf/plugin-pan": "^1.5.0",
|
"@embedpdf/plugin-interaction-manager": "^2.3.0",
|
||||||
"@embedpdf/plugin-print": "^1.5.0",
|
"@embedpdf/plugin-pan": "^2.3.0",
|
||||||
"@embedpdf/plugin-redaction": "^1.5.0",
|
"@embedpdf/plugin-print": "^2.3.0",
|
||||||
"@embedpdf/plugin-render": "^1.5.0",
|
"@embedpdf/plugin-redaction": "^2.3.0",
|
||||||
"@embedpdf/plugin-rotate": "^1.5.0",
|
"@embedpdf/plugin-render": "^2.3.0",
|
||||||
"@embedpdf/plugin-scroll": "^1.5.0",
|
"@embedpdf/plugin-rotate": "^2.3.0",
|
||||||
"@embedpdf/plugin-search": "^1.5.0",
|
"@embedpdf/plugin-scroll": "^2.3.0",
|
||||||
"@embedpdf/plugin-selection": "^1.5.0",
|
"@embedpdf/plugin-search": "^2.3.0",
|
||||||
"@embedpdf/plugin-spread": "^1.5.0",
|
"@embedpdf/plugin-selection": "^2.3.0",
|
||||||
"@embedpdf/plugin-thumbnail": "^1.5.0",
|
"@embedpdf/plugin-spread": "^2.3.0",
|
||||||
"@embedpdf/plugin-tiling": "^1.5.0",
|
"@embedpdf/plugin-thumbnail": "^2.3.0",
|
||||||
"@embedpdf/plugin-viewport": "^1.5.0",
|
"@embedpdf/plugin-tiling": "^2.3.0",
|
||||||
"@embedpdf/plugin-zoom": "^1.5.0",
|
"@embedpdf/plugin-viewport": "^2.3.0",
|
||||||
|
"@embedpdf/plugin-zoom": "^2.3.0",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@iconify/react": "^6.0.2",
|
"@iconify/react": "^6.0.2",
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import React, { createContext, useContext, useState, useEffect, useRef } from 'react';
|
||||||
|
import { useDocumentManagerPlugin } from '@embedpdf/plugin-document-manager/react';
|
||||||
|
|
||||||
|
interface ActiveDocumentContextType {
|
||||||
|
documentId: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActiveDocumentContext = createContext<ActiveDocumentContextType>({ documentId: null });
|
||||||
|
|
||||||
|
export function ActiveDocumentProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const { plugin, isLoading } = useDocumentManagerPlugin();
|
||||||
|
const [documentId, setDocumentId] = useState<string | null>(null);
|
||||||
|
const unsubscribeRef = useRef<(() => void) | null>(null);
|
||||||
|
const documentIdRef = useRef<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoading || !plugin) return;
|
||||||
|
|
||||||
|
const docManagerApi = plugin.provides?.();
|
||||||
|
if (!docManagerApi) return;
|
||||||
|
|
||||||
|
// Get initial active document (synchronously if available)
|
||||||
|
const activeDoc = docManagerApi.getActiveDocument?.();
|
||||||
|
if (activeDoc?.id && activeDoc.id !== documentIdRef.current) {
|
||||||
|
documentIdRef.current = activeDoc.id;
|
||||||
|
setDocumentId(activeDoc.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to document changes (only if not already subscribed)
|
||||||
|
if (!unsubscribeRef.current && docManagerApi.onDocumentOpened) {
|
||||||
|
unsubscribeRef.current = docManagerApi.onDocumentOpened((event: any) => {
|
||||||
|
const docId = event?.documentId || event?.id || event?.document?.id;
|
||||||
|
if (docId && docId !== documentIdRef.current) {
|
||||||
|
documentIdRef.current = docId;
|
||||||
|
setDocumentId(docId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: We don't unsubscribe on effect cleanup to avoid re-subscribing on every render
|
||||||
|
// Cleanup happens only on unmount
|
||||||
|
}, [plugin, isLoading]);
|
||||||
|
|
||||||
|
// Cleanup on unmount only
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (unsubscribeRef.current) {
|
||||||
|
unsubscribeRef.current();
|
||||||
|
unsubscribeRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActiveDocumentContext.Provider value={{ documentId }}>
|
||||||
|
{children}
|
||||||
|
</ActiveDocumentContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useActiveDocument(): string | null {
|
||||||
|
const context = useContext(ActiveDocumentContext);
|
||||||
|
return context.documentId;
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import type {
|
|||||||
} from '@app/components/viewer/viewerTypes';
|
} from '@app/components/viewer/viewerTypes';
|
||||||
|
|
||||||
type NoteIcon = NonNullable<AnnotationToolOptions['icon']>;
|
type NoteIcon = NonNullable<AnnotationToolOptions['icon']>;
|
||||||
|
|
||||||
type AnnotationDefaults =
|
type AnnotationDefaults =
|
||||||
| {
|
| {
|
||||||
type:
|
type:
|
||||||
@@ -17,13 +18,15 @@ type AnnotationDefaults =
|
|||||||
| PdfAnnotationSubtype.UNDERLINE
|
| PdfAnnotationSubtype.UNDERLINE
|
||||||
| PdfAnnotationSubtype.STRIKEOUT
|
| PdfAnnotationSubtype.STRIKEOUT
|
||||||
| PdfAnnotationSubtype.SQUIGGLY;
|
| PdfAnnotationSubtype.SQUIGGLY;
|
||||||
color: string;
|
strokeColor: string;
|
||||||
|
color?: string;
|
||||||
opacity: number;
|
opacity: number;
|
||||||
customData?: Record<string, unknown>;
|
customData?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: PdfAnnotationSubtype.INK;
|
type: PdfAnnotationSubtype.INK;
|
||||||
color: string;
|
strokeColor: string;
|
||||||
|
color?: string;
|
||||||
opacity?: number;
|
opacity?: number;
|
||||||
borderWidth?: number;
|
borderWidth?: number;
|
||||||
strokeWidth?: number;
|
strokeWidth?: number;
|
||||||
@@ -37,7 +40,8 @@ type AnnotationDefaults =
|
|||||||
fontFamily?: string;
|
fontFamily?: string;
|
||||||
textAlign?: number;
|
textAlign?: number;
|
||||||
opacity?: number;
|
opacity?: number;
|
||||||
backgroundColor?: string;
|
color?: string;
|
||||||
|
interiorColor?: string;
|
||||||
borderWidth?: number;
|
borderWidth?: number;
|
||||||
contents?: string;
|
contents?: string;
|
||||||
icon?: PdfAnnotationIcon;
|
icon?: PdfAnnotationIcon;
|
||||||
@@ -106,7 +110,7 @@ const DEFAULTS = {
|
|||||||
ink: '#1f2933',
|
ink: '#1f2933',
|
||||||
inkHighlighter: '#ffd54f',
|
inkHighlighter: '#ffd54f',
|
||||||
text: '#111111',
|
text: '#111111',
|
||||||
note: '#ffd54f', // match highlight color
|
note: '#ffd54f',
|
||||||
shapeFill: '#0000ff',
|
shapeFill: '#0000ff',
|
||||||
shapeStroke: '#cf5b5b',
|
shapeStroke: '#cf5b5b',
|
||||||
shapeOpacity: 0.5,
|
shapeOpacity: 0.5,
|
||||||
@@ -124,42 +128,62 @@ const buildStampDefaults: ToolDefaultsBuilder = (options) => ({
|
|||||||
...withCustomData(options),
|
...withCustomData(options),
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildInkDefaults = (options?: AnnotationToolOptions, opacityOverride?: number): AnnotationDefaults => ({
|
const buildInkDefaults = (options?: AnnotationToolOptions, opacityOverride?: number): AnnotationDefaults => {
|
||||||
|
const colorValue = options?.color ?? (opacityOverride ? DEFAULTS.inkHighlighter : DEFAULTS.ink);
|
||||||
|
return {
|
||||||
type: PdfAnnotationSubtype.INK,
|
type: PdfAnnotationSubtype.INK,
|
||||||
color: options?.color ?? (opacityOverride ? DEFAULTS.inkHighlighter : DEFAULTS.ink),
|
strokeColor: colorValue,
|
||||||
|
color: colorValue,
|
||||||
opacity: options?.opacity ?? opacityOverride ?? 1,
|
opacity: options?.opacity ?? opacityOverride ?? 1,
|
||||||
borderWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
borderWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
||||||
strokeWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
strokeWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
||||||
lineWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
lineWidth: options?.thickness ?? (opacityOverride ? 6 : 2),
|
||||||
...withCustomData(options),
|
...withCustomData(options),
|
||||||
});
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const TOOL_DEFAULT_BUILDERS: Record<AnnotationToolId, ToolDefaultsBuilder> = {
|
const TOOL_DEFAULT_BUILDERS: Record<AnnotationToolId, ToolDefaultsBuilder> = {
|
||||||
select: () => null,
|
select: () => null,
|
||||||
highlight: (options) => ({
|
highlight: (options) => {
|
||||||
|
const colorValue = options?.color ?? DEFAULTS.highlight;
|
||||||
|
return {
|
||||||
type: PdfAnnotationSubtype.HIGHLIGHT,
|
type: PdfAnnotationSubtype.HIGHLIGHT,
|
||||||
color: options?.color ?? DEFAULTS.highlight,
|
strokeColor: colorValue,
|
||||||
|
color: colorValue,
|
||||||
opacity: options?.opacity ?? 0.6,
|
opacity: options?.opacity ?? 0.6,
|
||||||
...withCustomData(options),
|
...withCustomData(options),
|
||||||
}),
|
};
|
||||||
underline: (options) => ({
|
},
|
||||||
|
underline: (options) => {
|
||||||
|
const colorValue = options?.color ?? DEFAULTS.underline;
|
||||||
|
return {
|
||||||
type: PdfAnnotationSubtype.UNDERLINE,
|
type: PdfAnnotationSubtype.UNDERLINE,
|
||||||
color: options?.color ?? DEFAULTS.underline,
|
strokeColor: colorValue,
|
||||||
|
color: colorValue,
|
||||||
opacity: options?.opacity ?? 1,
|
opacity: options?.opacity ?? 1,
|
||||||
...withCustomData(options),
|
...withCustomData(options),
|
||||||
}),
|
};
|
||||||
strikeout: (options) => ({
|
},
|
||||||
|
strikeout: (options) => {
|
||||||
|
const colorValue = options?.color ?? DEFAULTS.strikeout;
|
||||||
|
return {
|
||||||
type: PdfAnnotationSubtype.STRIKEOUT,
|
type: PdfAnnotationSubtype.STRIKEOUT,
|
||||||
color: options?.color ?? DEFAULTS.strikeout,
|
strokeColor: colorValue,
|
||||||
|
color: colorValue,
|
||||||
opacity: options?.opacity ?? 1,
|
opacity: options?.opacity ?? 1,
|
||||||
...withCustomData(options),
|
...withCustomData(options),
|
||||||
}),
|
};
|
||||||
squiggly: (options) => ({
|
},
|
||||||
|
squiggly: (options) => {
|
||||||
|
const colorValue = options?.color ?? DEFAULTS.squiggly;
|
||||||
|
return {
|
||||||
type: PdfAnnotationSubtype.SQUIGGLY,
|
type: PdfAnnotationSubtype.SQUIGGLY,
|
||||||
color: options?.color ?? DEFAULTS.squiggly,
|
strokeColor: colorValue,
|
||||||
|
color: colorValue,
|
||||||
opacity: options?.opacity ?? 1,
|
opacity: options?.opacity ?? 1,
|
||||||
...withCustomData(options),
|
...withCustomData(options),
|
||||||
}),
|
};
|
||||||
|
},
|
||||||
ink: (options) => buildInkDefaults(options),
|
ink: (options) => buildInkDefaults(options),
|
||||||
inkHighlighter: (options) => buildInkDefaults(options, options?.opacity ?? 0.6),
|
inkHighlighter: (options) => buildInkDefaults(options, options?.opacity ?? 0.6),
|
||||||
text: (options) => ({
|
text: (options) => ({
|
||||||
@@ -170,21 +194,21 @@ const TOOL_DEFAULT_BUILDERS: Record<AnnotationToolId, ToolDefaultsBuilder> = {
|
|||||||
textAlign: options?.textAlign ?? 0,
|
textAlign: options?.textAlign ?? 0,
|
||||||
opacity: options?.opacity ?? 1,
|
opacity: options?.opacity ?? 1,
|
||||||
borderWidth: options?.thickness ?? 1,
|
borderWidth: options?.thickness ?? 1,
|
||||||
...(options?.fillColor ? { backgroundColor: options.fillColor } : {}),
|
...(options?.fillColor ? { color: options.fillColor, interiorColor: options.fillColor } : {}),
|
||||||
...withCustomData(options),
|
...withCustomData(options),
|
||||||
}),
|
}),
|
||||||
note: (options) => {
|
note: (options) => {
|
||||||
const backgroundColor = options?.fillColor ?? DEFAULTS.note;
|
const bgColor = options?.fillColor ?? DEFAULTS.note;
|
||||||
const fontColor = options?.color ?? DEFAULTS.text;
|
const fontColor = options?.color ?? DEFAULTS.text;
|
||||||
return {
|
return {
|
||||||
type: PdfAnnotationSubtype.FREETEXT,
|
type: PdfAnnotationSubtype.FREETEXT,
|
||||||
fontColor,
|
fontColor,
|
||||||
color: fontColor,
|
|
||||||
fontFamily: options?.fontFamily ?? 'Helvetica',
|
fontFamily: options?.fontFamily ?? 'Helvetica',
|
||||||
textAlign: options?.textAlign ?? 0,
|
textAlign: options?.textAlign ?? 0,
|
||||||
fontSize: options?.fontSize ?? 12,
|
fontSize: options?.fontSize ?? 12,
|
||||||
opacity: options?.opacity ?? 1,
|
opacity: options?.opacity ?? 1,
|
||||||
backgroundColor,
|
color: bgColor,
|
||||||
|
interiorColor: bgColor,
|
||||||
borderWidth: options?.thickness ?? 0,
|
borderWidth: options?.thickness ?? 0,
|
||||||
contents: options?.contents ?? 'Note',
|
contents: options?.contents ?? 'Note',
|
||||||
icon: getIconEnum(options?.icon),
|
icon: getIconEnum(options?.icon),
|
||||||
@@ -317,7 +341,11 @@ export const AnnotationAPIBridge = forwardRef<AnnotationAPI>(function Annotation
|
|||||||
// `this`/state dependency (e.g. reading `selectedUid` from undefined).
|
// `this`/state dependency (e.g. reading `selectedUid` from undefined).
|
||||||
// If that happens, fail gracefully and treat it as "no selection"
|
// If that happens, fail gracefully and treat it as "no selection"
|
||||||
// instead of crashing the entire annotations tool.
|
// instead of crashing the entire annotations tool.
|
||||||
|
// Only log unexpected errors - "No active document" is a common expected state during init
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
if (!errorMessage.includes('No active document')) {
|
||||||
console.error('[AnnotationAPIBridge] getSelectedAnnotation failed:', error);
|
console.error('[AnnotationAPIBridge] getSelectedAnnotation failed:', error);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useEffect, useMemo, useState, useCallback } from 'react';
|
import { useEffect, useMemo, useState, useCallback } from 'react';
|
||||||
import { useBookmarkCapability } from '@embedpdf/plugin-bookmark/react';
|
import { useBookmarkCapability, BookmarkCapability } from '@embedpdf/plugin-bookmark/react';
|
||||||
import { BookmarkCapability } from '@embedpdf/plugin-bookmark';
|
|
||||||
import { useViewer } from '@app/contexts/ViewerContext';
|
import { useViewer } from '@app/contexts/ViewerContext';
|
||||||
import { BookmarkState, BookmarkAPIWrapper } from '@app/contexts/viewer/viewerBridges';
|
import { BookmarkState, BookmarkAPIWrapper } from '@app/contexts/viewer/viewerBridges';
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
import { useDocumentState } from '@embedpdf/core/react';
|
||||||
import { useSearch } from '@embedpdf/plugin-search/react';
|
import { useSearch } from '@embedpdf/plugin-search/react';
|
||||||
import { useViewer } from '@app/contexts/ViewerContext';
|
|
||||||
import { SEARCH_CONSTANTS } from '@app/components/viewer/constants/search';
|
import { SEARCH_CONSTANTS } from '@app/components/viewer/constants/search';
|
||||||
|
|
||||||
interface SearchLayerProps {
|
interface SearchLayerProps {
|
||||||
|
documentId?: string;
|
||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
scale: number;
|
scale?: number;
|
||||||
highlightColor?: string;
|
highlightColor?: string;
|
||||||
activeHighlightColor?: string;
|
activeHighlightColor?: string;
|
||||||
opacity?: number;
|
opacity?: number;
|
||||||
@@ -25,18 +26,22 @@ interface SearchResultState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CustomSearchLayer({
|
export function CustomSearchLayer({
|
||||||
|
documentId = '',
|
||||||
pageIndex,
|
pageIndex,
|
||||||
scale,
|
scale: scaleProp,
|
||||||
highlightColor = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.BACKGROUND,
|
highlightColor = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.BACKGROUND,
|
||||||
activeHighlightColor = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.ACTIVE_BACKGROUND,
|
activeHighlightColor = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.ACTIVE_BACKGROUND,
|
||||||
opacity = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.OPACITY,
|
opacity = SEARCH_CONSTANTS.HIGHLIGHT_COLORS.OPACITY,
|
||||||
padding = SEARCH_CONSTANTS.UI.HIGHLIGHT_PADDING,
|
padding = SEARCH_CONSTANTS.UI.HIGHLIGHT_PADDING,
|
||||||
borderRadius = 4
|
borderRadius = 4
|
||||||
}: SearchLayerProps) {
|
}: SearchLayerProps) {
|
||||||
const { provides: searchProvides } = useSearch();
|
const { provides: searchProvides } = useSearch(documentId);
|
||||||
const { scrollActions } = useViewer();
|
const documentState = useDocumentState(documentId);
|
||||||
const [searchResultState, setSearchResultState] = useState<SearchResultState | null>(null);
|
const [searchResultState, setSearchResultState] = useState<SearchResultState | null>(null);
|
||||||
|
|
||||||
|
// Use document scale from EmbedPDF state, fallback to prop, then to 1
|
||||||
|
const scale = documentState?.scale ?? scaleProp ?? 1;
|
||||||
|
|
||||||
// Subscribe to search result state changes
|
// Subscribe to search result state changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!searchProvides) {
|
if (!searchProvides) {
|
||||||
@@ -45,16 +50,8 @@ export function CustomSearchLayer({
|
|||||||
|
|
||||||
const unsubscribe = searchProvides.onSearchResultStateChange?.((state: SearchResultState) => {
|
const unsubscribe = searchProvides.onSearchResultStateChange?.((state: SearchResultState) => {
|
||||||
if (!state) return;
|
if (!state) return;
|
||||||
|
// Only update state - do NOT auto-scroll here
|
||||||
// Auto-scroll to active search result
|
// Scrolling should only happen when user explicitly navigates via next/previous
|
||||||
if (state.results && state.activeResultIndex !== undefined && state.activeResultIndex >= 0) {
|
|
||||||
const activeResult = state.results[state.activeResultIndex];
|
|
||||||
if (activeResult) {
|
|
||||||
const pageNumber = activeResult.pageIndex + 1; // Convert to 1-based page number
|
|
||||||
scrollActions.scrollToPage(pageNumber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setSearchResultState(state);
|
setSearchResultState(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
import { useViewer } from '@app/contexts/ViewerContext';
|
||||||
|
import {
|
||||||
|
PdfPermissionFlag,
|
||||||
|
DocumentPermissionsState,
|
||||||
|
DocumentPermissionsAPIWrapper,
|
||||||
|
} from '@app/contexts/viewer/viewerBridges';
|
||||||
|
|
||||||
|
function hasPermissionFlag(permissions: number, flag: PdfPermissionFlag): boolean {
|
||||||
|
if (permissions === 0 || permissions === PdfPermissionFlag.AllowAll) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (permissions & flag) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DocumentPermissionsAPIBridgeProps {
|
||||||
|
isEncrypted?: boolean;
|
||||||
|
isOwnerUnlocked?: boolean;
|
||||||
|
permissions?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DocumentPermissionsAPIBridge({
|
||||||
|
isEncrypted = false,
|
||||||
|
isOwnerUnlocked = false,
|
||||||
|
permissions = PdfPermissionFlag.AllowAll,
|
||||||
|
}: DocumentPermissionsAPIBridgeProps) {
|
||||||
|
const { registerBridge } = useViewer();
|
||||||
|
|
||||||
|
const state = useMemo<DocumentPermissionsState>(() => ({
|
||||||
|
isEncrypted,
|
||||||
|
isOwnerUnlocked,
|
||||||
|
permissions,
|
||||||
|
canPrint: hasPermissionFlag(permissions, PdfPermissionFlag.Print),
|
||||||
|
canModifyContents: hasPermissionFlag(permissions, PdfPermissionFlag.ModifyContents),
|
||||||
|
canCopyContents: hasPermissionFlag(permissions, PdfPermissionFlag.CopyContents),
|
||||||
|
canModifyAnnotations: hasPermissionFlag(permissions, PdfPermissionFlag.ModifyAnnotations),
|
||||||
|
canFillForms: hasPermissionFlag(permissions, PdfPermissionFlag.FillForms),
|
||||||
|
canExtractForAccessibility: hasPermissionFlag(permissions, PdfPermissionFlag.ExtractForAccessibility),
|
||||||
|
canAssembleDocument: hasPermissionFlag(permissions, PdfPermissionFlag.AssembleDocument),
|
||||||
|
canPrintHighQuality: hasPermissionFlag(permissions, PdfPermissionFlag.PrintHighQuality),
|
||||||
|
}), [isEncrypted, isOwnerUnlocked, permissions]);
|
||||||
|
|
||||||
|
const api = useMemo<DocumentPermissionsAPIWrapper>(() => ({
|
||||||
|
hasPermission: (flag: PdfPermissionFlag) => hasPermissionFlag(permissions, flag),
|
||||||
|
hasAllPermissions: (flags: PdfPermissionFlag[]) =>
|
||||||
|
flags.every(flag => hasPermissionFlag(permissions, flag)),
|
||||||
|
getEffectivePermission: (flag: PdfPermissionFlag) => {
|
||||||
|
if (isOwnerUnlocked) return true;
|
||||||
|
return hasPermissionFlag(permissions, flag);
|
||||||
|
},
|
||||||
|
}), [permissions, isOwnerUnlocked]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
registerBridge('permissions', {
|
||||||
|
state,
|
||||||
|
api,
|
||||||
|
});
|
||||||
|
}, [registerBridge, state, api]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PdfPermissionFlag };
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useDocumentManagerPlugin } from '@embedpdf/plugin-document-manager/react';
|
||||||
|
|
||||||
|
interface DocumentReadyWrapperProps {
|
||||||
|
children: (documentId: string) => React.ReactNode;
|
||||||
|
fallback?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DocumentReadyWrapper({ children, fallback = null }: DocumentReadyWrapperProps) {
|
||||||
|
const { plugin, isLoading, ready } = useDocumentManagerPlugin();
|
||||||
|
const [activeDocumentId, setActiveDocumentId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoading || !plugin) return;
|
||||||
|
|
||||||
|
const checkActiveDocument = async () => {
|
||||||
|
await ready;
|
||||||
|
|
||||||
|
// Try to get the active document from the plugin's provides()
|
||||||
|
const docManagerApi = plugin.provides?.();
|
||||||
|
if (docManagerApi) {
|
||||||
|
// Try different methods to get the active document
|
||||||
|
const activeDoc = docManagerApi.getActiveDocument?.();
|
||||||
|
if (activeDoc?.id) {
|
||||||
|
setActiveDocumentId(activeDoc.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkActiveDocument();
|
||||||
|
|
||||||
|
// Subscribe to document changes
|
||||||
|
const docManagerApi = plugin.provides?.();
|
||||||
|
if (docManagerApi?.onDocumentOpened) {
|
||||||
|
const unsubscribe = docManagerApi.onDocumentOpened((event: any) => {
|
||||||
|
const docId = event?.documentId || event?.id || event?.document?.id;
|
||||||
|
if (docId) {
|
||||||
|
setActiveDocumentId(docId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (typeof unsubscribe === 'function') {
|
||||||
|
unsubscribe();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [plugin, isLoading, ready]);
|
||||||
|
|
||||||
|
if (!activeDocumentId) {
|
||||||
|
return <>{fallback}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children(activeDocumentId)}</>;
|
||||||
|
}
|
||||||
@@ -345,21 +345,6 @@ const EmbedPdfViewerContent = ({
|
|||||||
// Always consider applied redactions as unsaved until export
|
// Always consider applied redactions as unsaved until export
|
||||||
const hasAppliedRedactions = redactionsApplied;
|
const hasAppliedRedactions = redactionsApplied;
|
||||||
|
|
||||||
// When in redact mode, still include annotation changes (users may draw)
|
|
||||||
if (isManualRedactMode) {
|
|
||||||
console.log('[Viewer] Checking for unsaved changes (redact mode):', {
|
|
||||||
hasPendingRedactions,
|
|
||||||
hasAppliedRedactions,
|
|
||||||
hasAnnotationChanges,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('[Viewer] Checking for unsaved changes:', {
|
|
||||||
hasAnnotationChanges,
|
|
||||||
hasPendingRedactions,
|
|
||||||
hasAppliedRedactions,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasAnnotationChanges || hasPendingRedactions || hasAppliedRedactions;
|
return hasAnnotationChanges || hasPendingRedactions || hasAppliedRedactions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
||||||
|
import { useDocumentState } from '@embedpdf/core/react';
|
||||||
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
|
import { useAnnotationCapability } from '@embedpdf/plugin-annotation/react';
|
||||||
import { useScroll } from '@embedpdf/plugin-scroll/react';
|
import { useScroll } from '@embedpdf/plugin-scroll/react';
|
||||||
|
import { PdfAnnotationSubtype } from '@embedpdf/models';
|
||||||
|
|
||||||
enum PDFActionType {
|
enum PDFActionType {
|
||||||
GoTo = 0,
|
GoTo = 0,
|
||||||
GoToR = 1,
|
GoToR = 1,
|
||||||
GoToE = 2,
|
GoToE = 2,
|
||||||
URI = 3,
|
URI = 3,
|
||||||
// Add other types as needed
|
Launch = 4,
|
||||||
|
Named = 5,
|
||||||
|
JavaScript = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PDFRect {
|
interface PDFRect {
|
||||||
@@ -46,24 +50,242 @@ function isURIAction(action: PDFAction): boolean {
|
|||||||
return action.type === 'URI' || action.type === PDFActionType.URI;
|
return action.type === 'URI' || action.type === PDFActionType.URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInternalLink(link: LinkAnnotation): boolean {
|
// Utility functions for link type detection - prefixed to indicate future use
|
||||||
|
function _isInternalLink(link: LinkAnnotation): boolean {
|
||||||
return Boolean(link.target?.type === 'destination' ||
|
return Boolean(link.target?.type === 'destination' ||
|
||||||
(link.target?.type === 'action' && link.target.action && isGoToAction(link.target.action)));
|
(link.target?.type === 'action' && link.target.action && isGoToAction(link.target.action)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isExternalLink(link: LinkAnnotation): boolean {
|
function _isExternalLink(link: LinkAnnotation): boolean {
|
||||||
return Boolean(link.target?.type === 'uri' ||
|
return Boolean(link.target?.type === 'uri' ||
|
||||||
(link.target?.type === 'action' && link.target.action && isURIAction(link.target.action)));
|
(link.target?.type === 'action' && link.target.action && isURIAction(link.target.action)));
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LinkLayerProps {
|
interface LinkLayerProps {
|
||||||
|
documentId: string;
|
||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
scale: number;
|
pageWidth: number;
|
||||||
document?: any;
|
pageHeight: number;
|
||||||
pdfFile?: File | Blob;
|
|
||||||
onLinkClick?: (target: any) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const LinkLayer: React.FC<LinkLayerProps> = ({
|
||||||
|
documentId,
|
||||||
|
pageIndex,
|
||||||
|
pageWidth,
|
||||||
|
pageHeight,
|
||||||
|
}) => {
|
||||||
|
const { provides: annotation } = useAnnotationCapability();
|
||||||
|
const { provides: scroll } = useScroll(documentId);
|
||||||
|
const documentState = useDocumentState(documentId);
|
||||||
|
const [links, setLinks] = useState<LinkAnnotation[]>([]);
|
||||||
|
const [isNavigating, setIsNavigating] = useState(false);
|
||||||
|
|
||||||
|
// Get original PDF page dimensions from document state
|
||||||
|
const pdfPage = documentState?.document?.pages?.[pageIndex];
|
||||||
|
const pdfPageWidth = pdfPage?.size?.width ?? 0;
|
||||||
|
const pdfPageHeight = pdfPage?.size?.height ?? 0;
|
||||||
|
|
||||||
|
// Process links with proper coordinate transformation
|
||||||
|
const processedLinks = useMemo(() => {
|
||||||
|
if (!pageWidth || !pageHeight || !pdfPageWidth || !pdfPageHeight) return [];
|
||||||
|
|
||||||
|
// Use the document scale like EmbedPDF's AnnotationLayer does
|
||||||
|
const scale = documentState?.scale ?? 1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return links.map(link => {
|
||||||
|
const { origin, size } = link.rect;
|
||||||
|
|
||||||
|
// Use document scale like EmbedPDF does
|
||||||
|
const scaledLeft = origin.x * scale;
|
||||||
|
const scaledTop = origin.y * scale;
|
||||||
|
const scaledWidth = size.width * scale;
|
||||||
|
const scaledHeight = size.height * scale;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...link,
|
||||||
|
scaledRect: {
|
||||||
|
left: scaledLeft,
|
||||||
|
top: scaledTop,
|
||||||
|
width: scaledWidth,
|
||||||
|
height: scaledHeight,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [links, pageWidth, pageHeight, pdfPageWidth, pdfPageHeight, documentState?.scale]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchLinks = async () => {
|
||||||
|
if (!annotation) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pageAnnotationsResult = annotation.getPageAnnotations({
|
||||||
|
pageIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resolveAnnotations = async (result: unknown): Promise<any[]> => {
|
||||||
|
if (result && typeof (result as any).toPromise === 'function') {
|
||||||
|
return (result as any).toPromise();
|
||||||
|
}
|
||||||
|
if (result && typeof (result as any).then === 'function') {
|
||||||
|
return result as Promise<any[]>;
|
||||||
|
}
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const pageAnnotations = await resolveAnnotations(pageAnnotationsResult);
|
||||||
|
|
||||||
|
const linkAnnotations = pageAnnotations.filter(
|
||||||
|
(ann: any) => ann.type === 2 || ann.type === PdfAnnotationSubtype.LINK
|
||||||
|
) as LinkAnnotation[];
|
||||||
|
|
||||||
|
if (linkAnnotations.length > 0) {
|
||||||
|
setLinks(linkAnnotations);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[LinkLayer] Failed to fetch links from annotation API:', {
|
||||||
|
error,
|
||||||
|
pageIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchLinks();
|
||||||
|
}, [annotation, pageIndex]);
|
||||||
|
|
||||||
|
const handleLinkClick = useCallback(async (link: LinkAnnotation) => {
|
||||||
|
if (isNavigating) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsNavigating(true);
|
||||||
|
|
||||||
|
// Try to extract destination from various possible locations in the link object
|
||||||
|
const linkData = link as any;
|
||||||
|
|
||||||
|
// Check for destination in various locations
|
||||||
|
let targetPage: number | undefined;
|
||||||
|
let uri: string | undefined;
|
||||||
|
|
||||||
|
// Try target.destination first
|
||||||
|
if (link.target?.destination?.pageIndex !== undefined) {
|
||||||
|
targetPage = link.target.destination.pageIndex;
|
||||||
|
}
|
||||||
|
// Try target.action.destination
|
||||||
|
else if (link.target?.action?.destination?.pageIndex !== undefined) {
|
||||||
|
targetPage = link.target.action.destination.pageIndex;
|
||||||
|
}
|
||||||
|
// Try direct dest property (PDF.js style)
|
||||||
|
else if (linkData.dest?.pageIndex !== undefined) {
|
||||||
|
targetPage = linkData.dest.pageIndex;
|
||||||
|
}
|
||||||
|
// Try destination at root level
|
||||||
|
else if (linkData.destination?.pageIndex !== undefined) {
|
||||||
|
targetPage = linkData.destination.pageIndex;
|
||||||
|
}
|
||||||
|
// Try action at root level
|
||||||
|
else if (linkData.action?.destination?.pageIndex !== undefined) {
|
||||||
|
targetPage = linkData.action.destination.pageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for URI in various locations
|
||||||
|
if (link.target?.uri) {
|
||||||
|
uri = link.target.uri;
|
||||||
|
} else if (link.target?.action?.uri) {
|
||||||
|
uri = link.target.action.uri;
|
||||||
|
} else if (linkData.uri) {
|
||||||
|
uri = linkData.uri;
|
||||||
|
} else if (linkData.url) {
|
||||||
|
uri = linkData.url;
|
||||||
|
} else if (linkData.action?.uri) {
|
||||||
|
uri = linkData.action.uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetPage !== undefined && scroll) {
|
||||||
|
scroll.scrollToPage({
|
||||||
|
pageNumber: targetPage + 1,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
} else if (uri) {
|
||||||
|
try {
|
||||||
|
const url = new URL(uri, window.location.href);
|
||||||
|
if (['http:', 'https:', 'mailto:'].includes(url.protocol)) {
|
||||||
|
window.open(uri, '_blank', 'noopener,noreferrer');
|
||||||
|
} else {
|
||||||
|
console.warn('[LinkLayer] Blocked potentially unsafe URL protocol:', url.protocol);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
window.open(uri, '_blank', 'noopener,noreferrer');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[LinkLayer] Could not extract destination or URI from link:', link);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[LinkLayer] Navigation failed:', error);
|
||||||
|
} finally {
|
||||||
|
setIsNavigating(false);
|
||||||
|
}
|
||||||
|
}, [isNavigating, scroll]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="absolute inset-0"
|
||||||
|
style={{
|
||||||
|
pointerEvents: 'none',
|
||||||
|
zIndex: 10, // Above selection layer but below UI controls
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{processedLinks.map((link) => {
|
||||||
|
const { id } = link;
|
||||||
|
const { left, top, width, height } = link.scaledRect;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
key={id}
|
||||||
|
href="#"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handleLinkClick(link);
|
||||||
|
}}
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
className={`absolute block cursor-pointer ${isNavigating ? 'cursor-not-allowed' : ''}`}
|
||||||
|
style={{
|
||||||
|
left: `${left}px`,
|
||||||
|
top: `${top}px`,
|
||||||
|
width: `${width}px`,
|
||||||
|
height: `${height}px`,
|
||||||
|
minWidth: '8px',
|
||||||
|
minHeight: '8px',
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
zIndex: 11,
|
||||||
|
border: '2px solid transparent',
|
||||||
|
borderRadius: '2px',
|
||||||
|
transition: 'background-color 0.15s ease-in-out, border-color 0.15s ease-in-out',
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'rgba(59, 130, 246, 0.15)';
|
||||||
|
e.currentTarget.style.borderColor = 'rgba(59, 130, 246, 0.5)';
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = 'transparent';
|
||||||
|
e.currentTarget.style.borderColor = 'transparent';
|
||||||
|
}}
|
||||||
|
title={getLinkTitle(link)}
|
||||||
|
aria-label={getLinkAriaLabel(link)}
|
||||||
|
>
|
||||||
|
{/* Invisible clickable area */}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getLinkTitle = (link: LinkAnnotation): string => {
|
const getLinkTitle = (link: LinkAnnotation): string => {
|
||||||
if (link.target?.type === 'destination') {
|
if (link.target?.type === 'destination') {
|
||||||
return `Go to page ${(link.target.destination?.pageIndex ?? 0) + 1}`;
|
return `Go to page ${(link.target.destination?.pageIndex ?? 0) + 1}`;
|
||||||
@@ -92,153 +314,3 @@ const getLinkAriaLabel = (link: LinkAnnotation): string => {
|
|||||||
}
|
}
|
||||||
return 'Open external link';
|
return 'Open external link';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LinkLayer: React.FC<LinkLayerProps> = ({
|
|
||||||
pageIndex,
|
|
||||||
scale,
|
|
||||||
document: pdfDocument,
|
|
||||||
onLinkClick
|
|
||||||
}) => {
|
|
||||||
const { provides: annotation } = useAnnotationCapability();
|
|
||||||
const { provides: scroll } = useScroll();
|
|
||||||
const [links, setLinks] = useState<LinkAnnotation[]>([]);
|
|
||||||
const [isNavigating, setIsNavigating] = useState(false);
|
|
||||||
|
|
||||||
const processedLinks = useMemo(() => {
|
|
||||||
return links.map(link => ({
|
|
||||||
...link,
|
|
||||||
scaledRect: {
|
|
||||||
left: link.rect.origin.x * scale,
|
|
||||||
top: link.rect.origin.y * scale,
|
|
||||||
width: link.rect.size.width * scale,
|
|
||||||
height: link.rect.size.height * scale,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}, [links, scale]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchLinks = async () => {
|
|
||||||
if (!annotation) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Use the annotation API's built-in filtering if available
|
|
||||||
const pageAnnotations = await annotation
|
|
||||||
.getPageAnnotations({
|
|
||||||
pageIndex,
|
|
||||||
// Try to filter for link annotations (type 2) if the API supports it
|
|
||||||
...(annotation.getPageAnnotations.length > 1 ? { types: [2] } : {})
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
// Filter for link annotations (type 2 is LINK in PDF spec) as fallback
|
|
||||||
const linkAnnotations = pageAnnotations.filter(
|
|
||||||
(ann: any) => ann.type === 2
|
|
||||||
) as LinkAnnotation[];
|
|
||||||
|
|
||||||
if (linkAnnotations.length > 0) {
|
|
||||||
setLinks(linkAnnotations);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LinkLayer] Failed to fetch links from annotation API:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pdfDocument) {
|
|
||||||
try {
|
|
||||||
// Try different methods to get link annotations
|
|
||||||
let pdfLinks: any[] = [];
|
|
||||||
|
|
||||||
if (pdfDocument.getPageAnnotations && typeof pdfDocument.getPageAnnotations === 'function') {
|
|
||||||
pdfLinks = await pdfDocument.getPageAnnotations(pageIndex);
|
|
||||||
} else if (pdfDocument.getAnnotations && typeof pdfDocument.getAnnotations === 'function') {
|
|
||||||
const allAnnotations = await pdfDocument.getAnnotations();
|
|
||||||
pdfLinks = allAnnotations.filter((ann: any) => ann.pageIndex === pageIndex && ann.type === 2);
|
|
||||||
} else if (pdfDocument.pages && pdfDocument.pages[pageIndex]) {
|
|
||||||
const page = pdfDocument.pages[pageIndex];
|
|
||||||
if (page.getAnnotations && typeof page.getAnnotations === 'function') {
|
|
||||||
pdfLinks = await page.getAnnotations();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const convertedLinks = pdfLinks.map((ann: any) => ({
|
|
||||||
id: ann.id || `pdf-link-${pageIndex}-${Math.random()}`,
|
|
||||||
type: ann.type || 2,
|
|
||||||
rect: ann.rect || ann,
|
|
||||||
target: ann.target || ann.action
|
|
||||||
})) as LinkAnnotation[];
|
|
||||||
|
|
||||||
setLinks(convertedLinks);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('[LinkLayer] Failed to get annotations from PDF document:', error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn('[LinkLayer] No annotation API or PDF document available');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchLinks();
|
|
||||||
}, [annotation, pageIndex, pdfDocument]);
|
|
||||||
|
|
||||||
const handleLinkClick = useCallback(async (link: LinkAnnotation) => {
|
|
||||||
if (isNavigating) return; // Prevent multiple simultaneous navigations
|
|
||||||
|
|
||||||
try {
|
|
||||||
setIsNavigating(true);
|
|
||||||
|
|
||||||
if (onLinkClick) {
|
|
||||||
onLinkClick(link.target);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInternalLink(link)) {
|
|
||||||
const targetPage = link.target?.destination?.pageIndex ??
|
|
||||||
link.target?.action?.destination?.pageIndex;
|
|
||||||
if (targetPage !== undefined && scroll) {
|
|
||||||
await scroll.scrollToPage({
|
|
||||||
pageNumber: targetPage + 1, // PDF pages are 1-indexed
|
|
||||||
behavior: 'smooth',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (isExternalLink(link)) {
|
|
||||||
const uri = link.target?.uri ?? link.target?.action?.uri;
|
|
||||||
if (uri) {
|
|
||||||
window.open(uri, '_blank', 'noopener,noreferrer');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unsupported link type: ${link.target?.type}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LinkLayer] Navigation failed:', error);
|
|
||||||
} finally {
|
|
||||||
setIsNavigating(false);
|
|
||||||
}
|
|
||||||
}, [isNavigating, onLinkClick, scroll]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="absolute inset-0 pointer-events-none">
|
|
||||||
{processedLinks.map((link) => {
|
|
||||||
const { id } = link;
|
|
||||||
const { left, top, width, height } = link.scaledRect;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={id}
|
|
||||||
onClick={() => handleLinkClick(link)}
|
|
||||||
disabled={isNavigating}
|
|
||||||
className={`absolute opacity-0 hover:opacity-20 bg-blue-500 transition-opacity cursor-pointer pointer-events-auto border border-blue-400 hover:border-blue-600 ${isNavigating ? 'cursor-not-allowed opacity-50' : ''}`}
|
|
||||||
style={{
|
|
||||||
left: `${left}px`,
|
|
||||||
top: `${top}px`,
|
|
||||||
width: `${width}px`,
|
|
||||||
height: `${height}px`,
|
|
||||||
minWidth: '8px',
|
|
||||||
minHeight: '8px',
|
|
||||||
}}
|
|
||||||
title={getLinkTitle(link)}
|
|
||||||
aria-label={getLinkAriaLabel(link)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { PrivateContent } from '@app/components/shared/PrivateContent';
|
|||||||
|
|
||||||
// Import the essential plugins
|
// Import the essential plugins
|
||||||
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
|
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
|
||||||
import { Scroller, ScrollPluginPackage, ScrollStrategy } from '@embedpdf/plugin-scroll/react';
|
import { Scroller, ScrollPluginPackage } from '@embedpdf/plugin-scroll/react';
|
||||||
import { LoaderPluginPackage } from '@embedpdf/plugin-loader/react';
|
import { DocumentManagerPluginPackage } from '@embedpdf/plugin-document-manager/react';
|
||||||
import { RenderPluginPackage } from '@embedpdf/plugin-render/react';
|
import { RenderPluginPackage } from '@embedpdf/plugin-render/react';
|
||||||
import { ZoomPluginPackage, ZoomMode } from '@embedpdf/plugin-zoom/react';
|
import { ZoomPluginPackage, ZoomMode } from '@embedpdf/plugin-zoom/react';
|
||||||
import { InteractionManagerPluginPackage, PagePointerProvider, GlobalPointerProvider } from '@embedpdf/plugin-interaction-manager/react';
|
import { InteractionManagerPluginPackage, PagePointerProvider, GlobalPointerProvider } from '@embedpdf/plugin-interaction-manager/react';
|
||||||
@@ -19,7 +19,7 @@ import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
|
|||||||
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
|
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
|
||||||
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
|
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
|
||||||
import { ExportPluginPackage } from '@embedpdf/plugin-export/react';
|
import { ExportPluginPackage } from '@embedpdf/plugin-export/react';
|
||||||
import { BookmarkPluginPackage } from '@embedpdf/plugin-bookmark';
|
import { BookmarkPluginPackage } from '@embedpdf/plugin-bookmark/react';
|
||||||
import { PrintPluginPackage } from '@embedpdf/plugin-print/react';
|
import { PrintPluginPackage } from '@embedpdf/plugin-print/react';
|
||||||
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
|
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
|
||||||
import { AnnotationLayer, AnnotationPluginPackage } from '@embedpdf/plugin-annotation/react';
|
import { AnnotationLayer, AnnotationPluginPackage } from '@embedpdf/plugin-annotation/react';
|
||||||
@@ -49,8 +49,13 @@ import { LinkLayer } from '@app/components/viewer/LinkLayer';
|
|||||||
import { RedactionSelectionMenu } from '@app/components/viewer/RedactionSelectionMenu';
|
import { RedactionSelectionMenu } from '@app/components/viewer/RedactionSelectionMenu';
|
||||||
import { RedactionPendingTracker, RedactionPendingTrackerAPI } from '@app/components/viewer/RedactionPendingTracker';
|
import { RedactionPendingTracker, RedactionPendingTrackerAPI } from '@app/components/viewer/RedactionPendingTracker';
|
||||||
import { RedactionAPIBridge } from '@app/components/viewer/RedactionAPIBridge';
|
import { RedactionAPIBridge } from '@app/components/viewer/RedactionAPIBridge';
|
||||||
|
import { DocumentPermissionsAPIBridge } from '@app/components/viewer/DocumentPermissionsAPIBridge';
|
||||||
|
import { DocumentReadyWrapper } from '@app/components/viewer/DocumentReadyWrapper';
|
||||||
|
import { ActiveDocumentProvider } from '@app/components/viewer/ActiveDocumentContext';
|
||||||
import { absoluteWithBasePath } from '@app/constants/app';
|
import { absoluteWithBasePath } from '@app/constants/app';
|
||||||
|
|
||||||
|
const DOCUMENT_NAME = 'stirling-pdf-viewer';
|
||||||
|
|
||||||
interface LocalEmbedPDFProps {
|
interface LocalEmbedPDFProps {
|
||||||
file?: File | Blob;
|
file?: File | Blob;
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -90,22 +95,16 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
const viewportGap = rootFontSize * 3.5;
|
const viewportGap = rootFontSize * 3.5;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
createPluginRegistration(LoaderPluginPackage, {
|
createPluginRegistration(DocumentManagerPluginPackage, {
|
||||||
loadingOptions: {
|
initialDocuments: [{
|
||||||
type: 'url',
|
|
||||||
pdfFile: {
|
|
||||||
id: 'stirling-pdf-viewer',
|
|
||||||
url: pdfUrl,
|
url: pdfUrl,
|
||||||
},
|
name: DOCUMENT_NAME,
|
||||||
},
|
}],
|
||||||
}),
|
}),
|
||||||
createPluginRegistration(ViewportPluginPackage, {
|
createPluginRegistration(ViewportPluginPackage, {
|
||||||
viewportGap,
|
viewportGap,
|
||||||
}),
|
}),
|
||||||
createPluginRegistration(ScrollPluginPackage, {
|
createPluginRegistration(ScrollPluginPackage),
|
||||||
strategy: ScrollStrategy.Vertical,
|
|
||||||
initialPage: 0,
|
|
||||||
}),
|
|
||||||
createPluginRegistration(RenderPluginPackage, {
|
createPluginRegistration(RenderPluginPackage, {
|
||||||
withForms: true,
|
withForms: true,
|
||||||
withAnnotations: showBakedAnnotations && !enableAnnotations, // Show baked annotations only when: visibility is ON and annotation layer is OFF
|
withAnnotations: showBakedAnnotations && !enableAnnotations, // Show baked annotations only when: visibility is ON and annotation layer is OFF
|
||||||
@@ -257,7 +256,8 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
<EmbedPDF
|
<EmbedPDF
|
||||||
engine={engine}
|
engine={engine}
|
||||||
plugins={plugins}
|
plugins={plugins}
|
||||||
onInitialized={async (registry) => {
|
onInitialized={async (registry: any) => {
|
||||||
|
// v2.0: Use registry.getPlugin() to access plugin APIs
|
||||||
const annotationPlugin = registry.getPlugin('annotation');
|
const annotationPlugin = registry.getPlugin('annotation');
|
||||||
if (!annotationPlugin || !annotationPlugin.provides) return;
|
if (!annotationPlugin || !annotationPlugin.provides) return;
|
||||||
|
|
||||||
@@ -279,6 +279,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.HIGHLIGHT ? 10 : 0),
|
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.HIGHLIGHT ? 10 : 0),
|
||||||
defaults: {
|
defaults: {
|
||||||
type: PdfAnnotationSubtype.HIGHLIGHT,
|
type: PdfAnnotationSubtype.HIGHLIGHT,
|
||||||
|
strokeColor: '#ffd54f',
|
||||||
color: '#ffd54f',
|
color: '#ffd54f',
|
||||||
opacity: 0.6,
|
opacity: 0.6,
|
||||||
},
|
},
|
||||||
@@ -295,6 +296,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.UNDERLINE ? 10 : 0),
|
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.UNDERLINE ? 10 : 0),
|
||||||
defaults: {
|
defaults: {
|
||||||
type: PdfAnnotationSubtype.UNDERLINE,
|
type: PdfAnnotationSubtype.UNDERLINE,
|
||||||
|
strokeColor: '#ffb300',
|
||||||
color: '#ffb300',
|
color: '#ffb300',
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
},
|
},
|
||||||
@@ -311,6 +313,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.STRIKEOUT ? 10 : 0),
|
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.STRIKEOUT ? 10 : 0),
|
||||||
defaults: {
|
defaults: {
|
||||||
type: PdfAnnotationSubtype.STRIKEOUT,
|
type: PdfAnnotationSubtype.STRIKEOUT,
|
||||||
|
strokeColor: '#e53935',
|
||||||
color: '#e53935',
|
color: '#e53935',
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
},
|
},
|
||||||
@@ -327,6 +330,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.SQUIGGLY ? 10 : 0),
|
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.SQUIGGLY ? 10 : 0),
|
||||||
defaults: {
|
defaults: {
|
||||||
type: PdfAnnotationSubtype.SQUIGGLY,
|
type: PdfAnnotationSubtype.SQUIGGLY,
|
||||||
|
strokeColor: '#00acc1',
|
||||||
color: '#00acc1',
|
color: '#00acc1',
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
},
|
},
|
||||||
@@ -343,6 +347,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK ? 10 : 0),
|
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK ? 10 : 0),
|
||||||
defaults: {
|
defaults: {
|
||||||
type: PdfAnnotationSubtype.INK,
|
type: PdfAnnotationSubtype.INK,
|
||||||
|
strokeColor: '#1f2933',
|
||||||
color: '#1f2933',
|
color: '#1f2933',
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
@@ -359,9 +364,10 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
id: 'inkHighlighter',
|
id: 'inkHighlighter',
|
||||||
name: 'Ink Highlighter',
|
name: 'Ink Highlighter',
|
||||||
interaction: { exclusive: true, cursor: 'crosshair' },
|
interaction: { exclusive: true, cursor: 'crosshair' },
|
||||||
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK && annotation.color === '#ffd54f' ? 8 : 0),
|
matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK && (annotation.strokeColor === '#ffd54f' || annotation.color === '#ffd54f') ? 8 : 0),
|
||||||
defaults: {
|
defaults: {
|
||||||
type: PdfAnnotationSubtype.INK,
|
type: PdfAnnotationSubtype.INK,
|
||||||
|
strokeColor: '#ffd54f',
|
||||||
color: '#ffd54f',
|
color: '#ffd54f',
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
borderWidth: 6,
|
borderWidth: 6,
|
||||||
@@ -590,6 +596,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
matchScore: () => 0,
|
matchScore: () => 0,
|
||||||
defaults: {
|
defaults: {
|
||||||
type: PdfAnnotationSubtype.INK,
|
type: PdfAnnotationSubtype.INK,
|
||||||
|
strokeColor: '#000000',
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
@@ -622,6 +629,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ActiveDocumentProvider>
|
||||||
<ZoomAPIBridge />
|
<ZoomAPIBridge />
|
||||||
<ScrollAPIBridge />
|
<ScrollAPIBridge />
|
||||||
<SelectionAPIBridge />
|
<SelectionAPIBridge />
|
||||||
@@ -641,8 +649,18 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
<ExportAPIBridge />
|
<ExportAPIBridge />
|
||||||
<BookmarkAPIBridge />
|
<BookmarkAPIBridge />
|
||||||
<PrintAPIBridge />
|
<PrintAPIBridge />
|
||||||
<GlobalPointerProvider>
|
<DocumentPermissionsAPIBridge />
|
||||||
|
<DocumentReadyWrapper
|
||||||
|
fallback={
|
||||||
|
<Center style={{ height: '100%', width: '100%' }}>
|
||||||
|
<ToolLoadingFallback />
|
||||||
|
</Center>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(documentId) => (
|
||||||
|
<GlobalPointerProvider documentId={documentId}>
|
||||||
<Viewport
|
<Viewport
|
||||||
|
documentId={documentId}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'var(--bg-background)',
|
backgroundColor: 'var(--bg-background)',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -658,10 +676,11 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Scroller
|
<Scroller
|
||||||
renderPage={({ document, width, height, pageIndex, scale, rotation }) => {
|
documentId={documentId}
|
||||||
|
renderPage={({ width, height, pageIndex }) => {
|
||||||
return (
|
return (
|
||||||
<Rotate key={document?.id} pageSize={{ width, height }}>
|
<Rotate key={`${documentId}-${pageIndex}`} documentId={documentId} pageIndex={pageIndex}>
|
||||||
<PagePointerProvider pageIndex={pageIndex} pageWidth={width} pageHeight={height} scale={scale} rotation={rotation}>
|
<PagePointerProvider documentId={documentId} pageIndex={pageIndex}>
|
||||||
<div
|
<div
|
||||||
data-page-index={pageIndex}
|
data-page-index={pageIndex}
|
||||||
data-page-width={width}
|
data-page-width={width}
|
||||||
@@ -681,39 +700,36 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
onDrop={(e) => e.preventDefault()}
|
onDrop={(e) => e.preventDefault()}
|
||||||
onDragOver={(e) => e.preventDefault()}
|
onDragOver={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
{/* High-resolution tile layer */}
|
<TilingLayer documentId={documentId} pageIndex={pageIndex} />
|
||||||
<TilingLayer pageIndex={pageIndex} scale={scale} />
|
|
||||||
|
|
||||||
{/* Search highlight layer */}
|
<CustomSearchLayer documentId={documentId} pageIndex={pageIndex} />
|
||||||
<CustomSearchLayer pageIndex={pageIndex} scale={scale} />
|
|
||||||
|
|
||||||
{/* Selection layer for text interaction */}
|
<SelectionLayer documentId={documentId} pageIndex={pageIndex} />
|
||||||
<SelectionLayer pageIndex={pageIndex} scale={scale} />
|
|
||||||
|
|
||||||
{/* Link layer for clickable PDF links */}
|
{/* AnnotationLayer for annotation editing (only when enabled) */}
|
||||||
<LinkLayer pageIndex={pageIndex} scale={scale} document={document} pdfFile={file} />
|
|
||||||
|
|
||||||
{/* Annotation layer for signatures (only when enabled) */}
|
|
||||||
{enableAnnotations && (
|
{enableAnnotations && (
|
||||||
<AnnotationLayer
|
<AnnotationLayer
|
||||||
|
documentId={documentId}
|
||||||
pageIndex={pageIndex}
|
pageIndex={pageIndex}
|
||||||
scale={scale}
|
|
||||||
pageWidth={width}
|
|
||||||
pageHeight={height}
|
|
||||||
rotation={rotation}
|
|
||||||
selectionOutlineColor="#007ACC"
|
selectionOutlineColor="#007ACC"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Redaction layer for marking areas to redact (only when enabled) */}
|
|
||||||
{enableRedaction && (
|
{enableRedaction && (
|
||||||
<RedactionLayer
|
<RedactionLayer
|
||||||
|
documentId={documentId}
|
||||||
pageIndex={pageIndex}
|
pageIndex={pageIndex}
|
||||||
scale={scale}
|
|
||||||
rotation={rotation}
|
|
||||||
selectionMenu={(props) => <RedactionSelectionMenu {...props} />}
|
selectionMenu={(props) => <RedactionSelectionMenu {...props} />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* LinkLayer on top to handle link navigation - must be last for click priority */}
|
||||||
|
<LinkLayer
|
||||||
|
documentId={documentId}
|
||||||
|
pageIndex={pageIndex}
|
||||||
|
pageWidth={width}
|
||||||
|
pageHeight={height}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</PagePointerProvider>
|
</PagePointerProvider>
|
||||||
</Rotate>
|
</Rotate>
|
||||||
@@ -722,6 +738,9 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, enableReda
|
|||||||
/>
|
/>
|
||||||
</Viewport>
|
</Viewport>
|
||||||
</GlobalPointerProvider>
|
</GlobalPointerProvider>
|
||||||
|
)}
|
||||||
|
</DocumentReadyWrapper>
|
||||||
|
</ActiveDocumentProvider>
|
||||||
</EmbedPDF>
|
</EmbedPDF>
|
||||||
</div>
|
</div>
|
||||||
</PrivateContent>
|
</PrivateContent>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { usePdfiumEngine } from '@embedpdf/engines/react';
|
|||||||
|
|
||||||
// Import the essential plugins
|
// Import the essential plugins
|
||||||
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
|
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
|
||||||
import { Scroller, ScrollPluginPackage, ScrollStrategy } from '@embedpdf/plugin-scroll/react';
|
import { Scroller, ScrollPluginPackage } from '@embedpdf/plugin-scroll/react';
|
||||||
import { LoaderPluginPackage } from '@embedpdf/plugin-loader/react';
|
import { DocumentManagerPluginPackage } from '@embedpdf/plugin-document-manager/react';
|
||||||
import { RenderPluginPackage } from '@embedpdf/plugin-render/react';
|
import { RenderPluginPackage } from '@embedpdf/plugin-render/react';
|
||||||
import { ZoomPluginPackage } from '@embedpdf/plugin-zoom/react';
|
import { ZoomPluginPackage } from '@embedpdf/plugin-zoom/react';
|
||||||
import { InteractionManagerPluginPackage, PagePointerProvider, GlobalPointerProvider } from '@embedpdf/plugin-interaction-manager/react';
|
import { InteractionManagerPluginPackage, PagePointerProvider, GlobalPointerProvider } from '@embedpdf/plugin-interaction-manager/react';
|
||||||
@@ -17,12 +17,11 @@ import { SpreadPluginPackage, SpreadMode } from '@embedpdf/plugin-spread/react';
|
|||||||
import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
|
import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
|
||||||
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
|
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
|
||||||
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
|
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
|
||||||
import { Rotation } from '@embedpdf/models';
|
import { Rotation, PdfAnnotationSubtype } from '@embedpdf/models';
|
||||||
|
|
||||||
// Import annotation plugins
|
// Import annotation plugins
|
||||||
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
|
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
|
||||||
import { AnnotationLayer, AnnotationPluginPackage } from '@embedpdf/plugin-annotation/react';
|
import { AnnotationLayer, AnnotationPluginPackage } from '@embedpdf/plugin-annotation/react';
|
||||||
import { PdfAnnotationSubtype } from '@embedpdf/models';
|
|
||||||
|
|
||||||
import { CustomSearchLayer } from '@app/components/viewer/CustomSearchLayer';
|
import { CustomSearchLayer } from '@app/components/viewer/CustomSearchLayer';
|
||||||
import { ZoomAPIBridge } from '@app/components/viewer/ZoomAPIBridge';
|
import { ZoomAPIBridge } from '@app/components/viewer/ZoomAPIBridge';
|
||||||
@@ -35,6 +34,9 @@ import { SpreadAPIBridge } from '@app/components/viewer/SpreadAPIBridge';
|
|||||||
import { SearchAPIBridge } from '@app/components/viewer/SearchAPIBridge';
|
import { SearchAPIBridge } from '@app/components/viewer/SearchAPIBridge';
|
||||||
import { ThumbnailAPIBridge } from '@app/components/viewer/ThumbnailAPIBridge';
|
import { ThumbnailAPIBridge } from '@app/components/viewer/ThumbnailAPIBridge';
|
||||||
import { RotateAPIBridge } from '@app/components/viewer/RotateAPIBridge';
|
import { RotateAPIBridge } from '@app/components/viewer/RotateAPIBridge';
|
||||||
|
import { DocumentReadyWrapper } from '@app/components/viewer/DocumentReadyWrapper';
|
||||||
|
|
||||||
|
const DOCUMENT_NAME = 'stirling-pdf-signing-viewer';
|
||||||
|
|
||||||
interface LocalEmbedPDFWithAnnotationsProps {
|
interface LocalEmbedPDFWithAnnotationsProps {
|
||||||
file?: File | Blob;
|
file?: File | Blob;
|
||||||
@@ -69,22 +71,16 @@ export function LocalEmbedPDFWithAnnotations({
|
|||||||
const viewportGap = rootFontSize * 3.5;
|
const viewportGap = rootFontSize * 3.5;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
createPluginRegistration(LoaderPluginPackage, {
|
createPluginRegistration(DocumentManagerPluginPackage, {
|
||||||
loadingOptions: {
|
initialDocuments: [{
|
||||||
type: 'url',
|
|
||||||
pdfFile: {
|
|
||||||
id: 'stirling-pdf-signing-viewer',
|
|
||||||
url: pdfUrl,
|
url: pdfUrl,
|
||||||
},
|
name: DOCUMENT_NAME,
|
||||||
},
|
}],
|
||||||
}),
|
}),
|
||||||
createPluginRegistration(ViewportPluginPackage, {
|
createPluginRegistration(ViewportPluginPackage, {
|
||||||
viewportGap,
|
viewportGap,
|
||||||
}),
|
}),
|
||||||
createPluginRegistration(ScrollPluginPackage, {
|
createPluginRegistration(ScrollPluginPackage),
|
||||||
strategy: ScrollStrategy.Vertical,
|
|
||||||
initialPage: 0,
|
|
||||||
}),
|
|
||||||
createPluginRegistration(RenderPluginPackage, {
|
createPluginRegistration(RenderPluginPackage, {
|
||||||
withForms: true,
|
withForms: true,
|
||||||
withAnnotations: true,
|
withAnnotations: true,
|
||||||
@@ -191,7 +187,8 @@ export function LocalEmbedPDFWithAnnotations({
|
|||||||
<EmbedPDF
|
<EmbedPDF
|
||||||
engine={engine}
|
engine={engine}
|
||||||
plugins={plugins}
|
plugins={plugins}
|
||||||
onInitialized={async (registry) => {
|
onInitialized={async (registry: any) => {
|
||||||
|
// v2.0: Use registry.getPlugin() to access plugin APIs
|
||||||
const annotationPlugin = registry.getPlugin('annotation');
|
const annotationPlugin = registry.getPlugin('annotation');
|
||||||
if (!annotationPlugin || !annotationPlugin.provides) return;
|
if (!annotationPlugin || !annotationPlugin.provides) return;
|
||||||
|
|
||||||
@@ -244,8 +241,17 @@ export function LocalEmbedPDFWithAnnotations({
|
|||||||
<SearchAPIBridge />
|
<SearchAPIBridge />
|
||||||
<ThumbnailAPIBridge />
|
<ThumbnailAPIBridge />
|
||||||
<RotateAPIBridge />
|
<RotateAPIBridge />
|
||||||
<GlobalPointerProvider>
|
<DocumentReadyWrapper
|
||||||
|
fallback={
|
||||||
|
<Center style={{ height: '100%', width: '100%' }}>
|
||||||
|
<ToolLoadingFallback />
|
||||||
|
</Center>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(documentId) => (
|
||||||
|
<GlobalPointerProvider documentId={documentId}>
|
||||||
<Viewport
|
<Viewport
|
||||||
|
documentId={documentId}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'var(--bg-surface)',
|
backgroundColor: 'var(--bg-surface)',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -261,21 +267,10 @@ export function LocalEmbedPDFWithAnnotations({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Scroller
|
<Scroller
|
||||||
renderPage={({ width, height, pageIndex, scale, rotation }: {
|
documentId={documentId}
|
||||||
width: number;
|
renderPage={({ width, height, pageIndex }) => (
|
||||||
height: number;
|
<Rotate key={`${documentId}-${pageIndex}`} documentId={documentId} pageIndex={pageIndex}>
|
||||||
pageIndex: number;
|
<PagePointerProvider documentId={documentId} pageIndex={pageIndex}>
|
||||||
scale: number;
|
|
||||||
rotation?: number;
|
|
||||||
}) => (
|
|
||||||
<Rotate pageSize={{ width, height }}>
|
|
||||||
<PagePointerProvider {...{
|
|
||||||
pageWidth: width,
|
|
||||||
pageHeight: height,
|
|
||||||
pageIndex,
|
|
||||||
scale,
|
|
||||||
rotation: rotation || 0
|
|
||||||
}}>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
width,
|
width,
|
||||||
@@ -291,22 +286,15 @@ export function LocalEmbedPDFWithAnnotations({
|
|||||||
onDrop={(e) => e.preventDefault()}
|
onDrop={(e) => e.preventDefault()}
|
||||||
onDragOver={(e) => e.preventDefault()}
|
onDragOver={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
{/* High-resolution tile layer */}
|
<TilingLayer documentId={documentId} pageIndex={pageIndex} />
|
||||||
<TilingLayer pageIndex={pageIndex} scale={scale} />
|
|
||||||
|
|
||||||
{/* Search highlight layer */}
|
<CustomSearchLayer documentId={documentId} pageIndex={pageIndex} />
|
||||||
<CustomSearchLayer pageIndex={pageIndex} scale={scale} />
|
|
||||||
|
|
||||||
{/* Selection layer for text interaction */}
|
<SelectionLayer documentId={documentId} pageIndex={pageIndex} />
|
||||||
<SelectionLayer pageIndex={pageIndex} scale={scale} />
|
|
||||||
|
|
||||||
{/* Annotation layer for signatures */}
|
|
||||||
<AnnotationLayer
|
<AnnotationLayer
|
||||||
|
documentId={documentId}
|
||||||
pageIndex={pageIndex}
|
pageIndex={pageIndex}
|
||||||
scale={scale}
|
|
||||||
pageWidth={width}
|
|
||||||
pageHeight={height}
|
|
||||||
rotation={rotation || 0}
|
|
||||||
selectionOutlineColor="#007ACC"
|
selectionOutlineColor="#007ACC"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -316,6 +304,8 @@ export function LocalEmbedPDFWithAnnotations({
|
|||||||
/>
|
/>
|
||||||
</Viewport>
|
</Viewport>
|
||||||
</GlobalPointerProvider>
|
</GlobalPointerProvider>
|
||||||
|
)}
|
||||||
|
</DocumentReadyWrapper>
|
||||||
</EmbedPDF>
|
</EmbedPDF>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,44 +1,60 @@
|
|||||||
import { useEffect, useState, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { usePan } from '@embedpdf/plugin-pan/react';
|
import { usePan } from '@embedpdf/plugin-pan/react';
|
||||||
import { useViewer } from '@app/contexts/ViewerContext';
|
import { useViewer } from '@app/contexts/ViewerContext';
|
||||||
|
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||||
|
|
||||||
/**
|
|
||||||
* Component that runs inside EmbedPDF context and updates pan state in ViewerContext
|
|
||||||
*/
|
|
||||||
export function PanAPIBridge() {
|
export function PanAPIBridge() {
|
||||||
const { provides: pan, isPanning } = usePan();
|
const activeDocumentId = useActiveDocumentId();
|
||||||
|
|
||||||
|
// Don't render the inner component until we have a valid document ID
|
||||||
|
if (!activeDocumentId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <PanAPIBridgeInner documentId={activeDocumentId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PanAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||||
|
const { provides: pan, isPanning } = usePan(documentId);
|
||||||
const { registerBridge, triggerImmediatePanUpdate } = useViewer();
|
const { registerBridge, triggerImmediatePanUpdate } = useViewer();
|
||||||
|
|
||||||
// Store state locally
|
// Keep pan ref updated to avoid re-running effect when object reference changes
|
||||||
const [_localState, setLocalState] = useState({
|
const panRef = useRef(pan);
|
||||||
isPanning: false
|
useEffect(() => {
|
||||||
});
|
panRef.current = pan;
|
||||||
|
}, [pan]);
|
||||||
|
|
||||||
// Track previous isPanning value to detect changes
|
// Track previous isPanning value to detect changes
|
||||||
const prevIsPanningRef = useRef<boolean>(isPanning);
|
const prevIsPanningRef = useRef<boolean>(isPanning);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pan) {
|
const currentPan = panRef.current;
|
||||||
// Update local state
|
if (currentPan) {
|
||||||
const newState = {
|
const newState = {
|
||||||
isPanning
|
isPanning
|
||||||
};
|
};
|
||||||
setLocalState(newState);
|
|
||||||
|
|
||||||
// Register this bridge with ViewerContext
|
// Register this bridge with ViewerContext
|
||||||
registerBridge('pan', {
|
registerBridge('pan', {
|
||||||
state: newState,
|
state: newState,
|
||||||
api: {
|
api: {
|
||||||
enable: () => {
|
enable: () => {
|
||||||
pan.enablePan();
|
currentPan.enablePan();
|
||||||
},
|
},
|
||||||
disable: () => {
|
disable: () => {
|
||||||
pan.disablePan();
|
currentPan.disablePan();
|
||||||
},
|
},
|
||||||
toggle: () => {
|
toggle: () => {
|
||||||
pan.togglePan();
|
currentPan.togglePan();
|
||||||
|
},
|
||||||
|
makePanDefault: () => {
|
||||||
|
// v2.3.0: makePanDefault may not exist, enable pan as fallback
|
||||||
|
if ('makePanDefault' in currentPan && typeof (currentPan as any).makePanDefault === 'function') {
|
||||||
|
(currentPan as any).makePanDefault();
|
||||||
|
} else {
|
||||||
|
currentPan.enablePan();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
makePanDefault: () => pan.makePanDefault(),
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,7 +64,7 @@ export function PanAPIBridge() {
|
|||||||
triggerImmediatePanUpdate(isPanning);
|
triggerImmediatePanUpdate(isPanning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [pan, isPanning, triggerImmediatePanUpdate]);
|
}, [isPanning, registerBridge, triggerImmediatePanUpdate]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
import { useEffect, useImperativeHandle } from 'react';
|
import { useEffect, useImperativeHandle } from 'react';
|
||||||
import { useRedaction as useEmbedPdfRedaction } from '@embedpdf/plugin-redaction/react';
|
import { useRedaction as useEmbedPdfRedaction } from '@embedpdf/plugin-redaction/react';
|
||||||
import { useRedaction } from '@app/contexts/RedactionContext';
|
import { useRedaction } from '@app/contexts/RedactionContext';
|
||||||
|
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||||
|
|
||||||
/**
|
|
||||||
* RedactionAPIBridge connects the EmbedPDF redaction plugin to our RedactionContext.
|
|
||||||
* It must be rendered inside the EmbedPDF context to access the plugin API.
|
|
||||||
*
|
|
||||||
* It does two things:
|
|
||||||
* 1. Syncs EmbedPDF state (pendingCount, activeType, isRedacting) to our context
|
|
||||||
* 2. Exposes the EmbedPDF API through our context's ref so outside components can call it
|
|
||||||
*/
|
|
||||||
export function RedactionAPIBridge() {
|
export function RedactionAPIBridge() {
|
||||||
const { state, provides } = useEmbedPdfRedaction();
|
const activeDocumentId = useActiveDocumentId();
|
||||||
|
|
||||||
|
// Don't render the inner component until we have a valid document ID
|
||||||
|
if (!activeDocumentId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <RedactionAPIBridgeInner documentId={activeDocumentId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RedactionAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||||
|
const { state, provides } = useEmbedPdfRedaction(documentId);
|
||||||
const {
|
const {
|
||||||
redactionApiRef,
|
redactionApiRef,
|
||||||
setPendingCount,
|
setPendingCount,
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
import { useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
|
import { useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
|
||||||
import { useRedaction as useEmbedPdfRedaction } from '@embedpdf/plugin-redaction/react';
|
import { useRedaction as useEmbedPdfRedaction } from '@embedpdf/plugin-redaction/react';
|
||||||
|
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||||
|
|
||||||
export interface RedactionPendingTrackerAPI {
|
export interface RedactionPendingTrackerAPI {
|
||||||
commitAllPending: () => void;
|
commitAllPending: () => void;
|
||||||
getPendingCount: () => number;
|
getPendingCount: () => number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* RedactionPendingTracker monitors pending redactions and exposes an API
|
|
||||||
* for committing and checking pending redactions.
|
|
||||||
* Must be rendered inside the EmbedPDF context.
|
|
||||||
*
|
|
||||||
* Note: The unsaved changes checker is registered by EmbedPdfViewer, not here,
|
|
||||||
* to avoid conflicts and allow the viewer to check both annotations and redactions.
|
|
||||||
*/
|
|
||||||
export const RedactionPendingTracker = forwardRef<RedactionPendingTrackerAPI>(
|
export const RedactionPendingTracker = forwardRef<RedactionPendingTrackerAPI>(
|
||||||
function RedactionPendingTracker(_, ref) {
|
function RedactionPendingTracker(_, ref) {
|
||||||
const { state, provides } = useEmbedPdfRedaction();
|
const activeDocumentId = useActiveDocumentId();
|
||||||
|
|
||||||
|
// Don't render the inner component until we have a valid document ID
|
||||||
|
if (!activeDocumentId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <RedactionPendingTrackerInner documentId={activeDocumentId} ref={ref} />;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const RedactionPendingTrackerInner = forwardRef<RedactionPendingTrackerAPI, { documentId: string }>(
|
||||||
|
function RedactionPendingTrackerInner({ documentId }, ref) {
|
||||||
|
const { state, provides } = useEmbedPdfRedaction(documentId);
|
||||||
|
|
||||||
const pendingCountRef = useRef(0);
|
const pendingCountRef = useRef(0);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useRedaction as useEmbedPdfRedaction, SelectionMenuProps } from '@embedpdf/plugin-redaction/react';
|
import { useRedaction as useEmbedPdfRedaction, RedactionSelectionMenuProps } from '@embedpdf/plugin-redaction/react';
|
||||||
import { ActionIcon, Tooltip, Button, Group } from '@mantine/core';
|
import { ActionIcon, Tooltip, Button, Group } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
@@ -6,46 +6,63 @@ import { useEffect, useState, useRef, useCallback } from 'react';
|
|||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||||
import { useRedaction } from '@app/contexts/RedactionContext';
|
import { useRedaction } from '@app/contexts/RedactionContext';
|
||||||
|
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||||
|
|
||||||
/**
|
// Use the official EmbedPDF v2.3.0 types
|
||||||
* Custom menu component that appears when a pending redaction mark is selected.
|
export type { RedactionSelectionMenuProps };
|
||||||
* Allows users to remove or apply individual pending marks.
|
|
||||||
* Uses a portal to ensure it appears above all content, including next pages.
|
export function RedactionSelectionMenu(props: RedactionSelectionMenuProps) {
|
||||||
*/
|
const activeDocumentId = useActiveDocumentId();
|
||||||
export function RedactionSelectionMenu({ item, selected, menuWrapperProps }: SelectionMenuProps) {
|
|
||||||
|
// Don't render until we have a valid document ID
|
||||||
|
if (!activeDocumentId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RedactionSelectionMenuInner
|
||||||
|
documentId={activeDocumentId}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RedactionSelectionMenuInner({
|
||||||
|
documentId,
|
||||||
|
context,
|
||||||
|
selected,
|
||||||
|
menuWrapperProps,
|
||||||
|
}: RedactionSelectionMenuProps & { documentId: string }) {
|
||||||
|
// Extract item and pageIndex from context (EmbedPDF v2.3.0 API)
|
||||||
|
const item = context?.item;
|
||||||
|
const pageIndex = context?.pageIndex;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { provides } = useEmbedPdfRedaction();
|
const { provides } = useEmbedPdfRedaction(documentId);
|
||||||
const { setRedactionsApplied } = useRedaction();
|
const { setRedactionsApplied } = useRedaction();
|
||||||
const [menuPosition, setMenuPosition] = useState<{ top: number; left: number } | null>(null);
|
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [menuPosition, setMenuPosition] = useState<{ top: number; left: number } | null>(null);
|
||||||
|
|
||||||
// Merge refs if menuWrapperProps has a ref
|
// Merge refs - menuWrapperProps.ref is a callback ref
|
||||||
const setRef = useCallback((node: HTMLDivElement | null) => {
|
const setRef = useCallback((node: HTMLDivElement | null) => {
|
||||||
wrapperRef.current = node;
|
wrapperRef.current = node;
|
||||||
if (menuWrapperProps?.ref) {
|
// Call the EmbedPDF ref callback
|
||||||
const ref = menuWrapperProps.ref;
|
menuWrapperProps?.ref?.(node);
|
||||||
if (typeof ref === 'function') {
|
|
||||||
ref(node);
|
|
||||||
} else if (ref && 'current' in ref) {
|
|
||||||
(ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [menuWrapperProps]);
|
}, [menuWrapperProps]);
|
||||||
|
|
||||||
const handleRemove = useCallback(() => {
|
const handleRemove = useCallback(() => {
|
||||||
if (provides?.removePending && item) {
|
if (provides?.removePending && item && pageIndex !== undefined) {
|
||||||
provides.removePending(item.page, item.id);
|
provides.removePending(pageIndex, item.id);
|
||||||
}
|
}
|
||||||
}, [provides, item]);
|
}, [provides, item, pageIndex]);
|
||||||
|
|
||||||
const handleApply = useCallback(() => {
|
const handleApply = useCallback(() => {
|
||||||
if (provides?.commitPending && item) {
|
if (provides?.commitPending && item && pageIndex !== undefined) {
|
||||||
provides.commitPending(item.page, item.id);
|
provides.commitPending(pageIndex, item.id);
|
||||||
// Mark redactions as applied (but not yet saved) so the Save Changes button stays enabled
|
// Mark redactions as applied (but not yet saved) so the Save Changes button stays enabled
|
||||||
// This ensures the button doesn't become disabled when pendingCount decreases
|
// This ensures the button doesn't become disabled when pendingCount decreases
|
||||||
setRedactionsApplied(true);
|
setRedactionsApplied(true);
|
||||||
}
|
}
|
||||||
}, [provides, item, setRedactionsApplied]);
|
}, [provides, item, pageIndex, setRedactionsApplied]);
|
||||||
|
|
||||||
// Calculate position for portal based on wrapper element
|
// Calculate position for portal based on wrapper element
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -61,13 +78,13 @@ export function RedactionSelectionMenu({ item, selected, menuWrapperProps }: Sel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rect = wrapper.getBoundingClientRect();
|
const wrapperRect = wrapper.getBoundingClientRect();
|
||||||
// Position menu below the wrapper, centered
|
// Position menu below the wrapper, centered
|
||||||
// Use getBoundingClientRect which gives viewport-relative coordinates
|
// Use getBoundingClientRect which gives viewport-relative coordinates
|
||||||
// Since we're using fixed positioning in the portal, we don't need to add scroll offsets
|
// Since we're using fixed positioning in the portal, we don't need to add scroll offsets
|
||||||
setMenuPosition({
|
setMenuPosition({
|
||||||
top: rect.bottom + 8,
|
top: wrapperRect.bottom + 8,
|
||||||
left: rect.left + rect.width / 2,
|
left: wrapperRect.left + wrapperRect.width / 2,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,19 +169,16 @@ export function RedactionSelectionMenu({ item, selected, menuWrapperProps }: Sel
|
|||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
// Extract ref from menuWrapperProps to avoid conflicts
|
|
||||||
const { ref: _, ...wrapperPropsWithoutRef } = menuWrapperProps || {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/* Invisible wrapper that provides positioning - uses EmbedPDF's menuWrapperProps */}
|
||||||
<div
|
<div
|
||||||
ref={setRef}
|
ref={setRef}
|
||||||
{...wrapperPropsWithoutRef}
|
|
||||||
style={{
|
style={{
|
||||||
// Preserve the original positioning from menuWrapperProps
|
// Use EmbedPDF's positioning styles
|
||||||
...(wrapperPropsWithoutRef?.style || {}),
|
...menuWrapperProps?.style,
|
||||||
// Override visibility to hide the wrapper (we only need its position)
|
// Keep the wrapper invisible but still occupying space for positioning
|
||||||
visibility: 'hidden',
|
opacity: 0,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -174,4 +188,3 @@ export function RedactionSelectionMenu({ item, selected, menuWrapperProps }: Sel
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,48 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useRotate } from '@embedpdf/plugin-rotate/react';
|
import { useRotate } from '@embedpdf/plugin-rotate/react';
|
||||||
import { useViewer } from '@app/contexts/ViewerContext';
|
import { useViewer } from '@app/contexts/ViewerContext';
|
||||||
|
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||||
|
|
||||||
/**
|
|
||||||
* Component that runs inside EmbedPDF context and updates rotation state in ViewerContext
|
|
||||||
*/
|
|
||||||
export function RotateAPIBridge() {
|
export function RotateAPIBridge() {
|
||||||
const { provides: rotate, rotation } = useRotate();
|
const activeDocumentId = useActiveDocumentId();
|
||||||
|
|
||||||
|
// Don't render the inner component until we have a valid document ID
|
||||||
|
if (!activeDocumentId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <RotateAPIBridgeInner documentId={activeDocumentId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RotateAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||||
|
const { provides: rotate, rotation } = useRotate(documentId);
|
||||||
const { registerBridge } = useViewer();
|
const { registerBridge } = useViewer();
|
||||||
|
|
||||||
// Store state locally
|
// Keep rotate ref updated to avoid re-running effect when object reference changes
|
||||||
const [_localState, setLocalState] = useState({
|
const rotateRef = useRef(rotate);
|
||||||
rotation: 0
|
useEffect(() => {
|
||||||
});
|
rotateRef.current = rotate;
|
||||||
|
}, [rotate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (rotate) {
|
const currentRotate = rotateRef.current;
|
||||||
// Update local state
|
if (currentRotate) {
|
||||||
const newState = {
|
const newState = {
|
||||||
rotation
|
rotation
|
||||||
};
|
};
|
||||||
setLocalState(newState);
|
|
||||||
|
|
||||||
// Register this bridge with ViewerContext
|
// Register this bridge with ViewerContext
|
||||||
registerBridge('rotation', {
|
registerBridge('rotation', {
|
||||||
state: newState,
|
state: newState,
|
||||||
api: {
|
api: {
|
||||||
rotateForward: () => rotate.rotateForward(),
|
rotateForward: () => currentRotate.rotateForward(),
|
||||||
rotateBackward: () => rotate.rotateBackward(),
|
rotateBackward: () => currentRotate.rotateBackward(),
|
||||||
setRotation: (rotationValue: number) => rotate.setRotation(rotationValue),
|
setRotation: (rotationValue: number) => currentRotate.setRotation(rotationValue),
|
||||||
getRotation: () => rotation,
|
getRotation: () => rotation,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [rotate, rotation]);
|
}, [rotation, registerBridge]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,39 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useScroll } from '@embedpdf/plugin-scroll/react';
|
import { useScroll } from '@embedpdf/plugin-scroll/react';
|
||||||
import { useViewer } from '@app/contexts/ViewerContext';
|
import { useViewer } from '@app/contexts/ViewerContext';
|
||||||
|
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||||
|
|
||||||
/**
|
|
||||||
* ScrollAPIBridge manages scroll state and exposes scroll actions.
|
|
||||||
* Registers with ViewerContext to provide scroll functionality to UI components.
|
|
||||||
*/
|
|
||||||
export function ScrollAPIBridge() {
|
export function ScrollAPIBridge() {
|
||||||
const { provides: scroll, state: scrollState } = useScroll();
|
const activeDocumentId = useActiveDocumentId();
|
||||||
|
|
||||||
|
// Don't render the inner component until we have a valid document ID
|
||||||
|
if (!activeDocumentId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ScrollAPIBridgeInner documentId={activeDocumentId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ScrollAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||||
|
const { provides: scroll, state: scrollState } = useScroll(documentId);
|
||||||
const { registerBridge, triggerImmediateScrollUpdate } = useViewer();
|
const { registerBridge, triggerImmediateScrollUpdate } = useViewer();
|
||||||
|
|
||||||
|
// Keep scroll ref updated to avoid re-running effect when object reference changes
|
||||||
|
const scrollRef = useRef(scroll);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scroll && scrollState) {
|
scrollRef.current = scroll;
|
||||||
|
}, [scroll]);
|
||||||
|
|
||||||
|
// Extract primitive values to avoid dependency on object references
|
||||||
|
const currentPage = scrollState?.currentPage;
|
||||||
|
const totalPages = scrollState?.totalPages;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const currentScroll = scrollRef.current;
|
||||||
|
if (currentScroll && currentPage !== undefined && totalPages !== undefined) {
|
||||||
const newState = {
|
const newState = {
|
||||||
currentPage: scrollState.currentPage,
|
currentPage,
|
||||||
totalPages: scrollState.totalPages,
|
totalPages,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Trigger immediate update for responsive UI
|
// Trigger immediate update for responsive UI
|
||||||
@@ -22,10 +41,10 @@ export function ScrollAPIBridge() {
|
|||||||
|
|
||||||
registerBridge('scroll', {
|
registerBridge('scroll', {
|
||||||
state: newState,
|
state: newState,
|
||||||
api: scroll
|
api: currentScroll
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [scroll, scrollState]);
|
}, [currentPage, totalPages, registerBridge, triggerImmediateScrollUpdate]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import { useSearch } from '@embedpdf/plugin-search/react';
|
import { useSearch } from '@embedpdf/plugin-search/react';
|
||||||
import { useViewer } from '@app/contexts/ViewerContext';
|
import { useViewer } from '@app/contexts/ViewerContext';
|
||||||
|
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||||
|
|
||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
@@ -10,13 +11,29 @@ interface SearchResult {
|
|||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* SearchAPIBridge manages search state and provides search functionality.
|
|
||||||
* Listens for search result changes from EmbedPDF and maintains local state.
|
|
||||||
*/
|
|
||||||
export function SearchAPIBridge() {
|
export function SearchAPIBridge() {
|
||||||
const { provides: search } = useSearch();
|
const activeDocumentId = useActiveDocumentId();
|
||||||
const { registerBridge } = useViewer();
|
|
||||||
|
// Don't render the inner component until we have a valid document ID
|
||||||
|
if (!activeDocumentId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <SearchAPIBridgeInner documentId={activeDocumentId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SearchAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||||
|
const { provides: search } = useSearch(documentId);
|
||||||
|
const { registerBridge, scrollActions } = useViewer();
|
||||||
|
|
||||||
|
// Keep search ref updated to avoid re-running effects when object reference changes
|
||||||
|
const searchRef = useRef(search);
|
||||||
|
const isSearchingRef = useRef(false);
|
||||||
|
const lastScrolledIndexRef = useRef<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
searchRef.current = search;
|
||||||
|
}, [search]);
|
||||||
|
|
||||||
const [localState, setLocalState] = useState({
|
const [localState, setLocalState] = useState({
|
||||||
results: null as SearchResult[] | null,
|
results: null as SearchResult[] | null,
|
||||||
@@ -24,10 +41,18 @@ export function SearchAPIBridge() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Subscribe to search result changes from EmbedPDF
|
// Subscribe to search result changes from EmbedPDF
|
||||||
|
const subscriptionRef = useRef<(() => void) | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Cleanup previous subscription
|
||||||
|
if (subscriptionRef.current) {
|
||||||
|
subscriptionRef.current();
|
||||||
|
subscriptionRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!search) return;
|
if (!search) return;
|
||||||
|
|
||||||
const unsubscribe = search.onSearchResultStateChange?.((state: any) => {
|
subscriptionRef.current = search.onSearchResultStateChange?.((state: any) => {
|
||||||
if (!state) return;
|
if (!state) return;
|
||||||
|
|
||||||
const newState = {
|
const newState = {
|
||||||
@@ -42,27 +67,80 @@ export function SearchAPIBridge() {
|
|||||||
}
|
}
|
||||||
return prevState;
|
return prevState;
|
||||||
});
|
});
|
||||||
});
|
}) ?? null;
|
||||||
|
|
||||||
return unsubscribe;
|
return () => {
|
||||||
|
if (subscriptionRef.current) {
|
||||||
|
subscriptionRef.current();
|
||||||
|
subscriptionRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
// Register bridge whenever search API or state changes
|
// Extract primitive values from localState to avoid object reference dependencies
|
||||||
|
const localResults = localState.results;
|
||||||
|
const localActiveIndex = localState.activeIndex;
|
||||||
|
|
||||||
|
// Scroll to active result when it changes (for next/previous navigation)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (search) {
|
if (!localResults || localResults.length === 0) {
|
||||||
|
lastScrolledIndexRef.current = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only scroll if the active index actually changed
|
||||||
|
const activeResultIndex = localActiveIndex - 1; // Convert back to 0-based
|
||||||
|
if (activeResultIndex >= 0 &&
|
||||||
|
activeResultIndex < localResults.length &&
|
||||||
|
lastScrolledIndexRef.current !== activeResultIndex) {
|
||||||
|
const activeResult = localResults[activeResultIndex];
|
||||||
|
if (activeResult) {
|
||||||
|
const pageNumber = activeResult.pageIndex + 1; // Convert to 1-based page number
|
||||||
|
scrollActions.scrollToPage(pageNumber);
|
||||||
|
lastScrolledIndexRef.current = activeResultIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [localResults, localActiveIndex, scrollActions]);
|
||||||
|
|
||||||
|
// Register bridge whenever state changes
|
||||||
|
useEffect(() => {
|
||||||
|
const currentSearch = searchRef.current;
|
||||||
|
if (currentSearch) {
|
||||||
registerBridge('search', {
|
registerBridge('search', {
|
||||||
state: localState,
|
state: { results: localResults, activeIndex: localActiveIndex },
|
||||||
api: {
|
api: {
|
||||||
search: async (query: string) => {
|
search: async (query: string) => {
|
||||||
if (search?.startSearch && search?.searchAllPages) {
|
// Prevent overlapping searches
|
||||||
search.startSearch();
|
if (isSearchingRef.current) {
|
||||||
return search.searchAllPages(query);
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentSearch?.startSearch || !currentSearch?.searchAllPages) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSearchingRef.current = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
currentSearch.startSearch();
|
||||||
|
const results = await currentSearch.searchAllPages(query);
|
||||||
|
return results;
|
||||||
|
} catch (error: any) {
|
||||||
|
// Handle abort errors gracefully - these occur when searches overlap
|
||||||
|
if (error?.type === 'abort' || error?.message?.includes('abort')) {
|
||||||
|
// Silently handle abort - this is expected when user types quickly
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
console.error('Search failed:', error);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
isSearchingRef.current = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clear: () => {
|
clear: () => {
|
||||||
try {
|
try {
|
||||||
if (search?.stopSearch) {
|
if (currentSearch?.stopSearch) {
|
||||||
search.stopSearch();
|
currentSearch.stopSearch();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error stopping search:', error);
|
console.warn('Error stopping search:', error);
|
||||||
@@ -71,21 +149,21 @@ export function SearchAPIBridge() {
|
|||||||
},
|
},
|
||||||
next: () => {
|
next: () => {
|
||||||
try {
|
try {
|
||||||
search?.nextResult?.();
|
currentSearch?.nextResult?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error navigating to next result:', error);
|
console.warn('Error navigating to next result:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
previous: () => {
|
previous: () => {
|
||||||
try {
|
try {
|
||||||
search?.previousResult?.();
|
currentSearch?.previousResult?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error navigating to previous result:', error);
|
console.warn('Error navigating to previous result:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goToResult: (index: number) => {
|
goToResult: (index: number) => {
|
||||||
try {
|
try {
|
||||||
search?.goToResult?.(index);
|
currentSearch?.goToResult?.(index);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error going to result:', error);
|
console.warn('Error going to result:', error);
|
||||||
}
|
}
|
||||||
@@ -93,7 +171,7 @@ export function SearchAPIBridge() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [search, localState]);
|
}, [localResults, localActiveIndex, registerBridge]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useSelectionCapability, SelectionRangeX } from '@embedpdf/plugin-selection/react';
|
import { useSelectionCapability } from '@embedpdf/plugin-selection/react';
|
||||||
import { useViewer } from '@app/contexts/ViewerContext';
|
import { useViewer } from '@app/contexts/ViewerContext';
|
||||||
|
|
||||||
/**
|
|
||||||
* Component that runs inside EmbedPDF context and updates selection state in ViewerContext
|
|
||||||
*/
|
|
||||||
export function SelectionAPIBridge() {
|
export function SelectionAPIBridge() {
|
||||||
const { provides: selection } = useSelectionCapability();
|
const { provides: selection } = useSelectionCapability();
|
||||||
const { registerBridge } = useViewer();
|
const { registerBridge } = useViewer();
|
||||||
@@ -16,7 +13,6 @@ export function SelectionAPIBridge() {
|
|||||||
hasSelection
|
hasSelection
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register this bridge with ViewerContext
|
|
||||||
registerBridge('selection', {
|
registerBridge('selection', {
|
||||||
state: newState,
|
state: newState,
|
||||||
api: {
|
api: {
|
||||||
@@ -26,12 +22,10 @@ export function SelectionAPIBridge() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for selection changes to track when text is selected
|
const unsubscribe = selection.onSelectionChange((event: any) => {
|
||||||
const unsubscribe = selection.onSelectionChange((sel: SelectionRangeX | null) => {
|
const hasText = !!(event?.selection || event);
|
||||||
const hasText = !!sel;
|
|
||||||
setHasSelection(hasText);
|
setHasSelection(hasText);
|
||||||
const updatedState = { hasSelection: hasText };
|
const updatedState = { hasSelection: hasText };
|
||||||
// Re-register with updated state
|
|
||||||
registerBridge('selection', {
|
registerBridge('selection', {
|
||||||
state: updatedState,
|
state: updatedState,
|
||||||
api: {
|
api: {
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useSpread, SpreadMode } from '@embedpdf/plugin-spread/react';
|
import { useSpread, SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||||
import { useViewer } from '@app/contexts/ViewerContext';
|
import { useViewer } from '@app/contexts/ViewerContext';
|
||||||
|
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||||
|
|
||||||
/**
|
|
||||||
* Component that runs inside EmbedPDF context and updates spread state in ViewerContext
|
|
||||||
*/
|
|
||||||
export function SpreadAPIBridge() {
|
export function SpreadAPIBridge() {
|
||||||
const { provides: spread, spreadMode } = useSpread();
|
const activeDocumentId = useActiveDocumentId();
|
||||||
|
|
||||||
|
// Don't render the inner component until we have a valid document ID
|
||||||
|
if (!activeDocumentId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <SpreadAPIBridgeInner documentId={activeDocumentId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SpreadAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||||
|
const { provides: spread, spreadMode } = useSpread(documentId);
|
||||||
const { registerBridge, triggerImmediateSpreadUpdate } = useViewer();
|
const { registerBridge, triggerImmediateSpreadUpdate } = useViewer();
|
||||||
|
|
||||||
|
// Keep spread ref updated to avoid re-running effect when object reference changes
|
||||||
|
const spreadRef = useRef(spread);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!spread) {
|
spreadRef.current = spread;
|
||||||
|
}, [spread]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const currentSpread = spreadRef.current;
|
||||||
|
if (!currentSpread || spreadMode === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,20 +39,20 @@ export function SpreadAPIBridge() {
|
|||||||
state: newState,
|
state: newState,
|
||||||
api: {
|
api: {
|
||||||
setSpreadMode: (mode: SpreadMode) => {
|
setSpreadMode: (mode: SpreadMode) => {
|
||||||
spread.setSpreadMode(mode);
|
currentSpread.setSpreadMode(mode);
|
||||||
},
|
},
|
||||||
getSpreadMode: () => spread.getSpreadMode(),
|
getSpreadMode: () => currentSpread.getSpreadMode(),
|
||||||
toggleSpreadMode: () => {
|
toggleSpreadMode: () => {
|
||||||
const current = spread.getSpreadMode();
|
const current = currentSpread.getSpreadMode();
|
||||||
const nextMode = current === SpreadMode.None ? SpreadMode.Odd : SpreadMode.None;
|
const nextMode = current === SpreadMode.None ? SpreadMode.Odd : SpreadMode.None;
|
||||||
spread.setSpreadMode(nextMode);
|
currentSpread.setSpreadMode(nextMode);
|
||||||
},
|
},
|
||||||
SpreadMode,
|
SpreadMode,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
triggerImmediateSpreadUpdate(spreadMode);
|
triggerImmediateSpreadUpdate(spreadMode);
|
||||||
}, [spread, spreadMode, registerBridge, triggerImmediateSpreadUpdate]);
|
}, [spreadMode, registerBridge, triggerImmediateSpreadUpdate]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|||||||
import { useZoom, ZoomMode } from '@embedpdf/plugin-zoom/react';
|
import { useZoom, ZoomMode } from '@embedpdf/plugin-zoom/react';
|
||||||
import { useSpread, SpreadMode } from '@embedpdf/plugin-spread/react';
|
import { useSpread, SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||||
import { useViewer } from '@app/contexts/ViewerContext';
|
import { useViewer } from '@app/contexts/ViewerContext';
|
||||||
|
import { useActiveDocumentId } from '@app/components/viewer/useActiveDocumentId';
|
||||||
import { useFileState } from '@app/contexts/FileContext';
|
import { useFileState } from '@app/contexts/FileContext';
|
||||||
import {
|
import {
|
||||||
determineAutoZoom,
|
determineAutoZoom,
|
||||||
@@ -12,8 +13,19 @@ import {
|
|||||||
import { getFirstPageAspectRatioFromStub } from '@app/utils/pageMetadata';
|
import { getFirstPageAspectRatioFromStub } from '@app/utils/pageMetadata';
|
||||||
|
|
||||||
export function ZoomAPIBridge() {
|
export function ZoomAPIBridge() {
|
||||||
const { provides: zoom, state: zoomState } = useZoom();
|
const activeDocumentId = useActiveDocumentId();
|
||||||
const { spreadMode } = useSpread();
|
|
||||||
|
// Don't render the inner component until we have a valid document ID
|
||||||
|
if (!activeDocumentId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ZoomAPIBridgeInner documentId={activeDocumentId} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ZoomAPIBridgeInner({ documentId }: { documentId: string }) {
|
||||||
|
const { provides: zoom, state: zoomState } = useZoom(documentId);
|
||||||
|
const { spreadMode } = useSpread(documentId);
|
||||||
const { registerBridge, triggerImmediateZoomUpdate } = useViewer();
|
const { registerBridge, triggerImmediateZoomUpdate } = useViewer();
|
||||||
const { selectors } = useFileState();
|
const { selectors } = useFileState();
|
||||||
|
|
||||||
@@ -21,8 +33,14 @@ export function ZoomAPIBridge() {
|
|||||||
const lastSpreadMode = useRef(spreadMode ?? SpreadMode.None);
|
const lastSpreadMode = useRef(spreadMode ?? SpreadMode.None);
|
||||||
const lastFileId = useRef<string | undefined>(undefined);
|
const lastFileId = useRef<string | undefined>(undefined);
|
||||||
const lastAppliedZoom = useRef<number | null>(null);
|
const lastAppliedZoom = useRef<number | null>(null);
|
||||||
|
const zoomRef = useRef(zoom);
|
||||||
const [autoZoomTick, setAutoZoomTick] = useState(0);
|
const [autoZoomTick, setAutoZoomTick] = useState(0);
|
||||||
|
|
||||||
|
// Keep zoom ref updated
|
||||||
|
useEffect(() => {
|
||||||
|
zoomRef.current = zoom;
|
||||||
|
}, [zoom]);
|
||||||
|
|
||||||
const scheduleAutoZoom = useCallback(() => {
|
const scheduleAutoZoom = useCallback(() => {
|
||||||
hasSetInitialZoom.current = false;
|
hasSetInitialZoom.current = false;
|
||||||
lastAppliedZoom.current = null;
|
lastAppliedZoom.current = null;
|
||||||
@@ -30,15 +48,22 @@ export function ZoomAPIBridge() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const requestFitWidth = useCallback(() => {
|
const requestFitWidth = useCallback(() => {
|
||||||
if (zoom) {
|
if (zoomRef.current) {
|
||||||
zoom.requestZoom(ZoomMode.FitWidth, { vx: 0.5, vy: 0 });
|
zoomRef.current.requestZoom(ZoomMode.FitWidth, { vx: 0.5, vy: 0 });
|
||||||
}
|
}
|
||||||
}, [zoom]);
|
}, []);
|
||||||
|
|
||||||
const stubs = selectors.getStirlingFileStubs();
|
const stubs = selectors.getStirlingFileStubs();
|
||||||
const firstFileStub = stubs[0];
|
const firstFileStub = stubs[0];
|
||||||
const firstFileId = firstFileStub?.id;
|
const firstFileId = firstFileStub?.id;
|
||||||
|
|
||||||
|
// Extract primitive values from zoomState for dependency arrays
|
||||||
|
const zoomLevel = zoomState?.zoomLevel;
|
||||||
|
const currentZoomLevel = zoomState?.currentZoomLevel;
|
||||||
|
|
||||||
|
// Extract metadata aspect ratio as a primitive to avoid object reference issues
|
||||||
|
const metadataAspectRatio = getFirstPageAspectRatioFromStub(firstFileStub);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!firstFileId) {
|
if (!firstFileId) {
|
||||||
hasSetInitialZoom.current = false;
|
hasSetInitialZoom.current = false;
|
||||||
@@ -59,7 +84,6 @@ export function ZoomAPIBridge() {
|
|||||||
lastSpreadMode.current = currentSpreadMode;
|
lastSpreadMode.current = currentSpreadMode;
|
||||||
|
|
||||||
const hadTrackedAutoZoom = lastAppliedZoom.current !== null;
|
const hadTrackedAutoZoom = lastAppliedZoom.current !== null;
|
||||||
const zoomLevel = zoomState?.zoomLevel;
|
|
||||||
if (
|
if (
|
||||||
zoomLevel === ZoomMode.FitWidth ||
|
zoomLevel === ZoomMode.FitWidth ||
|
||||||
zoomLevel === ZoomMode.Automatic ||
|
zoomLevel === ZoomMode.Automatic ||
|
||||||
@@ -69,13 +93,13 @@ export function ZoomAPIBridge() {
|
|||||||
scheduleAutoZoom();
|
scheduleAutoZoom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [spreadMode, zoomState?.zoomLevel, scheduleAutoZoom, requestFitWidth]);
|
}, [spreadMode, zoomLevel, scheduleAutoZoom, requestFitWidth]);
|
||||||
|
|
||||||
|
|
||||||
const isManagedZoom =
|
const isManagedZoom =
|
||||||
!!zoom &&
|
!!zoom &&
|
||||||
(zoomState?.zoomLevel === ZoomMode.FitWidth ||
|
(zoomLevel === ZoomMode.FitWidth ||
|
||||||
zoomState?.zoomLevel === ZoomMode.Automatic ||
|
zoomLevel === ZoomMode.Automatic ||
|
||||||
lastAppliedZoom.current !== null);
|
lastAppliedZoom.current !== null);
|
||||||
|
|
||||||
useFitWidthResize({
|
useFitWidthResize({
|
||||||
@@ -85,7 +109,7 @@ export function ZoomAPIBridge() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!zoom || !zoomState) {
|
if (!zoom || zoomLevel === undefined || currentZoomLevel === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,14 +121,14 @@ export function ZoomAPIBridge() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zoomState.zoomLevel !== ZoomMode.FitWidth) {
|
if (zoomLevel !== ZoomMode.FitWidth) {
|
||||||
if (zoomState.zoomLevel === ZoomMode.Automatic) {
|
if (zoomLevel === ZoomMode.Automatic) {
|
||||||
requestFitWidth();
|
requestFitWidth();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fitWidthZoom = zoomState.currentZoomLevel;
|
const fitWidthZoom = currentZoomLevel;
|
||||||
if (!fitWidthZoom || fitWidthZoom <= 0 || fitWidthZoom === 1) {
|
if (!fitWidthZoom || fitWidthZoom <= 0 || fitWidthZoom === 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -121,7 +145,6 @@ export function ZoomAPIBridge() {
|
|||||||
const applyAutoZoom = async () => {
|
const applyAutoZoom = async () => {
|
||||||
const currentSpreadMode = spreadMode ?? SpreadMode.None;
|
const currentSpreadMode = spreadMode ?? SpreadMode.None;
|
||||||
const pagesPerSpread = currentSpreadMode !== SpreadMode.None ? 2 : 1;
|
const pagesPerSpread = currentSpreadMode !== SpreadMode.None ? 2 : 1;
|
||||||
const metadataAspectRatio = getFirstPageAspectRatioFromStub(firstFileStub);
|
|
||||||
|
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return;
|
return;
|
||||||
@@ -165,21 +188,31 @@ export function ZoomAPIBridge() {
|
|||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
zoom,
|
zoom,
|
||||||
zoomState,
|
zoomLevel,
|
||||||
|
currentZoomLevel,
|
||||||
firstFileId,
|
firstFileId,
|
||||||
firstFileStub,
|
metadataAspectRatio,
|
||||||
requestFitWidth,
|
requestFitWidth,
|
||||||
autoZoomTick,
|
autoZoomTick,
|
||||||
spreadMode,
|
spreadMode,
|
||||||
triggerImmediateZoomUpdate,
|
triggerImmediateZoomUpdate,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Subscribe to zoom changes - use ref to avoid re-subscribing when zoom reference changes
|
||||||
|
const zoomSubscriptionRef = useRef<(() => void) | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Cleanup previous subscription if any
|
||||||
|
if (zoomSubscriptionRef.current) {
|
||||||
|
zoomSubscriptionRef.current();
|
||||||
|
zoomSubscriptionRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!zoom) {
|
if (!zoom) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsubscribe = zoom.onZoomChange((event: { newZoom?: number }) => {
|
zoomSubscriptionRef.current = zoom.onZoomChange((event: { newZoom?: number }) => {
|
||||||
if (typeof event?.newZoom !== 'number') {
|
if (typeof event?.newZoom !== 'number') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -188,17 +221,25 @@ export function ZoomAPIBridge() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
if (zoomSubscriptionRef.current) {
|
||||||
|
zoomSubscriptionRef.current();
|
||||||
|
zoomSubscriptionRef.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [zoom, triggerImmediateZoomUpdate]);
|
}, [zoom, triggerImmediateZoomUpdate]);
|
||||||
|
|
||||||
|
// Extract primitive values to avoid dependency on object reference
|
||||||
|
const zoomStateCurrentZoomLevel = zoomState?.currentZoomLevel;
|
||||||
|
|
||||||
|
// Register bridge - only re-run when actual values change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!zoom || !zoomState) {
|
const currentZoom = zoomRef.current;
|
||||||
|
if (!currentZoom || zoomStateCurrentZoomLevel === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentZoomLevel =
|
const currentZoomLevel =
|
||||||
lastAppliedZoom.current ?? zoomState.currentZoomLevel ?? 1;
|
lastAppliedZoom.current ?? zoomStateCurrentZoomLevel ?? 1;
|
||||||
|
|
||||||
const newState = {
|
const newState = {
|
||||||
currentZoom: currentZoomLevel,
|
currentZoom: currentZoomLevel,
|
||||||
@@ -209,9 +250,9 @@ export function ZoomAPIBridge() {
|
|||||||
|
|
||||||
registerBridge('zoom', {
|
registerBridge('zoom', {
|
||||||
state: newState,
|
state: newState,
|
||||||
api: zoom,
|
api: currentZoom,
|
||||||
});
|
});
|
||||||
}, [zoom, zoomState, registerBridge, triggerImmediateZoomUpdate]);
|
}, [zoomStateCurrentZoomLevel, registerBridge, triggerImmediateZoomUpdate]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { useActiveDocument } from '@app/components/viewer/ActiveDocumentContext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to get the currently active document ID.
|
||||||
|
* Uses a shared context to avoid multiple subscriptions.
|
||||||
|
*/
|
||||||
|
export function useActiveDocumentId(): string | null {
|
||||||
|
return useActiveDocument();
|
||||||
|
}
|
||||||
@@ -38,6 +38,8 @@ import {
|
|||||||
ExportState,
|
ExportState,
|
||||||
ThumbnailAPIWrapper,
|
ThumbnailAPIWrapper,
|
||||||
BookmarkState,
|
BookmarkState,
|
||||||
|
DocumentPermissionsState,
|
||||||
|
PdfPermissionFlag,
|
||||||
} from '@app/contexts/viewer/viewerBridges';
|
} from '@app/contexts/viewer/viewerBridges';
|
||||||
import { SpreadMode } from '@embedpdf/plugin-spread/react';
|
import { SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||||
|
|
||||||
@@ -112,6 +114,8 @@ interface ViewerContextType {
|
|||||||
getExportState: () => ExportState;
|
getExportState: () => ExportState;
|
||||||
getBookmarkState: () => BookmarkState;
|
getBookmarkState: () => BookmarkState;
|
||||||
hasBookmarkSupport: () => boolean;
|
hasBookmarkSupport: () => boolean;
|
||||||
|
getDocumentPermissions: () => DocumentPermissionsState;
|
||||||
|
hasPermission: (flag: PdfPermissionFlag) => boolean;
|
||||||
|
|
||||||
// Immediate update callbacks
|
// Immediate update callbacks
|
||||||
registerImmediateZoomUpdate: (callback: (percent: number) => void) => () => void;
|
registerImmediateZoomUpdate: (callback: (percent: number) => void) => () => void;
|
||||||
@@ -308,6 +312,34 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
|||||||
|
|
||||||
const hasBookmarkSupport = () => Boolean(bridgeRefs.current.bookmark);
|
const hasBookmarkSupport = () => Boolean(bridgeRefs.current.bookmark);
|
||||||
|
|
||||||
|
const getDocumentPermissions = (): DocumentPermissionsState => {
|
||||||
|
return bridgeRefs.current.permissions?.state || {
|
||||||
|
isEncrypted: false,
|
||||||
|
isOwnerUnlocked: false,
|
||||||
|
permissions: PdfPermissionFlag.AllowAll,
|
||||||
|
canPrint: true,
|
||||||
|
canModifyContents: true,
|
||||||
|
canCopyContents: true,
|
||||||
|
canModifyAnnotations: true,
|
||||||
|
canFillForms: true,
|
||||||
|
canExtractForAccessibility: true,
|
||||||
|
canAssembleDocument: true,
|
||||||
|
canPrintHighQuality: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasPermission = (flag: PdfPermissionFlag): boolean => {
|
||||||
|
const api = bridgeRefs.current.permissions?.api;
|
||||||
|
if (api?.hasPermission) {
|
||||||
|
return api.hasPermission(flag);
|
||||||
|
}
|
||||||
|
// Default: allow all permissions - warn in development
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('[ViewerContext] Permissions API not available, defaulting to allow');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
// Action handlers - call APIs directly
|
// Action handlers - call APIs directly
|
||||||
const {
|
const {
|
||||||
scrollActions,
|
scrollActions,
|
||||||
@@ -360,6 +392,8 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
|||||||
getExportState,
|
getExportState,
|
||||||
getBookmarkState,
|
getBookmarkState,
|
||||||
hasBookmarkSupport,
|
hasBookmarkSupport,
|
||||||
|
getDocumentPermissions,
|
||||||
|
hasPermission,
|
||||||
|
|
||||||
// Immediate updates
|
// Immediate updates
|
||||||
registerImmediateZoomUpdate,
|
registerImmediateZoomUpdate,
|
||||||
|
|||||||
@@ -100,32 +100,63 @@ export function createViewerActions({
|
|||||||
scrollToPage: (page: number, behavior?: 'smooth' | 'instant') => {
|
scrollToPage: (page: number, behavior?: 'smooth' | 'instant') => {
|
||||||
const api = registry.current.scroll?.api;
|
const api = registry.current.scroll?.api;
|
||||||
if (api?.scrollToPage) {
|
if (api?.scrollToPage) {
|
||||||
|
try {
|
||||||
api.scrollToPage({ pageNumber: page, behavior: behavior || 'smooth' });
|
api.scrollToPage({ pageNumber: page, behavior: behavior || 'smooth' });
|
||||||
|
} catch (error) {
|
||||||
|
// Silently handle "Strategy not found" errors that occur during document transitions
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('[ScrollActions] scrollToPage failed (document may be transitioning):', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollToFirstPage: () => {
|
scrollToFirstPage: () => {
|
||||||
const api = registry.current.scroll?.api;
|
const api = registry.current.scroll?.api;
|
||||||
if (api?.scrollToPage) {
|
if (api?.scrollToPage) {
|
||||||
|
try {
|
||||||
api.scrollToPage({ pageNumber: 1 });
|
api.scrollToPage({ pageNumber: 1 });
|
||||||
|
} catch (error) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('[ScrollActions] scrollToFirstPage failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollToPreviousPage: () => {
|
scrollToPreviousPage: () => {
|
||||||
const api = registry.current.scroll?.api;
|
const api = registry.current.scroll?.api;
|
||||||
if (api?.scrollToPreviousPage) {
|
if (api?.scrollToPreviousPage) {
|
||||||
|
try {
|
||||||
api.scrollToPreviousPage();
|
api.scrollToPreviousPage();
|
||||||
|
} catch (error) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('[ScrollActions] scrollToPreviousPage failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollToNextPage: () => {
|
scrollToNextPage: () => {
|
||||||
const api = registry.current.scroll?.api;
|
const api = registry.current.scroll?.api;
|
||||||
if (api?.scrollToNextPage) {
|
if (api?.scrollToNextPage) {
|
||||||
|
try {
|
||||||
api.scrollToNextPage();
|
api.scrollToNextPage();
|
||||||
|
} catch (error) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('[ScrollActions] scrollToNextPage failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollToLastPage: () => {
|
scrollToLastPage: () => {
|
||||||
const api = registry.current.scroll?.api;
|
const api = registry.current.scroll?.api;
|
||||||
const state = getScrollState();
|
const state = getScrollState();
|
||||||
if (api?.scrollToPage && state.totalPages > 0) {
|
if (api?.scrollToPage && state.totalPages > 0) {
|
||||||
|
try {
|
||||||
api.scrollToPage({ pageNumber: state.totalPages });
|
api.scrollToPage({ pageNumber: state.totalPages });
|
||||||
|
} catch (error) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.warn('[ScrollActions] scrollToLastPage failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,38 @@
|
|||||||
import { SpreadMode } from '@embedpdf/plugin-spread/react';
|
import { SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||||
import { PdfBookmarkObject } from '@embedpdf/models';
|
import { PdfBookmarkObject } from '@embedpdf/models';
|
||||||
|
|
||||||
|
export enum PdfPermissionFlag {
|
||||||
|
Print = 0x0004,
|
||||||
|
ModifyContents = 0x0008,
|
||||||
|
CopyContents = 0x0010,
|
||||||
|
ModifyAnnotations = 0x0020,
|
||||||
|
FillForms = 0x0100,
|
||||||
|
ExtractForAccessibility = 0x0200,
|
||||||
|
AssembleDocument = 0x0400,
|
||||||
|
PrintHighQuality = 0x0800,
|
||||||
|
AllowAll = 0x0f3c,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DocumentPermissionsState {
|
||||||
|
isEncrypted: boolean;
|
||||||
|
isOwnerUnlocked: boolean;
|
||||||
|
permissions: number;
|
||||||
|
canPrint: boolean;
|
||||||
|
canModifyContents: boolean;
|
||||||
|
canCopyContents: boolean;
|
||||||
|
canModifyAnnotations: boolean;
|
||||||
|
canFillForms: boolean;
|
||||||
|
canExtractForAccessibility: boolean;
|
||||||
|
canAssembleDocument: boolean;
|
||||||
|
canPrintHighQuality: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DocumentPermissionsAPIWrapper {
|
||||||
|
hasPermission: (flag: PdfPermissionFlag) => boolean;
|
||||||
|
hasAllPermissions: (flags: PdfPermissionFlag[]) => boolean;
|
||||||
|
getEffectivePermission: (flag: PdfPermissionFlag) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ScrollAPIWrapper {
|
export interface ScrollAPIWrapper {
|
||||||
scrollToPage: (params: { pageNumber: number; behavior?: ScrollBehavior }) => void;
|
scrollToPage: (params: { pageNumber: number; behavior?: ScrollBehavior }) => void;
|
||||||
scrollToPreviousPage: () => void;
|
scrollToPreviousPage: () => void;
|
||||||
@@ -137,6 +169,7 @@ export interface BridgeStateMap {
|
|||||||
export: ExportState;
|
export: ExportState;
|
||||||
bookmark: BookmarkState;
|
bookmark: BookmarkState;
|
||||||
print: unknown;
|
print: unknown;
|
||||||
|
permissions: DocumentPermissionsState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BridgeApiMap {
|
export interface BridgeApiMap {
|
||||||
@@ -151,6 +184,7 @@ export interface BridgeApiMap {
|
|||||||
export: ExportAPIWrapper;
|
export: ExportAPIWrapper;
|
||||||
bookmark: BookmarkAPIWrapper;
|
bookmark: BookmarkAPIWrapper;
|
||||||
print: PrintAPIWrapper;
|
print: PrintAPIWrapper;
|
||||||
|
permissions: DocumentPermissionsAPIWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BridgeKey = keyof BridgeStateMap;
|
export type BridgeKey = keyof BridgeStateMap;
|
||||||
@@ -171,6 +205,7 @@ export const createBridgeRegistry = (): ViewerBridgeRegistry => ({
|
|||||||
export: null,
|
export: null,
|
||||||
bookmark: null,
|
bookmark: null,
|
||||||
print: null,
|
print: null,
|
||||||
|
permissions: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function registerBridge<K extends BridgeKey>(
|
export function registerBridge<K extends BridgeKey>(
|
||||||
|
|||||||
@@ -270,7 +270,11 @@ export function useAnnotationSelection({
|
|||||||
// internal selection state (e.g., accessing `selectedUid` on
|
// internal selection state (e.g., accessing `selectedUid` on
|
||||||
// an undefined object). Treat this as "no current selection"
|
// an undefined object). Treat this as "no current selection"
|
||||||
// instead of crashing the annotations tool.
|
// instead of crashing the annotations tool.
|
||||||
|
// Only log unexpected errors - "No active document" is a common expected state during init
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
if (!errorMessage.includes('No active document')) {
|
||||||
console.error('[useAnnotationSelection] getSelectedAnnotation failed:', error);
|
console.error('[useAnnotationSelection] getSelectedAnnotation failed:', error);
|
||||||
|
}
|
||||||
ann = null;
|
ann = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user