mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-01-14 20:11:17 +01:00
V2 manual redaction in the viewer
This commit is contained in:
parent
8f94c7d7b0
commit
ff6b1f333c
405
frontend/package-lock.json
generated
405
frontend/package-lock.json
generated
@ -10,24 +10,25 @@
|
||||
"license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE",
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||
"@embedpdf/core": "^1.3.14",
|
||||
"@embedpdf/engines": "^1.3.14",
|
||||
"@embedpdf/plugin-annotation": "^1.3.14",
|
||||
"@embedpdf/plugin-export": "^1.3.14",
|
||||
"@embedpdf/plugin-history": "^1.3.14",
|
||||
"@embedpdf/plugin-interaction-manager": "^1.3.14",
|
||||
"@embedpdf/plugin-loader": "^1.3.14",
|
||||
"@embedpdf/plugin-pan": "^1.3.14",
|
||||
"@embedpdf/plugin-render": "^1.3.14",
|
||||
"@embedpdf/plugin-rotate": "^1.3.14",
|
||||
"@embedpdf/plugin-scroll": "^1.3.14",
|
||||
"@embedpdf/plugin-search": "^1.3.14",
|
||||
"@embedpdf/plugin-selection": "^1.3.14",
|
||||
"@embedpdf/plugin-spread": "^1.3.14",
|
||||
"@embedpdf/plugin-thumbnail": "^1.3.14",
|
||||
"@embedpdf/plugin-tiling": "^1.3.14",
|
||||
"@embedpdf/plugin-viewport": "^1.3.14",
|
||||
"@embedpdf/plugin-zoom": "^1.3.14",
|
||||
"@embedpdf/core": "^1.4.1",
|
||||
"@embedpdf/engines": "^1.4.1",
|
||||
"@embedpdf/plugin-annotation": "^1.4.1",
|
||||
"@embedpdf/plugin-export": "^1.4.1",
|
||||
"@embedpdf/plugin-history": "^1.4.1",
|
||||
"@embedpdf/plugin-interaction-manager": "^1.4.1",
|
||||
"@embedpdf/plugin-loader": "^1.4.1",
|
||||
"@embedpdf/plugin-pan": "^1.4.1",
|
||||
"@embedpdf/plugin-redaction": "^1.4.1",
|
||||
"@embedpdf/plugin-render": "^1.4.1",
|
||||
"@embedpdf/plugin-rotate": "^1.4.1",
|
||||
"@embedpdf/plugin-scroll": "^1.4.1",
|
||||
"@embedpdf/plugin-search": "^1.4.1",
|
||||
"@embedpdf/plugin-selection": "^1.4.1",
|
||||
"@embedpdf/plugin-spread": "^1.4.1",
|
||||
"@embedpdf/plugin-thumbnail": "^1.4.1",
|
||||
"@embedpdf/plugin-tiling": "^1.4.1",
|
||||
"@embedpdf/plugin-viewport": "^1.4.1",
|
||||
"@embedpdf/plugin-zoom": "^1.4.1",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@iconify/react": "^6.0.2",
|
||||
@ -502,63 +503,65 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/core": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.3.14.tgz",
|
||||
"integrity": "sha512-lE/vfhA53CxamaCfGWEibrEPr+JeZT42QCF+cOELUwv4+Zt6b+IE6+4wsznx/8wjjJYwllXJ3GJ/un1UzTqARw==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.4.1.tgz",
|
||||
"integrity": "sha512-TGpxn2CvAKRnOJWJ3bsK+dKBiCp75ehxftRUmv7wAmPomhnG5XrDfoWJungvO+zbbqAwso6PocdeXINVt3hlAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/engines": "1.3.14",
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/engines": "1.4.1",
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/engines": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.3.14.tgz",
|
||||
"integrity": "sha512-+/FPW2gAzj2lQYvsMH/Oj9+MEXgkyEuyYDC+HFkltTuXvmiP2S/3BD0YslZDX9K4BzcmMxnWB+BiQpNJokbDVg==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.4.1.tgz",
|
||||
"integrity": "sha512-yugIb5OwTI/1VnAaEvSYxAd2DvYBPkV/D7wytagyaOq98o3sqzcY2Q9zHt+LhnawA5KKG1e/FDPjCd4qm8gsvg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14",
|
||||
"@embedpdf/pdfium": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1",
|
||||
"@embedpdf/pdfium": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/models": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.3.14.tgz",
|
||||
"integrity": "sha512-BujY4bmr8b2DQdoZkOge03SzoRVoWxzfIQATLSPPtp4WiFh1U4BPp6cADlGuCwGkp6zBcH/aM4h8PwwA75d/eg==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.4.1.tgz",
|
||||
"integrity": "sha512-2nTg8Q1qpplBvspZJXMCZOA+/OILpfdNRPddlplxZXY/Upx0rzKXx/e6pXWW7AuOgtfGneT4h9tMs3A595/PdQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@embedpdf/pdfium": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.3.14.tgz",
|
||||
"integrity": "sha512-TQMZabXzHmzvvfPwopubFcYgQuYV7POvMgjICYu3Pgfn3sgr+UdIUh3aNXR/COcl3q8sXPMFQ2GDuyOHR9QQnA==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.4.1.tgz",
|
||||
"integrity": "sha512-BekKEK4UNCwzj7xOffKn6WpL0FQHxq+mTj2iGI3N7OwAX2J/BO2G+rDOB+lvojQG+Dkpg8uqm427ZKJDRyLgVQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-annotation": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.3.14.tgz",
|
||||
"integrity": "sha512-JJYqEWwUKCdBZsXCDq/CW96p3pVLn8N+XZ4W3OyL7djI2fvYC9x6ys9m82vwlSathAVOxk1D7xXiY8AzJQVF0Q==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-1.4.1.tgz",
|
||||
"integrity": "sha512-d4HibNy6ecyDqx2Y2R8VjaqppSdjNofAJmU6VenOd88wn080sAUqvnkeVJ6ehJH5BoND4ymQrcAkcbVeYK0myA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14",
|
||||
"@embedpdf/utils": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1",
|
||||
"@embedpdf/utils": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/plugin-history": "1.3.14",
|
||||
"@embedpdf/plugin-interaction-manager": "1.3.14",
|
||||
"@embedpdf/plugin-selection": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"@embedpdf/plugin-history": "1.4.1",
|
||||
"@embedpdf/plugin-interaction-manager": "1.4.1",
|
||||
"@embedpdf/plugin-selection": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@ -566,31 +569,32 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-export": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.3.14.tgz",
|
||||
"integrity": "sha512-fMGp2YxvI4uTRIViUKxfnJts2Jw/vktEM45XUNGNSjT/kAW6znVNgdceYjpK++xU8CGs2grAQ1i5UvMd3aRNDA==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.4.1.tgz",
|
||||
"integrity": "sha512-g89fREFM/zkt2Ai2Q5dWwDkhXgC/JmVyUniaMgm1fTG/MZ0Z05E7f34DUzX/CKcJyVjxEgl6tojBTMeUbm15bA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-history": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.3.14.tgz",
|
||||
"integrity": "sha512-77hnNLp0W0FHw8lT7SeqzCgp8bOClfeOAPZdcInu/jPDhVASUGYbtE/0fkLhiaqPH7kyMirNCLif4sF6n4b5vg==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.4.1.tgz",
|
||||
"integrity": "sha512-5WLDiNMH6tACkLGGv/lJtNsDeozOhSbrh0mjD1btHun8u7Yscu/Vf8tdJRUOsd+nULivo2nQ2NFNKu0OTbVo8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@ -598,49 +602,71 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-interaction-manager": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.3.14.tgz",
|
||||
"integrity": "sha512-nR0ZxNoTQtGqOHhweFh6QJ+nUJ4S4Ag1wWur6vAUAi8U95HUOfZhOEa0polZo0zR9WmmblGqRWjFM+mVSOoi1w==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.4.1.tgz",
|
||||
"integrity": "sha512-Ng02S9SFIAi9JZS5rI+NXSnZZ1Yk9YYRw4MlN2pig49qOyivZdz0oScZaYxQPewo8ccJkLeghjdeWswOBW/6cA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-loader": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.3.14.tgz",
|
||||
"integrity": "sha512-KoJX1MacEWE2DrO1OeZeG/Ehz76//u+ida/xb4r9BfwqAp5TfYlksq09cOvcF8LMW5FY4pbAL+AHKI1Hjz+HNA==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.4.1.tgz",
|
||||
"integrity": "sha512-m3ZOk8JygsLxoa4cZ+0BVB5pfRWuBCg2/gPqjhoFZNKTqAFw4J6HGUrhYKg94GRYe+w1cTJl/NbTBYuU5DOrsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-pan": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.3.14.tgz",
|
||||
"integrity": "sha512-7EG+I5nn8yDCV8pT4x/g5mv7zJli2t3wPrh6Kt8uIpUorPHNb6J0Z67gl0uc/8rEasNzuKOuT0er46Y6/UYLzQ==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.4.1.tgz",
|
||||
"integrity": "sha512-zmOZJ9dUqXiaV0F5GPf/5WTWf3jAEkiv153Tl3x8HT9Rfff+WQhV48NruCIBAy/T4jVt4aH7D1zt/B/ftvcdkA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/plugin-interaction-manager": "1.3.14",
|
||||
"@embedpdf/plugin-viewport": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"@embedpdf/plugin-interaction-manager": "1.4.1",
|
||||
"@embedpdf/plugin-viewport": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-redaction": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-1.4.1.tgz",
|
||||
"integrity": "sha512-lyVIDxAwU8mUbhxh8J1dhWZ90086YSZ1+RB50hfCAyaxftRF3XpHPhmLZhr6lr3z1lDx+w9VIcoCU6IvhcweTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.4.1",
|
||||
"@embedpdf/utils": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"@embedpdf/plugin-interaction-manager": "1.4.1",
|
||||
"@embedpdf/plugin-selection": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@ -648,182 +674,192 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-render": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.3.14.tgz",
|
||||
"integrity": "sha512-IPj7GCQXJBsY++JaU+z7y+FwX5NaDBj4YYV6hsHNtSGf42Y1AdlwJzDYetivG2bA84xmk7KgD1X2Y3eIFBhjwA==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.4.1.tgz",
|
||||
"integrity": "sha512-gKCdNKw6WBHBEpTc2DLBWIWOxzsNnaNbpfeY6C4f2Bum0EO+XW3Hl2oIx1uaRHjIhhnXso1J3QweqelsPwDGwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-rotate": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.3.14.tgz",
|
||||
"integrity": "sha512-OroEm11x/fPPXI9C0X+nm9LOjwaI0MvsToZRH+HpV60/FbQeOJvt6D8wThCDVLK95Na6A+JeYIMEu+Hiix7H+A==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.4.1.tgz",
|
||||
"integrity": "sha512-hVzHkKwMNH3tUhxqJGsj5qTLpYZXbj6E74AEcG0w/fz5FrK7EnofPqt0gRfYmIzxnQGIh+39BRtcp8gmx8UNnw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-scroll": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.3.14.tgz",
|
||||
"integrity": "sha512-fQbt7OlRMLQJMuZj/Bzh0qpRxMw1ld5Qe/OTw8N54b/plljnFA52joE7cITl3H03huWWyHS3NKOScbw7f34dog==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.4.1.tgz",
|
||||
"integrity": "sha512-Y9O+matB4j4fLim5s/jn7qIi+lMC9vmDJRpJhiWe8bvD9oYLP2xfD/DdhFgAjRKcNhPoxC+j8q8QN5BMeGAv2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/plugin-viewport": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"@embedpdf/plugin-viewport": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-search": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.3.14.tgz",
|
||||
"integrity": "sha512-tlZEgR2tG+GSNnh2u1SjCxhUHfTDgcr38sE/xRK1bRLDGPZWlr6Ln7qP7JSWqeYBGni75sGrj0iZqcZbPWyJag==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.4.1.tgz",
|
||||
"integrity": "sha512-8JG4CbOcUsLuT0vHJJ4cECmu+Yn53EokWFUVXi2Mo/XvHjhrQuWmD7+y6s/qQPEpctFYWmUCXTDAX9ynPud+2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/plugin-loader": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"@embedpdf/plugin-loader": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-selection": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.3.14.tgz",
|
||||
"integrity": "sha512-EXENuaAsse3rT6cjA1nYzyrNvoy62ojJl28wblCng6zcs3HSlGPemIQZAvaYKPUxoY608M+6nKlcMQ5neRnk/A==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.4.1.tgz",
|
||||
"integrity": "sha512-lo5Ytk1PH0PrRKv6zKVupm4t02VGsqIrnSIeP6NO8Ujx0wfqEhj//sqIuO/EwfFVJD8lcQIP9UUo9y8baCrEog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/plugin-interaction-manager": "1.3.14",
|
||||
"@embedpdf/plugin-viewport": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"@embedpdf/plugin-interaction-manager": "1.4.1",
|
||||
"@embedpdf/plugin-viewport": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-spread": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.3.14.tgz",
|
||||
"integrity": "sha512-DVlk6tDgUoDRkp2S4Jc3LrRTuf4DPMlph9vywJw5z6Qpbh0vgcMnObg896/S0Eu5FgACNAj0WGcXpLrcrn5b9Q==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.4.1.tgz",
|
||||
"integrity": "sha512-l+SrDVGTiiItkt2cEtzv7V/X5HhmLbYHcQ8CFobGeIKdJtzKS1Nu/JSKqg7Ki7eCNgyPL1yMNfNE92bNKYVN4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/plugin-loader": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"@embedpdf/plugin-loader": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-thumbnail": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.3.14.tgz",
|
||||
"integrity": "sha512-cnwb5dG8Jph8XSArys1WFCQ6kK2R5FKoO0B5mDrHFv9Fcm2pKszlmZC/NDoskX4pgNUgSnwhI1X3cP37ebF9Ng==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.4.1.tgz",
|
||||
"integrity": "sha512-bN3msjI0PovazgbPK3LyugYVTwIDo0RyBUhBaG42FgJxeY3hmFOWTPgfUH1QF7twHlySnksIvHRFYR3nViryVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/plugin-render": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"@embedpdf/plugin-render": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-tiling": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.3.14.tgz",
|
||||
"integrity": "sha512-SaCTo2LdZwGeE6jCqkwJxvwt8YKbsI3QGxa9S7Ez+5OcBchlhHeTfLQswcErDQ3WH2p8WHtGuucAcOLrVVOm0A==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.4.1.tgz",
|
||||
"integrity": "sha512-wgTfj5T8HV6KP61iiR63DVNrbVp8sPxTqa1Sm+2/D0jY+EPSSCmpt1/qYWiAXd1X+t78foOjCnfbo7fEMn5/pg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/plugin-render": "1.3.14",
|
||||
"@embedpdf/plugin-scroll": "1.3.14",
|
||||
"@embedpdf/plugin-viewport": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"@embedpdf/plugin-render": "1.4.1",
|
||||
"@embedpdf/plugin-scroll": "1.4.1",
|
||||
"@embedpdf/plugin-viewport": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-viewport": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.3.14.tgz",
|
||||
"integrity": "sha512-mfJ7EbbU68eKk6oFvQ4ozGJNpxUxWbjQ5Gm3uuB+Gj5/tWgBocBOX36k/9LgivEEeX7g2S0tOgyErljApmH8Vg==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.4.1.tgz",
|
||||
"integrity": "sha512-+TgFHKPCLTBiDYe2DdsmTS37hwQgcZ3dYIc7bE0l5cp+GVwouu1h0MTmjL+90loizeWwCiu10E/zXR6hz+CUaQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14"
|
||||
"@embedpdf/models": "1.4.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-zoom": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.3.14.tgz",
|
||||
"integrity": "sha512-/N5tyMk+8OzhObrS3O9yPkcmX8EPiuTo+WaT2QCVSmIUqKnOO4AnKpHJ6Vl0uVhcuXHCMwLucZKyhJ7tRqavwg==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.4.1.tgz",
|
||||
"integrity": "sha512-9HocmXnPZxqN06q7kyNAmLjgDHOEW8/8QfgNE3nMpRyNHIgnAjxvsWc9lApgp5ErDPG0cSDt0Cduil6nB3wSBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "1.3.14",
|
||||
"@embedpdf/models": "1.4.1",
|
||||
"hammerjs": "^2.0.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "1.3.14",
|
||||
"@embedpdf/plugin-interaction-manager": "1.3.14",
|
||||
"@embedpdf/plugin-scroll": "1.3.14",
|
||||
"@embedpdf/plugin-viewport": "1.3.14",
|
||||
"@embedpdf/core": "1.4.1",
|
||||
"@embedpdf/plugin-interaction-manager": "1.4.1",
|
||||
"@embedpdf/plugin-scroll": "1.4.1",
|
||||
"@embedpdf/plugin-viewport": "1.4.1",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
"svelte": ">=5 <6",
|
||||
"vue": ">=3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/utils": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.3.14.tgz",
|
||||
"integrity": "sha512-gxEJD12nageCMqAjdbicNfDQolXU3nvnV0EX96OdZITRNj0Q1tisutVYoaxcCiJu3vvIEOzipjsAnQOubbFCEA==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-1.4.1.tgz",
|
||||
"integrity": "sha512-vvJ51Qsz3PyJWR2YvDMMpJXg4+YqdV7Vn2cusmW9sx+4EnAiBiw0HevEE+FepgFV8k+A0WbwXzmsujDIQJ7R4A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
@ -3045,6 +3081,16 @@
|
||||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/acorn-typescript": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
|
||||
"integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"acorn": "^8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz",
|
||||
@ -3802,7 +3848,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/gapi": {
|
||||
@ -4743,7 +4788,6 @@
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@ -5147,6 +5191,16 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/b4a": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz",
|
||||
@ -7176,6 +7230,13 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/esm-env": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||
@ -7234,6 +7295,16 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/esrap": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz",
|
||||
"integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
}
|
||||
},
|
||||
"node_modules/esrecurse": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||
@ -8747,6 +8818,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-reference": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||
@ -9623,6 +9704,13 @@
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-character": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
@ -12959,6 +13047,42 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.43.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.43.3.tgz",
|
||||
"integrity": "sha512-kjkAjCk41mJfvJZG56XcJNOdJSke94JxtcX8zFzzz2vrt47E0LnoBzU6azIZ1aBxJgUep8qegAkguSf1GjxLXQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.4",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@sveltejs/acorn-typescript": "^1.0.5",
|
||||
"@types/estree": "^1.0.5",
|
||||
"acorn": "^8.12.1",
|
||||
"aria-query": "^5.3.1",
|
||||
"axobject-query": "^4.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"esm-env": "^1.2.1",
|
||||
"esrap": "^2.1.0",
|
||||
"is-reference": "^3.0.3",
|
||||
"locate-character": "^3.0.0",
|
||||
"magic-string": "^0.30.11",
|
||||
"zimmerframe": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte/node_modules/aria-query": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
@ -14447,6 +14571,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zimmerframe": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
|
||||
@ -6,24 +6,25 @@
|
||||
"proxy": "http://localhost:8080",
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||
"@embedpdf/core": "^1.3.14",
|
||||
"@embedpdf/engines": "^1.3.14",
|
||||
"@embedpdf/plugin-annotation": "^1.3.14",
|
||||
"@embedpdf/plugin-export": "^1.3.14",
|
||||
"@embedpdf/plugin-history": "^1.3.14",
|
||||
"@embedpdf/plugin-interaction-manager": "^1.3.14",
|
||||
"@embedpdf/plugin-loader": "^1.3.14",
|
||||
"@embedpdf/plugin-pan": "^1.3.14",
|
||||
"@embedpdf/plugin-render": "^1.3.14",
|
||||
"@embedpdf/plugin-rotate": "^1.3.14",
|
||||
"@embedpdf/plugin-scroll": "^1.3.14",
|
||||
"@embedpdf/plugin-search": "^1.3.14",
|
||||
"@embedpdf/plugin-selection": "^1.3.14",
|
||||
"@embedpdf/plugin-spread": "^1.3.14",
|
||||
"@embedpdf/plugin-thumbnail": "^1.3.14",
|
||||
"@embedpdf/plugin-tiling": "^1.3.14",
|
||||
"@embedpdf/plugin-viewport": "^1.3.14",
|
||||
"@embedpdf/plugin-zoom": "^1.3.14",
|
||||
"@embedpdf/core": "^1.4.1",
|
||||
"@embedpdf/engines": "^1.4.1",
|
||||
"@embedpdf/plugin-annotation": "^1.4.1",
|
||||
"@embedpdf/plugin-export": "^1.4.1",
|
||||
"@embedpdf/plugin-history": "^1.4.1",
|
||||
"@embedpdf/plugin-interaction-manager": "^1.4.1",
|
||||
"@embedpdf/plugin-loader": "^1.4.1",
|
||||
"@embedpdf/plugin-pan": "^1.4.1",
|
||||
"@embedpdf/plugin-render": "^1.4.1",
|
||||
"@embedpdf/plugin-rotate": "^1.4.1",
|
||||
"@embedpdf/plugin-scroll": "^1.4.1",
|
||||
"@embedpdf/plugin-search": "^1.4.1",
|
||||
"@embedpdf/plugin-selection": "^1.4.1",
|
||||
"@embedpdf/plugin-spread": "^1.4.1",
|
||||
"@embedpdf/plugin-thumbnail": "^1.4.1",
|
||||
"@embedpdf/plugin-tiling": "^1.4.1",
|
||||
"@embedpdf/plugin-viewport": "^1.4.1",
|
||||
"@embedpdf/plugin-zoom": "^1.4.1",
|
||||
"@embedpdf/plugin-redaction": "^1.4.1",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@iconify/react": "^6.0.2",
|
||||
|
||||
@ -64,7 +64,7 @@ export default function ViewerAnnotationControls({ currentView, disabled = false
|
||||
disabled={disabled || currentView !== 'viewer' || viewerContext?.isAnnotationMode || isPlacementMode}
|
||||
>
|
||||
<LocalIcon
|
||||
icon={viewerContext?.isAnnotationsVisible ? "visibility" : "visibility-off-rounded"}
|
||||
icon={viewerContext?.isAnnotationsVisible ? "layers-rounded" : "layers-clear-rounded"}
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
/>
|
||||
@ -136,6 +136,10 @@ export default function ViewerAnnotationControls({ currentView, disabled = false
|
||||
radius="md"
|
||||
className="right-rail-icon"
|
||||
onClick={() => {
|
||||
// Disable redaction tool while entering draw mode
|
||||
try { viewerContext?.redactionActions.commitAllPending?.(); } catch {}
|
||||
try { viewerContext?.redactionActions.deactivate?.(); } catch {}
|
||||
try { viewerContext?.panActions.disablePan?.(); } catch {}
|
||||
viewerContext?.toggleAnnotationMode();
|
||||
// Activate ink drawing tool when entering annotation mode
|
||||
if (signatureApiRef?.current && currentView === 'viewer') {
|
||||
@ -146,6 +150,10 @@ export default function ViewerAnnotationControls({ currentView, disabled = false
|
||||
console.log('Signature API not ready:', error);
|
||||
}
|
||||
}
|
||||
// Notify after state settles so right rail updates reliably
|
||||
setTimeout(() => {
|
||||
try { viewerContext?.triggerToolModeUpdate?.(); } catch {}
|
||||
}, 0);
|
||||
}}
|
||||
disabled={disabled}
|
||||
aria-label={typeof t === 'function' ? t('rightRail.draw', 'Draw') : 'Draw'}
|
||||
@ -164,6 +172,8 @@ export default function ViewerAnnotationControls({ currentView, disabled = false
|
||||
onClick={async () => {
|
||||
if (viewerContext?.exportActions?.saveAsCopy && currentView === 'viewer') {
|
||||
try {
|
||||
// Commit any pending redactions before exporting
|
||||
try { await viewerContext.redactionActions.commitAllPending(); } catch {}
|
||||
const pdfArrayBuffer = await viewerContext.exportActions.saveAsCopy();
|
||||
if (pdfArrayBuffer) {
|
||||
// Create new File object with flattened annotations
|
||||
@ -196,6 +206,8 @@ export default function ViewerAnnotationControls({ currentView, disabled = false
|
||||
|
||||
// Replace the original file with the saved version
|
||||
await fileActions.consumeFiles([currentFileId], [outputStirlingFile], [outputStub]);
|
||||
// Clear redaction dirty flag after save
|
||||
sessionStorage.removeItem('redaction:dirty');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
import { Stack } from '@mantine/core';
|
||||
import ButtonSelector from '@app/components/shared/ButtonSelector';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
export type ManualRedactionType = 'redactSelection' | 'marqueeRedact';
|
||||
|
||||
interface Props {
|
||||
value: ManualRedactionType; // reflect current plugin state if you like
|
||||
onChange?: (v: ManualRedactionType) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const LS_KEY = 'redaction:lastChoice';
|
||||
|
||||
export default function RedactManualControls({ value, onChange, disabled = false }: Props) {
|
||||
const [internal, setInternal] = useState<ManualRedactionType>(value);
|
||||
|
||||
useEffect(() => {
|
||||
if (value && value !== internal) setInternal(value);
|
||||
}, [value]); // keep UI in sync
|
||||
|
||||
const apply = (v: ManualRedactionType) => {
|
||||
localStorage.setItem(LS_KEY, v);
|
||||
(document as any)._embedpdf_redactMode = v;
|
||||
setInternal(v);
|
||||
onChange?.(v);
|
||||
// tell the bridge immediately
|
||||
const epdf = (window as any).__EMBEDPDF__?.bridges?.redaction;
|
||||
const apiBridge = epdf?.apiBridge;
|
||||
apiBridge?.setLastClicked?.(v);
|
||||
apiBridge?.setMode?.(v);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="var(--mantine-spacing-sm)">
|
||||
<ButtonSelector
|
||||
label="Manual Redaction"
|
||||
value={internal}
|
||||
onChange={apply}
|
||||
options={[
|
||||
{ value: 'redactSelection', label: 'Redact by Text' },
|
||||
{ value: 'marqueeRedact', label: 'Redact by Area' },
|
||||
]}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@ -24,7 +24,6 @@ export default function RedactModeSelector({ mode, onModeChange, disabled }: Red
|
||||
{
|
||||
value: 'manual' as const,
|
||||
label: t('redact.modeSelector.manual', 'Manual'),
|
||||
disabled: true, // Keep manual mode disabled until implemented
|
||||
},
|
||||
]}
|
||||
disabled={disabled}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMemo } from 'react';
|
||||
import { TooltipContent } from '@app/types/tips';
|
||||
|
||||
export const useRedactModeTips = (): TooltipContent => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return {
|
||||
return useMemo(() => ({
|
||||
header: {
|
||||
title: t("redact.tooltip.mode.header.title", "Redaction Method")
|
||||
},
|
||||
@ -18,13 +19,13 @@ export const useRedactModeTips = (): TooltipContent => {
|
||||
description: t("redact.tooltip.mode.manual.text", "Click and drag to manually select specific areas to redact. Gives you precise control over what gets redacted. (Coming soon)")
|
||||
}
|
||||
]
|
||||
};
|
||||
}), [t]);
|
||||
};
|
||||
|
||||
export const useRedactWordsTips = (): TooltipContent => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return {
|
||||
return useMemo(() => ({
|
||||
header: {
|
||||
title: t("redact.tooltip.words.header.title", "Words to Redact")
|
||||
},
|
||||
@ -43,13 +44,13 @@ export const useRedactWordsTips = (): TooltipContent => {
|
||||
description: t("redact.tooltip.words.examples.text", "Typical words to redact include: bank details, email addresses, or specific names.")
|
||||
}
|
||||
]
|
||||
};
|
||||
}), [t]);
|
||||
};
|
||||
|
||||
export const useRedactAdvancedTips = (): TooltipContent => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return {
|
||||
return useMemo(() => ({
|
||||
header: {
|
||||
title: t("redact.tooltip.advanced.header.title", "Advanced Redaction Settings")
|
||||
},
|
||||
@ -75,5 +76,25 @@ export const useRedactAdvancedTips = (): TooltipContent => {
|
||||
description: t("redact.tooltip.advanced.convert.text", "Converts the PDF to an image-based PDF after redaction. This ensures text behind redaction boxes is completely removed and unrecoverable.")
|
||||
}
|
||||
]
|
||||
};
|
||||
}), [t]);
|
||||
};
|
||||
|
||||
export const useRedactManualTips = (): TooltipContent => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useMemo(() => ({
|
||||
header: {
|
||||
title: t("redact.tooltip.manual.header.title", "Manual Redaction")
|
||||
},
|
||||
tips: [
|
||||
{
|
||||
title: t("redact.tooltip.manual.selectionByText.title", "Redact by Text"),
|
||||
description: t("redact.tooltip.manual.selectionByText.text", "Select and redact specific text in the document. Click and drag to select text, then apply the redaction to permanently remove it."),
|
||||
},
|
||||
{
|
||||
title: t("redact.tooltip.manual.selectionByArea.title", "Redact by Area"),
|
||||
description: t("redact.tooltip.manual.selectionByArea.text", "Draw a box to redact any area of the document, regardless of content. Click and drag to create a redaction box that covers the area you want to remove."),
|
||||
}
|
||||
]
|
||||
}), [t]);
|
||||
};
|
||||
|
||||
@ -35,7 +35,7 @@ const EmbedPdfViewerContent = ({
|
||||
const viewerRef = React.useRef<HTMLDivElement>(null);
|
||||
const [isViewerHovered, setIsViewerHovered] = React.useState(false);
|
||||
|
||||
const { isThumbnailSidebarVisible, toggleThumbnailSidebar, zoomActions, spreadActions, panActions: _panActions, rotationActions: _rotationActions, getScrollState, getZoomState, getSpreadState, getRotationState, isAnnotationMode, isAnnotationsVisible, exportActions } = useViewer();
|
||||
const { isThumbnailSidebarVisible, toggleThumbnailSidebar, zoomActions, spreadActions, panActions: _panActions, rotationActions: _rotationActions, getScrollState, getZoomState, getSpreadState, getRotationState, isAnnotationMode, isAnnotationsVisible, exportActions, getRedactionState, redactionActions } = useViewer();
|
||||
|
||||
// Register viewer right-rail buttons
|
||||
useViewerRightRailButtons();
|
||||
@ -184,6 +184,17 @@ const EmbedPdfViewerContent = ({
|
||||
}, [isViewerHovered]);
|
||||
|
||||
// Register checker for unsaved changes (annotations only for now)
|
||||
// Use refs for stable references to avoid re-registration
|
||||
const registerUnsavedChangesCheckerRef = useRef(registerUnsavedChangesChecker);
|
||||
const unregisterUnsavedChangesCheckerRef = useRef(unregisterUnsavedChangesChecker);
|
||||
const getRedactionStateRef = useRef(getRedactionState);
|
||||
|
||||
useEffect(() => {
|
||||
registerUnsavedChangesCheckerRef.current = registerUnsavedChangesChecker;
|
||||
unregisterUnsavedChangesCheckerRef.current = unregisterUnsavedChangesChecker;
|
||||
getRedactionStateRef.current = getRedactionState;
|
||||
}, [registerUnsavedChangesChecker, unregisterUnsavedChangesChecker, getRedactionState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (previewFile) {
|
||||
return;
|
||||
@ -192,21 +203,25 @@ const EmbedPdfViewerContent = ({
|
||||
const checkForChanges = () => {
|
||||
// Check for annotation changes via history
|
||||
const hasAnnotationChanges = historyApiRef.current?.canUndo() || false;
|
||||
// Check for redaction changes: any pending marks
|
||||
const redactionState = getRedactionStateRef.current();
|
||||
const hasRedactionChanges = redactionState.pendingCount > 0 || sessionStorage.getItem('redaction:dirty') === 'true';
|
||||
|
||||
console.log('[Viewer] Checking for unsaved changes:', {
|
||||
hasAnnotationChanges
|
||||
hasAnnotationChanges,
|
||||
hasRedactionChanges
|
||||
});
|
||||
return hasAnnotationChanges;
|
||||
return hasAnnotationChanges || hasRedactionChanges;
|
||||
};
|
||||
|
||||
console.log('[Viewer] Registering unsaved changes checker');
|
||||
registerUnsavedChangesChecker(checkForChanges);
|
||||
registerUnsavedChangesCheckerRef.current(checkForChanges);
|
||||
|
||||
return () => {
|
||||
console.log('[Viewer] Unregistering unsaved changes checker');
|
||||
unregisterUnsavedChangesChecker();
|
||||
unregisterUnsavedChangesCheckerRef.current();
|
||||
};
|
||||
}, [historyApiRef, previewFile, registerUnsavedChangesChecker, unregisterUnsavedChangesChecker]);
|
||||
}, [previewFile, historyApiRef]); // Removed function dependencies - use refs instead
|
||||
|
||||
// Apply changes - save annotations to new file version
|
||||
const applyChanges = useCallback(async () => {
|
||||
@ -215,6 +230,9 @@ const EmbedPdfViewerContent = ({
|
||||
try {
|
||||
console.log('[Viewer] Applying changes - exporting PDF with annotations');
|
||||
|
||||
// Commit any pending redactions before exporting
|
||||
try { await redactionActions.commitAllPending(); } catch (e) { /* ignore */ }
|
||||
|
||||
// Step 1: Export PDF with annotations using EmbedPDF
|
||||
const arrayBuffer = await exportActions.saveAsCopy();
|
||||
if (!arrayBuffer) {
|
||||
@ -238,6 +256,7 @@ const EmbedPdfViewerContent = ({
|
||||
await actions.consumeFiles(activeFileIds, stirlingFiles, stubs);
|
||||
|
||||
setHasUnsavedChanges(false);
|
||||
sessionStorage.removeItem('redaction:dirty');
|
||||
} catch (error) {
|
||||
console.error('Apply changes failed:', error);
|
||||
}
|
||||
@ -298,6 +317,17 @@ const EmbedPdfViewerContent = ({
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{/* Overlay root for redaction menus - rendered above all PDF pages */}
|
||||
<div
|
||||
id="pdf-overlay-root"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
pointerEvents: 'none',
|
||||
zIndex: 2147483647,
|
||||
overflow: 'visible'
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
74
frontend/src/core/components/viewer/HoverToSelectPending.tsx
Normal file
74
frontend/src/core/components/viewer/HoverToSelectPending.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useRedaction } from '@embedpdf/plugin-redaction/react';
|
||||
|
||||
type Props = {
|
||||
pageIndex: number;
|
||||
scale: number;
|
||||
getPageEl: () => HTMLElement | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* When hovering a pending box and the user is in marquee (area) mode,
|
||||
* temporarily pause drawing so clicks select the box.
|
||||
* When leaving the box (and nothing is selected), restore the last clicked mode.
|
||||
*/
|
||||
export default function HoverToSelectPending({ pageIndex, scale, getPageEl }: Props) {
|
||||
const { state, provides } = useRedaction();
|
||||
const pausedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const el = getPageEl();
|
||||
if (!el || !provides) return;
|
||||
|
||||
const restoreIfIdle = () => {
|
||||
if (!pausedRef.current) return;
|
||||
if (state.selected) return; // keep paused while a box is selected
|
||||
pausedRef.current = false;
|
||||
|
||||
const desired = (document as any)._embedpdf_redactMode as 'marqueeRedact' | 'redactSelection' | undefined;
|
||||
const anyProv = provides as any;
|
||||
if (desired && typeof anyProv.setActiveType === 'function') {
|
||||
anyProv.setActiveType(desired);
|
||||
} else if (desired === 'marqueeRedact' && state.activeType !== 'marqueeRedact') {
|
||||
provides.toggleMarqueeRedact?.();
|
||||
} else if (desired === 'redactSelection' && state.activeType !== 'redactSelection') {
|
||||
provides.toggleRedactSelection?.();
|
||||
}
|
||||
};
|
||||
|
||||
const onMove = (e: PointerEvent) => {
|
||||
const list: any[] = (state.pending as any)?.[pageIndex] || [];
|
||||
if (!list.length) { restoreIfIdle(); return; }
|
||||
|
||||
const r = el.getBoundingClientRect();
|
||||
const px = (e.clientX - r.left) / scale;
|
||||
const py = (e.clientY - r.top) / scale;
|
||||
|
||||
const hit = list.find((it) => {
|
||||
if (!it?.rect) return false;
|
||||
const { pos, size } = it.rect;
|
||||
if (!pos || !size) return false;
|
||||
return px >= pos.x && px <= pos.x + size.width && py >= pos.y && py <= pos.y + size.height;
|
||||
});
|
||||
|
||||
// Only pause auto-draw when we're in marquee mode and actually hovering a box
|
||||
if (hit && state.activeType === 'marqueeRedact' && !pausedRef.current) {
|
||||
pausedRef.current = true;
|
||||
provides.toggleMarqueeRedact?.(); // turn OFF drawing so clicks select the box
|
||||
} else if (!hit) {
|
||||
restoreIfIdle();
|
||||
}
|
||||
};
|
||||
|
||||
const onLeave = () => restoreIfIdle();
|
||||
|
||||
el.addEventListener('pointermove', onMove, { passive: true });
|
||||
el.addEventListener('pointerleave', onLeave, { passive: true });
|
||||
return () => {
|
||||
el.removeEventListener('pointermove', onMove);
|
||||
el.removeEventListener('pointerleave', onLeave);
|
||||
};
|
||||
}, [provides, state.pending, state.selected, state.activeType, pageIndex, scale]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPluginRegistration } from '@embedpdf/core';
|
||||
import { EmbedPDF } from '@embedpdf/core/react';
|
||||
import { usePdfiumEngine } from '@embedpdf/engines/react';
|
||||
@ -11,6 +11,7 @@ import { RenderPluginPackage } from '@embedpdf/plugin-render/react';
|
||||
import { ZoomPluginPackage } from '@embedpdf/plugin-zoom/react';
|
||||
import { InteractionManagerPluginPackage, PagePointerProvider, GlobalPointerProvider } from '@embedpdf/plugin-interaction-manager/react';
|
||||
import { SelectionLayer, SelectionPluginPackage } from '@embedpdf/plugin-selection/react';
|
||||
import { RedactionLayer, RedactionPluginPackage } from '@embedpdf/plugin-redaction/react';
|
||||
import { TilingLayer, TilingPluginPackage } from '@embedpdf/plugin-tiling/react';
|
||||
import { PanPluginPackage } from '@embedpdf/plugin-pan/react';
|
||||
import { SpreadPluginPackage, SpreadMode } from '@embedpdf/plugin-spread/react';
|
||||
@ -38,6 +39,9 @@ import { SignatureAPIBridge } from '@app/components/viewer/SignatureAPIBridge';
|
||||
import { HistoryAPIBridge } from '@app/components/viewer/HistoryAPIBridge';
|
||||
import type { SignatureAPI, HistoryAPI } from '@app/components/viewer/viewerTypes';
|
||||
import { ExportAPIBridge } from '@app/components/viewer/ExportAPIBridge';
|
||||
import { RedactionAPIBridge } from './RedactionAPIBridge';
|
||||
import RedactionSelectionMenu from '@app/components/viewer/RedactionSelectionMenu';
|
||||
import HoverToSelectPending from '@app/components/viewer/HoverToSelectPending';
|
||||
|
||||
interface LocalEmbedPDFProps {
|
||||
file?: File | Blob;
|
||||
@ -51,6 +55,7 @@ interface LocalEmbedPDFProps {
|
||||
export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatureAdded, signatureApiRef, historyApiRef }: LocalEmbedPDFProps) {
|
||||
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
||||
const [, setAnnotations] = useState<Array<{id: string, pageIndex: number, rect: any}>>([]);
|
||||
const pageElRef = useRef<Record<number, HTMLDivElement | null>>({});
|
||||
|
||||
// Convert File to URL if needed
|
||||
useEffect(() => {
|
||||
@ -96,6 +101,11 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
// Register selection plugin (depends on InteractionManager)
|
||||
createPluginRegistration(SelectionPluginPackage),
|
||||
|
||||
// Register redaction plugin (depends on InteractionManager and Selection)
|
||||
createPluginRegistration(RedactionPluginPackage, {
|
||||
drawBlackBoxes: true,
|
||||
}),
|
||||
|
||||
// Register history plugin for undo/redo (recommended for annotations)
|
||||
...(enableAnnotations ? [createPluginRegistration(HistoryPluginPackage)] : []),
|
||||
|
||||
@ -273,6 +283,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
{enableAnnotations && <SignatureAPIBridge ref={signatureApiRef} />}
|
||||
{enableAnnotations && <HistoryAPIBridge ref={historyApiRef} />}
|
||||
<ExportAPIBridge />
|
||||
<RedactionAPIBridge />
|
||||
<GlobalPointerProvider>
|
||||
<Viewport
|
||||
style={{
|
||||
@ -290,9 +301,9 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
}}
|
||||
>
|
||||
<Scroller
|
||||
renderPage={({ document, width, height, pageIndex, scale, rotation }) => {
|
||||
renderPage={({ width, height, pageIndex, scale, rotation }) => {
|
||||
return (
|
||||
<Rotate key={document?.id} pageSize={{ width, height }}>
|
||||
<Rotate key={`page-${pageIndex}`} pageSize={{ width, height }}>
|
||||
<PagePointerProvider pageIndex={pageIndex} pageWidth={width} pageHeight={height} scale={scale} rotation={rotation}>
|
||||
<div
|
||||
style={{
|
||||
@ -309,6 +320,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
onDragStart={(e) => e.preventDefault()}
|
||||
onDrop={(e) => e.preventDefault()}
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
ref={(node) => { pageElRef.current[pageIndex] = node; }}
|
||||
>
|
||||
{/* High-resolution tile layer */}
|
||||
<TilingLayer pageIndex={pageIndex} scale={scale} />
|
||||
@ -318,6 +330,18 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
||||
|
||||
{/* Selection layer for text interaction */}
|
||||
<SelectionLayer pageIndex={pageIndex} scale={scale} />
|
||||
{/* Redaction layer handles pending marks and UI */}
|
||||
<RedactionLayer
|
||||
pageIndex={pageIndex}
|
||||
scale={scale}
|
||||
rotation={rotation}
|
||||
selectionMenu={(props) => <RedactionSelectionMenu {...props} />}
|
||||
/>
|
||||
<HoverToSelectPending
|
||||
pageIndex={pageIndex}
|
||||
scale={scale}
|
||||
getPageEl={() => pageElRef.current[pageIndex] ?? null}
|
||||
/>
|
||||
{/* Annotation layer for signatures (only when enabled) */}
|
||||
{enableAnnotations && (
|
||||
<AnnotationLayer
|
||||
|
||||
@ -7,7 +7,7 @@ import { useViewer } from '@app/contexts/ViewerContext';
|
||||
*/
|
||||
export function PanAPIBridge() {
|
||||
const { provides: pan, isPanning } = usePan();
|
||||
const { registerBridge } = useViewer();
|
||||
const { registerBridge, triggerToolModeUpdate } = useViewer();
|
||||
|
||||
// Store state locally
|
||||
const [_localState, setLocalState] = useState({
|
||||
@ -38,8 +38,10 @@ export function PanAPIBridge() {
|
||||
makePanDefault: () => pan.makePanDefault(),
|
||||
}
|
||||
});
|
||||
// Notify listeners whenever pan state changes
|
||||
triggerToolModeUpdate();
|
||||
}
|
||||
}, [pan, isPanning]);
|
||||
}, [pan, isPanning, triggerToolModeUpdate, registerBridge]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
149
frontend/src/core/components/viewer/RedactionAPIBridge.tsx
Normal file
149
frontend/src/core/components/viewer/RedactionAPIBridge.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useRedaction } from '@embedpdf/plugin-redaction/react';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
|
||||
/**
|
||||
* Behavior contract:
|
||||
* - The *only* authority for the desired mode is the last toolbar button the user clicked.
|
||||
* - We persist that in localStorage and mirror it on document for quick reads.
|
||||
* - We never invert the user's choice during "create/pending/select" frames.
|
||||
* - After the plugin finishes its internal flip (end of draw, commit, etc),
|
||||
* we nudge it back to the user's choice using a deferred restore (microtask + 2x rAF).
|
||||
*/
|
||||
|
||||
const LS_KEY = 'redaction:lastChoice'; // 'redactSelection' | 'marqueeRedact'
|
||||
type Mode = 'redactSelection' | 'marqueeRedact';
|
||||
|
||||
export function RedactionAPIBridge() {
|
||||
const { state, provides } = useRedaction();
|
||||
const { registerBridge } = useViewer();
|
||||
|
||||
// live state ref
|
||||
const stateRef = useRef(state);
|
||||
useEffect(() => { stateRef.current = state; }, [state]);
|
||||
|
||||
// desired mode (last clicked)
|
||||
const lastChoiceRef = useRef<Mode | null>(
|
||||
(localStorage.getItem(LS_KEY) as Mode | null) ?? null
|
||||
);
|
||||
|
||||
const setLastChoice = useCallback((mode: Mode) => {
|
||||
lastChoiceRef.current = mode;
|
||||
(document as any)._embedpdf_redactMode = mode; // local mirror only
|
||||
}, []);
|
||||
|
||||
// bridge surface for other UI
|
||||
const bridgeRef = useRef<{ state: any; api: any } | null>(null);
|
||||
useEffect(() => {
|
||||
if (!provides) return;
|
||||
const bridge = {
|
||||
state: {
|
||||
isRedacting: stateRef.current.isRedacting,
|
||||
activeType: stateRef.current.activeType,
|
||||
pendingCount: stateRef.current.pendingCount,
|
||||
selected: stateRef.current.selected,
|
||||
},
|
||||
api: provides,
|
||||
};
|
||||
bridgeRef.current = bridge;
|
||||
registerBridge('redaction', bridge);
|
||||
}, [registerBridge, provides]);
|
||||
|
||||
// keep bridge.state fresh
|
||||
const prev = useRef(bridgeRef.current?.state);
|
||||
useEffect(() => {
|
||||
if (!bridgeRef.current) return;
|
||||
const s = {
|
||||
isRedacting: state.isRedacting,
|
||||
activeType: state.activeType,
|
||||
pendingCount: state.pendingCount,
|
||||
selected: state.selected,
|
||||
};
|
||||
if (!prev.current ||
|
||||
prev.current.isRedacting !== s.isRedacting ||
|
||||
prev.current.activeType !== s.activeType ||
|
||||
prev.current.pendingCount !== s.pendingCount ||
|
||||
prev.current.selected !== s.selected) {
|
||||
bridgeRef.current.state = s;
|
||||
prev.current = s;
|
||||
}
|
||||
}, [state.isRedacting, state.activeType, state.pendingCount, state.selected]);
|
||||
|
||||
// idempotent "set" using toggles (there is no official setter)
|
||||
const setMode = useCallback((target: Mode | null) => {
|
||||
if (!provides || !target) return;
|
||||
if (stateRef.current.activeType === target) return;
|
||||
|
||||
const anyProv = provides as any;
|
||||
if (typeof anyProv.setActiveType === 'function') {
|
||||
anyProv.setActiveType(target);
|
||||
return;
|
||||
}
|
||||
target === 'marqueeRedact'
|
||||
? provides.toggleMarqueeRedact?.()
|
||||
: provides.toggleRedactSelection?.();
|
||||
}, [provides]);
|
||||
|
||||
// public mini-API for your toolbar
|
||||
useEffect(() => {
|
||||
if (!bridgeRef.current) return;
|
||||
(bridgeRef.current as any).apiBridge = {
|
||||
setLastClicked: (mode: Mode) => setLastChoice(mode),
|
||||
setMode,
|
||||
getLastClicked: () => lastChoiceRef.current,
|
||||
};
|
||||
}, [setMode, setLastChoice]);
|
||||
|
||||
// defer helper: run after the plugin's own state churn
|
||||
const restoringRef = useRef(false);
|
||||
const deferRestore = useCallback(() => {
|
||||
if (restoringRef.current) return;
|
||||
restoringRef.current = true;
|
||||
// microtask + double rAF to reliably run *after* the plugin's flip
|
||||
Promise.resolve().then(() => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
const desired = lastChoiceRef.current;
|
||||
const s = stateRef.current;
|
||||
if (desired && s.activeType !== desired) {
|
||||
setMode(desired); // idempotent
|
||||
}
|
||||
setTimeout(() => { restoringRef.current = false; }, 60);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, [setMode]);
|
||||
|
||||
// remember user-initiated switches (when plugin reports a mode)
|
||||
useEffect(() => {
|
||||
if (!restoringRef.current) return;
|
||||
if (state.activeType === lastChoiceRef.current) {
|
||||
restoringRef.current = false;
|
||||
}
|
||||
}, [state.activeType]);
|
||||
|
||||
// steer back to user's choice after *any* redaction event
|
||||
useEffect(() => {
|
||||
if (!provides) return;
|
||||
const off = provides.onRedactionEvent?.((_evt: any) => {
|
||||
// Mark dirty for your nav guard if you use one
|
||||
try { sessionStorage.setItem('redaction:dirty', 'true'); } catch {}
|
||||
// Always defer a restore — hover helper will temporarily pause drawing when needed
|
||||
deferRestore();
|
||||
});
|
||||
return () => { off && off(); };
|
||||
}, [provides, deferRestore]);
|
||||
|
||||
// seed once on tool open
|
||||
useEffect(() => {
|
||||
if (!provides) return;
|
||||
const remembered = (localStorage.getItem(LS_KEY) as Mode | null) ?? null;
|
||||
if (remembered) {
|
||||
setLastChoice(remembered);
|
||||
// set immediately so first interaction is correct
|
||||
setMode(remembered);
|
||||
}
|
||||
}, [provides, setMode, setLastChoice]);
|
||||
|
||||
return null;
|
||||
}
|
||||
201
frontend/src/core/components/viewer/RedactionSelectionMenu.tsx
Normal file
201
frontend/src/core/components/viewer/RedactionSelectionMenu.tsx
Normal file
@ -0,0 +1,201 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { SelectionMenuProps, useRedaction } from '@embedpdf/plugin-redaction/react';
|
||||
|
||||
/**
|
||||
* Small inline menu rendered by RedactionLayer when a pending mark is selected.
|
||||
* Shows Accept (commit) and Remove controls.
|
||||
* Uses a portal to render at document body level to ensure it's always above PDF pages.
|
||||
*/
|
||||
export default function RedactionSelectionMenu({ item, selected, menuWrapperProps }: SelectionMenuProps) {
|
||||
const { provides, state } = useRedaction();
|
||||
const [position, setPosition] = useState<{ top: number; left: number } | null>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// Track the current mode before committing to ensure it's preserved
|
||||
const modeBeforeCommitRef = useRef<'redactSelection' | 'marqueeRedact' | null>(null);
|
||||
|
||||
// Store current mode when menu is shown
|
||||
useEffect(() => {
|
||||
if (selected && state.activeType) {
|
||||
modeBeforeCommitRef.current = state.activeType as 'redactSelection' | 'marqueeRedact' | null;
|
||||
}
|
||||
}, [selected, state.activeType]);
|
||||
|
||||
// Extract ref from menuWrapperProps - must happen before any conditional returns
|
||||
const { ref: wrapperPropsRef, ...restWrapperProps } = menuWrapperProps || {};
|
||||
|
||||
// Merge refs - must be called unconditionally
|
||||
const mergedRef = React.useCallback((node: HTMLDivElement | null) => {
|
||||
wrapperRef.current = node;
|
||||
if (typeof wrapperPropsRef === 'function') {
|
||||
wrapperPropsRef(node);
|
||||
} else if (wrapperPropsRef) {
|
||||
(wrapperPropsRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
||||
}
|
||||
}, [wrapperPropsRef]);
|
||||
|
||||
// Get overlay - must be called unconditionally
|
||||
const overlay = typeof document !== 'undefined' ? document.getElementById('pdf-overlay-root') : null;
|
||||
|
||||
// All hooks must be called before any conditional returns
|
||||
useEffect(() => {
|
||||
if (!selected) {
|
||||
setPosition(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatePosition = () => {
|
||||
// Get the overlay root
|
||||
const overlayEl = document.getElementById('pdf-overlay-root');
|
||||
if (!overlayEl || !wrapperRef.current) {
|
||||
setPosition(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the wrapper's bounding rect in viewport coordinates
|
||||
const wrapperRect = wrapperRef.current.getBoundingClientRect();
|
||||
const overlayRect = overlayEl.getBoundingClientRect();
|
||||
|
||||
// Calculate position relative to overlay
|
||||
const menuHeight = item?.rect?.size?.height || 0;
|
||||
const top = wrapperRect.top - overlayRect.top + menuHeight + 10;
|
||||
const left = wrapperRect.left - overlayRect.left;
|
||||
|
||||
setPosition({ top, left });
|
||||
};
|
||||
|
||||
// Initial position calculation
|
||||
updatePosition();
|
||||
|
||||
// Update on scroll, resize, and zoom changes
|
||||
const handleUpdate = () => {
|
||||
requestAnimationFrame(updatePosition);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleUpdate, true);
|
||||
window.addEventListener('resize', handleUpdate);
|
||||
|
||||
// Use a mutation observer to catch DOM changes that might affect position
|
||||
const observer = new MutationObserver(handleUpdate);
|
||||
if (wrapperRef.current) {
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
// Also use intersection observer as fallback
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
() => handleUpdate(),
|
||||
{ threshold: 0, root: null }
|
||||
);
|
||||
if (wrapperRef.current) {
|
||||
intersectionObserver.observe(wrapperRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleUpdate, true);
|
||||
window.removeEventListener('resize', handleUpdate);
|
||||
observer.disconnect();
|
||||
intersectionObserver.disconnect();
|
||||
};
|
||||
}, [selected, item?.rect?.size?.height]);
|
||||
|
||||
// Now we can conditionally render - but all hooks have been called
|
||||
if (!selected) {
|
||||
return (
|
||||
<div
|
||||
ref={mergedRef}
|
||||
{...restWrapperProps}
|
||||
style={{
|
||||
...restWrapperProps?.style,
|
||||
position: 'relative',
|
||||
pointerEvents: 'none',
|
||||
display: 'none'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const menuContent = (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: position ? `${position.top}px` : 0,
|
||||
left: position ? `${position.left}px` : 0,
|
||||
pointerEvents: 'auto',
|
||||
display: 'flex',
|
||||
gap: 12,
|
||||
background: 'rgba(255,255,255,0.98)',
|
||||
border: '1px solid rgba(15, 23, 42, 0.08)',
|
||||
borderRadius: 12,
|
||||
padding: '8px 10px',
|
||||
boxShadow: '0 6px 14px rgba(0,0,0,0.12)',
|
||||
zIndex: 2147483647
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={async () => {
|
||||
// Store the current mode before committing to ensure it's preserved
|
||||
const currentMode = state.activeType as 'redactSelection' | 'marqueeRedact' | null;
|
||||
if (currentMode) {
|
||||
modeBeforeCommitRef.current = currentMode;
|
||||
// Update session storage so bridge can restore it
|
||||
sessionStorage.setItem('redaction:lastManualType', currentMode);
|
||||
}
|
||||
|
||||
// Commit the redaction
|
||||
// The RedactionAPIBridge will handle mode restoration after commit
|
||||
if (provides?.commitPending) {
|
||||
await provides.commitPending(item.page, item.id);
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
padding: '10px 18px',
|
||||
borderRadius: 12,
|
||||
border: '1px solid #ef4444',
|
||||
background: '#ef4444',
|
||||
color: 'white',
|
||||
fontWeight: 600,
|
||||
fontSize: 16,
|
||||
lineHeight: 1.0,
|
||||
boxShadow: '0 2px 4px rgba(239, 68, 68, 0.4)',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
<button
|
||||
onClick={() => provides?.removePending?.(item.page, item.id)}
|
||||
style={{
|
||||
padding: '10px 18px',
|
||||
borderRadius: 12,
|
||||
border: '1px solid rgba(15, 23, 42, 0.12)',
|
||||
background: 'rgba(241, 245, 249, 0.8)',
|
||||
color: '#334155',
|
||||
fontWeight: 600,
|
||||
fontSize: 16,
|
||||
lineHeight: 1.0,
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={mergedRef}
|
||||
{...restWrapperProps}
|
||||
style={{
|
||||
...restWrapperProps?.style,
|
||||
position: 'relative',
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
{overlay && position && createPortal(menuContent, overlay)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMemo, useState, useEffect, useRef } from 'react';
|
||||
import { ActionIcon, Popover } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useViewer } from '@app/contexts/ViewerContext';
|
||||
@ -7,18 +7,57 @@ import LocalIcon from '@app/components/shared/LocalIcon';
|
||||
import { Tooltip } from '@app/components/shared/Tooltip';
|
||||
import { SearchInterface } from '@app/components/viewer/SearchInterface';
|
||||
import ViewerAnnotationControls from '@app/components/shared/rightRail/ViewerAnnotationControls';
|
||||
import { useNavigationActions, useNavigationState } from '@app/contexts/NavigationContext';
|
||||
import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext';
|
||||
|
||||
export function useViewerRightRailButtons() {
|
||||
const { t } = useTranslation();
|
||||
const viewer = useViewer();
|
||||
const [isPanning, setIsPanning] = useState<boolean>(() => viewer.getPanState()?.isPanning ?? false);
|
||||
const { actions: navActions } = useNavigationActions();
|
||||
const { handleToolSelect } = useToolWorkflow();
|
||||
const { workbench, selectedTool } = useNavigationState();
|
||||
|
||||
// Lift i18n labels out of memo for clarity
|
||||
const searchLabel = t('rightRail.search', 'Search PDF');
|
||||
const panLabel = t('rightRail.panMode', 'Pan Mode');
|
||||
const rotateLeftLabel = t('rightRail.rotateLeft', 'Rotate Left');
|
||||
const rotateRightLabel = t('rightRail.rotateRight', 'Rotate Right');
|
||||
const sidebarLabel = t('rightRail.toggleSidebar', 'Toggle Sidebar');
|
||||
// Extract stable references to viewer methods to avoid re-renders from context changes
|
||||
const getToolModeRef = useRef(viewer.getToolMode);
|
||||
const registerToolModeListenerRef = useRef(viewer.registerToolModeListener);
|
||||
const unregisterToolModeListenerRef = useRef(viewer.unregisterToolModeListener);
|
||||
const setAnnotationModeRef = useRef(viewer.setAnnotationMode);
|
||||
const redactionActionsRef = useRef(viewer.redactionActions);
|
||||
const panActionsRef = useRef(viewer.panActions);
|
||||
const rotationActionsRef = useRef(viewer.rotationActions);
|
||||
const toggleThumbnailSidebarRef = useRef(viewer.toggleThumbnailSidebar);
|
||||
const triggerToolModeUpdateRef = useRef(viewer.triggerToolModeUpdate);
|
||||
|
||||
// Update refs when viewer context changes (but don't cause re-renders)
|
||||
useEffect(() => {
|
||||
getToolModeRef.current = viewer.getToolMode;
|
||||
registerToolModeListenerRef.current = viewer.registerToolModeListener;
|
||||
unregisterToolModeListenerRef.current = viewer.unregisterToolModeListener;
|
||||
setAnnotationModeRef.current = viewer.setAnnotationMode;
|
||||
redactionActionsRef.current = viewer.redactionActions;
|
||||
panActionsRef.current = viewer.panActions;
|
||||
rotationActionsRef.current = viewer.rotationActions;
|
||||
toggleThumbnailSidebarRef.current = viewer.toggleThumbnailSidebar;
|
||||
triggerToolModeUpdateRef.current = viewer.triggerToolModeUpdate;
|
||||
}, [viewer]);
|
||||
|
||||
// Single source of truth for active tool mode (none | pan | redact | draw)
|
||||
const [activeMode, setActiveMode] = useState<'none' | 'pan' | 'redact' | 'draw'>(() => getToolModeRef.current());
|
||||
useEffect(() => {
|
||||
registerToolModeListenerRef.current((mode) => setActiveMode(mode));
|
||||
return () => unregisterToolModeListenerRef.current();
|
||||
}, []); // Empty deps - refs are stable
|
||||
|
||||
// Memoize i18n labels to prevent re-renders
|
||||
const searchLabel = useMemo(() => t('rightRail.search', 'Search PDF'), [t]);
|
||||
const panLabel = useMemo(() => t('rightRail.panMode', 'Pan Mode'), [t]);
|
||||
const rotateLeftLabel = useMemo(() => t('rightRail.rotateLeft', 'Rotate Left'), [t]);
|
||||
const rotateRightLabel = useMemo(() => t('rightRail.rotateRight', 'Rotate Right'), [t]);
|
||||
const sidebarLabel = useMemo(() => t('rightRail.toggleSidebar', 'Toggle Sidebar'), [t]);
|
||||
const redactLabel = useMemo(() => t('rightRail.redact', 'Redact'), [t]);
|
||||
|
||||
const isPanning = activeMode === 'pan';
|
||||
const isRedacting = activeMode === 'redact';
|
||||
|
||||
const viewerButtons = useMemo<RightRailButtonWithAction[]>(() => {
|
||||
return [
|
||||
@ -67,8 +106,24 @@ export function useViewerRightRailButtons() {
|
||||
radius="md"
|
||||
className="right-rail-icon"
|
||||
onClick={() => {
|
||||
viewer.panActions.togglePan();
|
||||
setIsPanning(prev => !prev);
|
||||
// Entering pan should disable draw and redaction; leaving pan just toggles off
|
||||
if (!isPanning) {
|
||||
try { setAnnotationModeRef.current(false); } catch {}
|
||||
try { redactionActionsRef.current.deactivate(); } catch {}
|
||||
const enable = () => {
|
||||
try { panActionsRef.current.enablePan(); } catch {}
|
||||
try { triggerToolModeUpdateRef.current(); } catch {}
|
||||
};
|
||||
if (typeof window !== 'undefined' && 'requestAnimationFrame' in window) {
|
||||
requestAnimationFrame(() => setTimeout(enable, 0));
|
||||
} else {
|
||||
setTimeout(enable, 0);
|
||||
}
|
||||
} else {
|
||||
try { panActionsRef.current.disablePan(); } catch {}
|
||||
try { triggerToolModeUpdateRef.current(); } catch {}
|
||||
}
|
||||
// activeMode will update via listener
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
@ -85,7 +140,7 @@ export function useViewerRightRailButtons() {
|
||||
section: 'top' as const,
|
||||
order: 30,
|
||||
onClick: () => {
|
||||
viewer.rotationActions.rotateBackward();
|
||||
rotationActionsRef.current.rotateBackward();
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -96,7 +151,7 @@ export function useViewerRightRailButtons() {
|
||||
section: 'top' as const,
|
||||
order: 40,
|
||||
onClick: () => {
|
||||
viewer.rotationActions.rotateForward();
|
||||
rotationActionsRef.current.rotateForward();
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -107,9 +162,56 @@ export function useViewerRightRailButtons() {
|
||||
section: 'top' as const,
|
||||
order: 50,
|
||||
onClick: () => {
|
||||
viewer.toggleThumbnailSidebar();
|
||||
toggleThumbnailSidebarRef.current();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'viewer-redaction',
|
||||
tooltip: redactLabel,
|
||||
ariaLabel: redactLabel,
|
||||
section: 'top' as const,
|
||||
order: 55,
|
||||
render: ({ disabled }) => (
|
||||
<Tooltip content={redactLabel} position="left" offset={12} arrow portalTarget={document.body}>
|
||||
<ActionIcon
|
||||
variant={isRedacting ? 'filled' : 'subtle'}
|
||||
color={isRedacting ? 'blue' : undefined}
|
||||
radius="md"
|
||||
className="right-rail-icon"
|
||||
onClick={() => {
|
||||
// Ensure the left sidebar opens the Redact tool in viewer with manual mode
|
||||
sessionStorage.setItem('redaction:init', 'manual');
|
||||
// Navigate to viewer with the redact tool if we're not already there
|
||||
if (workbench !== 'viewer' || selectedTool !== 'redact') {
|
||||
handleToolSelect('redact' as any);
|
||||
}
|
||||
// Disable draw and pan when activating redaction
|
||||
try { setAnnotationModeRef.current(false); } catch {}
|
||||
try { panActionsRef.current.disablePan(); } catch {}
|
||||
// Activate last used manual mode inside viewer.
|
||||
// Defer to next frame to allow annotation plugin to fully release interaction.
|
||||
const last = (sessionStorage.getItem('redaction:lastManualType') as 'redactSelection' | 'marqueeRedact' | null) || 'redactSelection';
|
||||
const activate = () => {
|
||||
if (last === 'marqueeRedact') {
|
||||
redactionActionsRef.current.activateArea();
|
||||
} else {
|
||||
redactionActionsRef.current.activateText();
|
||||
}
|
||||
try { triggerToolModeUpdateRef.current(); } catch {}
|
||||
};
|
||||
if (typeof window !== 'undefined' && 'requestAnimationFrame' in window) {
|
||||
requestAnimationFrame(() => setTimeout(activate, 0));
|
||||
} else {
|
||||
setTimeout(activate, 0);
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<LocalIcon icon="visibility-off-rounded" width="1.5rem" height="1.5rem" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'viewer-annotation-controls',
|
||||
section: 'top' as const,
|
||||
@ -119,7 +221,7 @@ export function useViewerRightRailButtons() {
|
||||
)
|
||||
}
|
||||
];
|
||||
}, [t, viewer, isPanning, searchLabel, panLabel, rotateLeftLabel, rotateRightLabel, sidebarLabel]);
|
||||
}, [activeMode, searchLabel, panLabel, rotateLeftLabel, rotateRightLabel, sidebarLabel, redactLabel, handleToolSelect, workbench, selectedTool, isPanning, isRedacting]);
|
||||
|
||||
useRightRailButtons(viewerButtons);
|
||||
}
|
||||
|
||||
@ -57,6 +57,19 @@ interface ExportAPIWrapper {
|
||||
saveAsCopy: () => { toPromise: () => Promise<ArrayBuffer> };
|
||||
}
|
||||
|
||||
// Redaction bridge wrappers
|
||||
interface RedactionAPIWrapper {
|
||||
toggleRedactSelection: () => void;
|
||||
toggleMarqueeRedact: () => void;
|
||||
clearPending: () => void;
|
||||
commitAllPending: () => { toPromise: () => Promise<void> } | Promise<void> | void;
|
||||
}
|
||||
|
||||
interface RedactionState {
|
||||
isRedacting: boolean;
|
||||
activeType: 'redactSelection' | 'marqueeRedact' | null;
|
||||
pendingCount: number;
|
||||
}
|
||||
|
||||
// State interfaces - represent the shape of data from each bridge
|
||||
interface ScrollState {
|
||||
@ -103,6 +116,8 @@ interface ExportState {
|
||||
canExport: boolean;
|
||||
}
|
||||
|
||||
type ToolMode = 'none' | 'pan' | 'redact' | 'draw';
|
||||
|
||||
// Bridge registration interface - bridges register with state and API
|
||||
interface BridgeRef<TState = unknown, TApi = unknown> {
|
||||
state: TState;
|
||||
@ -146,6 +161,8 @@ interface ViewerContextType {
|
||||
getSearchState: () => SearchState;
|
||||
getThumbnailAPI: () => ThumbnailAPIWrapper | null;
|
||||
getExportState: () => ExportState;
|
||||
getToolMode: () => ToolMode;
|
||||
getRedactionState: () => RedactionState;
|
||||
|
||||
// Immediate update callbacks
|
||||
registerImmediateZoomUpdate: (callback: (percent: number) => void) => void;
|
||||
@ -208,6 +225,20 @@ interface ViewerContextType {
|
||||
saveAsCopy: () => Promise<ArrayBuffer | null>;
|
||||
};
|
||||
|
||||
redactionActions: {
|
||||
activateText: () => void;
|
||||
activateArea: () => void;
|
||||
deactivate: () => void;
|
||||
commitAllPending: () => Promise<void>;
|
||||
clearPending: () => void;
|
||||
isActive: () => boolean;
|
||||
};
|
||||
|
||||
// Live updates for right-rail highlighting
|
||||
registerToolModeListener: (callback: (mode: ToolMode) => void) => void;
|
||||
unregisterToolModeListener: () => void;
|
||||
triggerToolModeUpdate: () => void;
|
||||
|
||||
// Bridge registration - internal use by bridges
|
||||
registerBridge: (type: string, ref: BridgeRef) => void;
|
||||
}
|
||||
@ -239,8 +270,11 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
rotation: null as BridgeRef<RotationState, RotationAPIWrapper> | null,
|
||||
thumbnail: null as BridgeRef<unknown, ThumbnailAPIWrapper> | null,
|
||||
export: null as BridgeRef<ExportState, ExportAPIWrapper> | null,
|
||||
redaction: null as BridgeRef<RedactionState, RedactionAPIWrapper> | null,
|
||||
});
|
||||
|
||||
const toolModeListenerRef = useRef<((mode: ToolMode) => void) | null>(null);
|
||||
|
||||
// Immediate zoom callback for responsive display updates
|
||||
const immediateZoomUpdateCallback = useRef<((percent: number) => void) | null>(null);
|
||||
|
||||
@ -277,6 +311,9 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
case 'export':
|
||||
bridgeRefs.current.export = ref as BridgeRef<ExportState, ExportAPIWrapper>;
|
||||
break;
|
||||
case 'redaction':
|
||||
bridgeRefs.current.redaction = ref as BridgeRef<RedactionState, RedactionAPIWrapper>;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@ -290,10 +327,14 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
|
||||
const setAnnotationMode = (enabled: boolean) => {
|
||||
setIsAnnotationModeState(enabled);
|
||||
// Notify listeners when draw mode changes
|
||||
triggerToolModeUpdate();
|
||||
};
|
||||
|
||||
const toggleAnnotationMode = () => {
|
||||
setIsAnnotationModeState(prev => !prev);
|
||||
// Notify listeners when draw mode changes
|
||||
setTimeout(() => triggerToolModeUpdate(), 0);
|
||||
};
|
||||
|
||||
// State getters - read from bridge refs
|
||||
@ -333,6 +374,25 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
return bridgeRefs.current.export?.state || { canExport: false };
|
||||
};
|
||||
|
||||
const getToolMode = (): ToolMode => {
|
||||
if (isAnnotationMode) return 'draw';
|
||||
const redactionActive = bridgeRefs.current.redaction?.state?.isRedacting;
|
||||
if (redactionActive) return 'redact';
|
||||
const panActive = bridgeRefs.current.pan?.state?.isPanning;
|
||||
if (panActive) return 'pan';
|
||||
return 'none';
|
||||
};
|
||||
|
||||
const getRedactionState = (): RedactionState => {
|
||||
return (
|
||||
bridgeRefs.current.redaction?.state || {
|
||||
isRedacting: false,
|
||||
activeType: null,
|
||||
pendingCount: 0,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Action handlers - call APIs directly
|
||||
const scrollActions = {
|
||||
scrollToPage: (page: number) => {
|
||||
@ -550,6 +610,66 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Track redaction dirty state (any commit or pending marks)
|
||||
const redactionActions = {
|
||||
activateText: () => {
|
||||
const api = bridgeRefs.current.redaction?.api;
|
||||
const state = bridgeRefs.current.redaction?.state;
|
||||
if (!api) return;
|
||||
if (state?.activeType === 'redactSelection') return; // already active
|
||||
// If other mode active, turn it off first
|
||||
if (state?.activeType === 'marqueeRedact' && api.toggleMarqueeRedact) api.toggleMarqueeRedact();
|
||||
if (api.toggleRedactSelection) api.toggleRedactSelection();
|
||||
},
|
||||
activateArea: () => {
|
||||
const api = bridgeRefs.current.redaction?.api;
|
||||
const state = bridgeRefs.current.redaction?.state;
|
||||
if (!api) return;
|
||||
if (state?.activeType === 'marqueeRedact') return; // already active
|
||||
if (state?.activeType === 'redactSelection' && api.toggleRedactSelection) api.toggleRedactSelection();
|
||||
if (api.toggleMarqueeRedact) api.toggleMarqueeRedact();
|
||||
},
|
||||
deactivate: () => {
|
||||
const state = bridgeRefs.current.redaction?.state;
|
||||
const api = bridgeRefs.current.redaction?.api;
|
||||
if (!state || !api) return;
|
||||
// If text is active, toggling text will deactivate; same for area
|
||||
if (state.activeType === 'redactSelection' && api.toggleRedactSelection) {
|
||||
api.toggleRedactSelection();
|
||||
} else if (state.activeType === 'marqueeRedact' && api.toggleMarqueeRedact) {
|
||||
api.toggleMarqueeRedact();
|
||||
}
|
||||
},
|
||||
commitAllPending: async () => {
|
||||
const api = bridgeRefs.current.redaction?.api;
|
||||
if (!api?.commitAllPending) return;
|
||||
const result = api.commitAllPending();
|
||||
if (result && typeof (result as any).toPromise === 'function') {
|
||||
await (result as any).toPromise();
|
||||
}
|
||||
},
|
||||
clearPending: () => {
|
||||
const api = bridgeRefs.current.redaction?.api;
|
||||
if (api?.clearPending) api.clearPending();
|
||||
},
|
||||
isActive: () => {
|
||||
return Boolean(bridgeRefs.current.redaction?.state?.isRedacting);
|
||||
},
|
||||
};
|
||||
|
||||
const registerToolModeListener = (callback: (mode: ToolMode) => void) => {
|
||||
toolModeListenerRef.current = callback;
|
||||
};
|
||||
|
||||
const unregisterToolModeListener = () => {
|
||||
toolModeListenerRef.current = null;
|
||||
};
|
||||
|
||||
const triggerToolModeUpdate = () => {
|
||||
const cb = toolModeListenerRef.current;
|
||||
if (cb) cb(getToolMode());
|
||||
};
|
||||
|
||||
const registerImmediateZoomUpdate = (callback: (percent: number) => void) => {
|
||||
immediateZoomUpdateCallback.current = callback;
|
||||
};
|
||||
@ -596,6 +716,8 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
getSearchState,
|
||||
getThumbnailAPI,
|
||||
getExportState,
|
||||
getToolMode,
|
||||
getRedactionState,
|
||||
|
||||
// Immediate updates
|
||||
registerImmediateZoomUpdate,
|
||||
@ -612,6 +734,10 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
|
||||
rotationActions,
|
||||
searchActions,
|
||||
exportActions,
|
||||
redactionActions,
|
||||
registerToolModeListener,
|
||||
unregisterToolModeListener,
|
||||
triggerToolModeUpdate,
|
||||
|
||||
// Bridge registration
|
||||
registerBridge,
|
||||
|
||||
@ -17,8 +17,9 @@ export const buildRedactFormData = (parameters: RedactParameters, file: File): F
|
||||
formData.append("customPadding", parameters.customPadding.toString());
|
||||
formData.append("convertPDFToImage", parameters.convertPDFToImage.toString());
|
||||
} else {
|
||||
// Manual mode parameters would go here when implemented
|
||||
throw new Error('Manual redaction not yet implemented');
|
||||
// Manual mode is handled interactively in the viewer via EmbedPDF redaction plugin.
|
||||
// The sidebar tool never posts a request in manual mode, so return minimal form data.
|
||||
formData.append('mode', 'manual');
|
||||
}
|
||||
|
||||
return formData;
|
||||
@ -33,8 +34,8 @@ export const redactOperationConfig = {
|
||||
if (parameters.mode === 'automatic') {
|
||||
return '/api/v1/security/auto-redact';
|
||||
} else {
|
||||
// Manual redaction endpoint would go here when implemented
|
||||
throw new Error('Manual redaction not yet implemented');
|
||||
// Manual mode does not call backend; return a placeholder that will not be invoked.
|
||||
return '/noop/manual-redaction';
|
||||
}
|
||||
},
|
||||
defaultParameters,
|
||||
|
||||
@ -34,15 +34,15 @@ export const useRedactParameters = (): RedactParametersHook => {
|
||||
if (params.mode === 'automatic') {
|
||||
return '/api/v1/security/auto-redact';
|
||||
}
|
||||
// Manual redaction endpoint would go here when implemented
|
||||
throw new Error('Manual redaction not yet implemented');
|
||||
// Manual mode is handled in the viewer; this endpoint will not be called
|
||||
return '/noop/manual-redaction';
|
||||
},
|
||||
validateFn: (params) => {
|
||||
if (params.mode === 'automatic') {
|
||||
return params.wordsToRedact.length > 0 && params.wordsToRedact.some(word => word.trim().length > 0);
|
||||
}
|
||||
// Manual mode validation would go here when implemented
|
||||
return false;
|
||||
// For manual, validation is not needed for network calls; allow switching
|
||||
return true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect, useRef, useMemo, useCallback } from "react";
|
||||
import { createToolFlow } from "@app/components/tools/shared/createToolFlow";
|
||||
import RedactModeSelector from "@app/components/tools/redact/RedactModeSelector";
|
||||
import { useRedactParameters } from "@app/hooks/tools/redact/useRedactParameters";
|
||||
import { useRedactOperation } from "@app/hooks/tools/redact/useRedactOperation";
|
||||
import { useBaseTool } from "@app/hooks/tools/shared/useBaseTool";
|
||||
import { BaseToolProps, ToolComponent } from "@app/types/tool";
|
||||
import { useRedactModeTips, useRedactWordsTips, useRedactAdvancedTips } from "@app/components/tooltips/useRedactTips";
|
||||
import { useRedactModeTips, useRedactWordsTips, useRedactAdvancedTips, useRedactManualTips } from "@app/components/tooltips/useRedactTips";
|
||||
import RedactAdvancedSettings from "@app/components/tools/redact/RedactAdvancedSettings";
|
||||
import WordsToRedactInput from "@app/components/tools/redact/WordsToRedactInput";
|
||||
import RedactManualControls, { ManualRedactionType } from "@app/components/tools/redact/RedactManualControls";
|
||||
import { useNavigationActions, useNavigationState } from "@app/contexts/NavigationContext";
|
||||
import { useViewer } from "@app/contexts/ViewerContext";
|
||||
import { Stack, Alert, Button, Text } from "@mantine/core";
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
|
||||
const Redact = (props: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
@ -29,6 +34,110 @@ const Redact = (props: BaseToolProps) => {
|
||||
const modeTips = useRedactModeTips();
|
||||
const wordsTips = useRedactWordsTips();
|
||||
const advancedTips = useRedactAdvancedTips();
|
||||
const manualTips = useRedactManualTips();
|
||||
|
||||
// Navigation for switching to viewer on Manual
|
||||
const { actions: navActions } = useNavigationActions();
|
||||
const { workbench } = useNavigationState();
|
||||
const viewer = useViewer();
|
||||
|
||||
// Extract stable references to viewer methods to avoid re-renders from context changes
|
||||
const getRedactionStateRef = useRef(viewer.getRedactionState);
|
||||
const registerToolModeListenerRef = useRef(viewer.registerToolModeListener);
|
||||
const unregisterToolModeListenerRef = useRef(viewer.unregisterToolModeListener);
|
||||
const redactionActionsRef = useRef(viewer.redactionActions);
|
||||
const setAnnotationModeRef = useRef(viewer.setAnnotationMode);
|
||||
const panActionsRef = useRef(viewer.panActions);
|
||||
const triggerToolModeUpdateRef = useRef(viewer.triggerToolModeUpdate);
|
||||
|
||||
// Update refs when viewer context changes (but don't cause re-renders)
|
||||
useEffect(() => {
|
||||
getRedactionStateRef.current = viewer.getRedactionState;
|
||||
registerToolModeListenerRef.current = viewer.registerToolModeListener;
|
||||
unregisterToolModeListenerRef.current = viewer.unregisterToolModeListener;
|
||||
redactionActionsRef.current = viewer.redactionActions;
|
||||
setAnnotationModeRef.current = viewer.setAnnotationMode;
|
||||
panActionsRef.current = viewer.panActions;
|
||||
triggerToolModeUpdateRef.current = viewer.triggerToolModeUpdate;
|
||||
}, [viewer]);
|
||||
|
||||
// Force re-render when tool mode changes to ensure buttons reflect current state
|
||||
const [updateCounter, setUpdateCounter] = useState(0);
|
||||
useEffect(() => {
|
||||
const handleToolModeUpdate = () => {
|
||||
setUpdateCounter(prev => prev + 1);
|
||||
};
|
||||
registerToolModeListenerRef.current(handleToolModeUpdate);
|
||||
return () => {
|
||||
unregisterToolModeListenerRef.current();
|
||||
};
|
||||
}, []); // Empty deps - refs are stable
|
||||
|
||||
// Track redaction state to ensure UI stays in sync with plugin state
|
||||
// Use a ref to track the last known state and only update when it changes
|
||||
const [redactionStateCheck, setRedactionStateCheck] = useState(0);
|
||||
const lastRedactionStateRef = useRef<{ activeType: string | null; pendingCount: number } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (base.params.parameters.mode !== 'manual') return;
|
||||
|
||||
// Check redaction state periodically, but only update if it actually changed
|
||||
const interval = setInterval(() => {
|
||||
const currentState = getRedactionStateRef.current();
|
||||
const lastState = lastRedactionStateRef.current;
|
||||
|
||||
// Only trigger update if activeType changed (not just pendingCount)
|
||||
if (!lastState || lastState.activeType !== currentState.activeType) {
|
||||
lastRedactionStateRef.current = {
|
||||
activeType: currentState.activeType,
|
||||
pendingCount: currentState.pendingCount,
|
||||
};
|
||||
setRedactionStateCheck(prev => prev + 1);
|
||||
} else {
|
||||
// Update ref even if we don't trigger re-render
|
||||
lastRedactionStateRef.current = {
|
||||
activeType: currentState.activeType,
|
||||
pendingCount: currentState.pendingCount,
|
||||
};
|
||||
}
|
||||
}, 500); // Check less frequently - only when mode actually changes
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [base.params.parameters.mode]); // Removed viewer dependency
|
||||
|
||||
// Check if we need to show the viewer warning
|
||||
const isManualModeOutsideViewer = base.params.parameters.mode === 'manual' && workbench !== 'viewer';
|
||||
|
||||
const handleEnterManual = useCallback(() => {
|
||||
// Mark that manual redaction should be initialized and activate last mode
|
||||
sessionStorage.setItem('redaction:init', 'manual');
|
||||
// Persist current choice if any
|
||||
const last = (sessionStorage.getItem('redaction:lastManualType') as ManualRedactionType | null) || 'redactSelection';
|
||||
sessionStorage.setItem('redaction:lastManualType', last);
|
||||
// Switch to viewer and show the Redact tool in sidebar
|
||||
navActions.setToolAndWorkbench('redact' as any, 'viewer');
|
||||
// Defer activation to ensure viewer is ready and other tools are disabled
|
||||
const activate = () => {
|
||||
try { setAnnotationModeRef.current(false); } catch {}
|
||||
try { panActionsRef.current.disablePan(); } catch {}
|
||||
const activateRedaction = () => {
|
||||
if (last === 'marqueeRedact') {
|
||||
redactionActionsRef.current.activateArea();
|
||||
} else {
|
||||
redactionActionsRef.current.activateText();
|
||||
}
|
||||
try { triggerToolModeUpdateRef.current(); } catch {}
|
||||
};
|
||||
// Use double deferral to ensure state is settled
|
||||
if (typeof window !== 'undefined' && 'requestAnimationFrame' in window) {
|
||||
requestAnimationFrame(() => setTimeout(activateRedaction, 0));
|
||||
} else {
|
||||
setTimeout(activateRedaction, 0);
|
||||
}
|
||||
};
|
||||
// Defer activation slightly to allow navigation to complete
|
||||
setTimeout(activate, 50);
|
||||
}, [navActions]);
|
||||
|
||||
const isExecuteDisabled = () => {
|
||||
if (base.params.parameters.mode === 'manual') {
|
||||
@ -38,12 +147,20 @@ const Redact = (props: BaseToolProps) => {
|
||||
};
|
||||
|
||||
// Compute actual collapsed state based on results and user state
|
||||
const getActualCollapsedState = (userCollapsed: boolean) => {
|
||||
const getActualCollapsedState = useCallback((userCollapsed: boolean) => {
|
||||
return (!base.hasFiles || base.hasResults) ? true : userCollapsed; // Force collapse when results are shown
|
||||
};
|
||||
}, [base.hasFiles, base.hasResults]);
|
||||
|
||||
// Memoize redaction state to avoid calling getRedactionState on every render
|
||||
const redactionState = useMemo(() => {
|
||||
if (base.params.parameters.mode !== 'manual') return null;
|
||||
// Reference check counter to ensure re-evaluation when it changes
|
||||
void redactionStateCheck;
|
||||
return getRedactionStateRef.current();
|
||||
}, [base.params.parameters.mode, redactionStateCheck]);
|
||||
|
||||
// Build conditional steps based on redaction mode
|
||||
const buildSteps = () => {
|
||||
const buildSteps = useCallback(() => {
|
||||
const steps = [
|
||||
// Method selection step (always present)
|
||||
{
|
||||
@ -52,11 +169,41 @@ const Redact = (props: BaseToolProps) => {
|
||||
onCollapsedClick: () => base.settingsCollapsed ? base.handleSettingsReset() : setMethodCollapsed(!methodCollapsed),
|
||||
tooltip: modeTips,
|
||||
content: (
|
||||
<RedactModeSelector
|
||||
mode={base.params.parameters.mode}
|
||||
onModeChange={(mode) => base.params.updateParameter('mode', mode)}
|
||||
disabled={base.endpointLoading}
|
||||
/>
|
||||
<Stack gap="md">
|
||||
<RedactModeSelector
|
||||
mode={base.params.parameters.mode}
|
||||
onModeChange={(mode) => {
|
||||
base.params.updateParameter('mode', mode);
|
||||
if (mode === 'manual') {
|
||||
handleEnterManual();
|
||||
}
|
||||
}}
|
||||
disabled={base.endpointLoading}
|
||||
/>
|
||||
{isManualModeOutsideViewer && (
|
||||
<Alert
|
||||
color="yellow"
|
||||
title={t("redact.manual.viewerWarning.title", "Manual Redaction Requires Viewer")}
|
||||
icon={<WarningIcon fontSize="small" />}
|
||||
>
|
||||
<Stack gap="sm">
|
||||
<Text size="sm">
|
||||
{t("redact.manual.viewerWarning.message", "Manual redaction can only be used in the viewer view. Please switch to the viewer to use this feature.")}
|
||||
</Text>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
onClick={() => {
|
||||
handleEnterManual();
|
||||
}}
|
||||
style={{ alignSelf: 'flex-start' }}
|
||||
>
|
||||
{t("redact.manual.viewerWarning.button", "Go to Viewer")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Alert>
|
||||
)}
|
||||
</Stack>
|
||||
),
|
||||
}
|
||||
];
|
||||
@ -88,11 +235,60 @@ const Redact = (props: BaseToolProps) => {
|
||||
},
|
||||
);
|
||||
} else if (base.params.parameters.mode === 'manual') {
|
||||
// Manual mode steps would go here when implemented
|
||||
steps.push({
|
||||
title: t("redact.manual.settings.title", "Manual Redaction"),
|
||||
isCollapsed: getActualCollapsedState(false),
|
||||
onCollapsedClick: () => {},
|
||||
tooltip: manualTips,
|
||||
content: (
|
||||
<RedactManualControls
|
||||
value={
|
||||
// Get actual active mode from redaction plugin, fallback to sessionStorage
|
||||
// redactionStateCheck changes only when activeType actually changes
|
||||
(redactionState?.activeType as ManualRedactionType) ||
|
||||
(sessionStorage.getItem('redaction:lastManualType') as ManualRedactionType) ||
|
||||
'redactSelection'
|
||||
}
|
||||
onChange={(val) => {
|
||||
sessionStorage.setItem('redaction:lastManualType', val);
|
||||
// Ensure we're in viewer and activate chosen tool
|
||||
handleEnterManual();
|
||||
if (val === 'marqueeRedact') redactionActionsRef.current.activateArea();
|
||||
else redactionActionsRef.current.activateText();
|
||||
// Trigger a state check to update UI
|
||||
setRedactionStateCheck(prev => prev + 1);
|
||||
}}
|
||||
disabled={base.endpointLoading}
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
return steps;
|
||||
};
|
||||
}, [
|
||||
base.params.parameters, // Required for RedactAdvancedSettings prop
|
||||
base.hasFiles,
|
||||
base.hasResults,
|
||||
base.endpointLoading,
|
||||
base.params.updateParameter,
|
||||
methodCollapsed,
|
||||
wordsCollapsed,
|
||||
advancedCollapsed,
|
||||
modeTips,
|
||||
wordsTips,
|
||||
advancedTips,
|
||||
manualTips,
|
||||
isManualModeOutsideViewer,
|
||||
handleEnterManual,
|
||||
redactionState,
|
||||
t,
|
||||
getActualCollapsedState,
|
||||
base.settingsCollapsed,
|
||||
base.handleSettingsReset,
|
||||
setMethodCollapsed,
|
||||
setWordsCollapsed,
|
||||
setAdvancedCollapsed,
|
||||
]);
|
||||
|
||||
return createToolFlow({
|
||||
files: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user