Compare commits

..
Author SHA1 Message Date
Anthony StirlingandClaude 2ef63566f1 Fix default parameter consistency and cleanup formatting
- Update tolerance default from 20 to 30 to match documentation
- Update min_area default from 8000 to 10000 to match documentation
- Add missing period to minimum contour area description
- Replace double dashes with single hyphens in tooltip translations

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-26 00:05:55 +01:00
Anthony StirlingandClaude 00efc8802c Fix TypeScript linting error in zipFileService
Remove unused filename parameter from filter function to resolve
@typescript-eslint/no-unused-vars error. Also include spotless
formatting changes.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-26 00:01:21 +01:00
Anthony StirlingandClaude 9973c6b1d3 Rewrite tooltip with concise, actionable content focused on real-world usage
Transform verbose tooltip into practical guidance:

New structure:
• "What this does" - Clear one-liner about functionality
• "When to use" - 4 concrete use cases (album pages, batches, collages, documents)
• "Quick fixes" - Problem → solution pairs with specific values
• "Setup tips" - Practical scanning advice (DPI, gaps, backgrounds)
• "Heads-up" - Important limitations and workarounds

Benefits:
- Shorter, scannable content
- Action-oriented language ("increase Tolerance to 30-50")
- Real-world scenarios users actually face
- Specific numeric guidance instead of vague descriptions
- Proactive troubleshooting before problems occur

Content matches professional but accessible tone throughout.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 23:55:57 +01:00
Anthony StirlingandClaude 5d828040a7 Replace abstract tooltip explanations with practical troubleshooting guide
- Remove unhelpful technical descriptions with meaningless numbers
- Add problem-solution pairs with specific value recommendations:
  • Photos not detected → increase Tolerance to 30-50
  • Too many false detections → increase Minimum Area to 15000-20000
  • Photos cropped tight → increase Border Size to 5-10
  • Tilted photos → lower Angle Threshold to 5
  • Dust detection → increase Minimum Contour Area to 1000

- Improve setup tips with concrete measurements:
  • Use white/light backgrounds
  • Leave 1cm gaps between photos
  • Scan at 300+ DPI
  • Try defaults before adjusting

Now users get actionable solutions instead of abstract technical jargon.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 23:44:20 +01:00
Anthony StirlingandClaude d6a3774e9b Add comprehensive tooltip support for scanner image split tool
- Create useScannerImageSplitTips with detailed explanations
- Explain use cases: photo album digitisation, flatbed scanning, collage splitting
- Add technical explanations for all 5 parameters with user-friendly language
- Include practical tips for optimal results
- Full GB translation support with British spelling consistency
- Integrate tooltip into main tool component

The tooltip helps users understand:
- What the tool does and when to use it
- How each technical parameter affects processing
- Best practices for scanning and image preparation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 23:23:17 +01:00
Anthony StirlingandClaude ce2065aec4 Add complete GB translations for scanner image split tool
- Add scannerImageSplit section with submit/title/error translations
- Update home.scannerImageSplit title and description to match registry expectations
- Ensure all React component translation keys are properly supported
- Maintain British spelling ("colour") in existing translations

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 23:17:49 +01:00
Anthony StirlingandClaude e7109bb4e9 Convert extract-image-scans to React component
- Create full React implementation for scanner image split tool
- Add ScannerImageSplit component with settings and operation hooks
- Enhance zipFileService with extractAllFiles() method for general ZIP handling
- Add custom response handler to properly extract ZIP files containing images
- Integrate tool into registry with complete operation config
- Support both single image and ZIP responses from backend

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-25 23:13:08 +01:00
32 changed files with 221 additions and 1548 deletions
-13
View File
@@ -6,11 +6,6 @@ http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Add .mjs MIME type mapping
types {
text/javascript mjs;
}
# Gzip compression
gzip on;
gzip_vary on;
@@ -95,14 +90,6 @@ http {
proxy_set_header X-Forwarded-Port $server_port;
}
# Serve .mjs files with correct MIME type (must come before general static assets)
location ~* \.mjs$ {
try_files $uri =404;
add_header Content-Type "text/javascript; charset=utf-8" always;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
+109 -140
View File
@@ -10,22 +10,21 @@
"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.0",
"@embedpdf/core": "^1.2.1",
"@embedpdf/engines": "^1.2.1",
"@embedpdf/plugin-export": "^1.3.0",
"@embedpdf/plugin-interaction-manager": "^1.3.0",
"@embedpdf/plugin-loader": "^1.3.0",
"@embedpdf/plugin-pan": "^1.3.0",
"@embedpdf/plugin-render": "^1.3.0",
"@embedpdf/plugin-rotate": "^1.3.0",
"@embedpdf/plugin-scroll": "^1.3.0",
"@embedpdf/plugin-search": "^1.3.0",
"@embedpdf/plugin-selection": "^1.3.0",
"@embedpdf/plugin-spread": "^1.3.0",
"@embedpdf/plugin-thumbnail": "^1.3.0",
"@embedpdf/plugin-tiling": "^1.3.0",
"@embedpdf/plugin-viewport": "^1.3.0",
"@embedpdf/plugin-zoom": "^1.3.0",
"@embedpdf/plugin-interaction-manager": "^1.2.1",
"@embedpdf/plugin-loader": "^1.2.1",
"@embedpdf/plugin-pan": "^1.2.1",
"@embedpdf/plugin-render": "^1.2.1",
"@embedpdf/plugin-rotate": "^1.2.1",
"@embedpdf/plugin-scroll": "^1.2.1",
"@embedpdf/plugin-search": "^1.2.1",
"@embedpdf/plugin-selection": "^1.2.1",
"@embedpdf/plugin-spread": "^1.2.1",
"@embedpdf/plugin-thumbnail": "^1.2.1",
"@embedpdf/plugin-tiling": "^1.2.1",
"@embedpdf/plugin-viewport": "^1.2.1",
"@embedpdf/plugin-zoom": "^1.2.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@iconify/react": "^6.0.2",
@@ -489,13 +488,12 @@
}
},
"node_modules/@embedpdf/core": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.3.0.tgz",
"integrity": "sha512-KEic1NA9JrtNRoTq3O3m93YTglRKweR6uqjzX3sLGCmy+LsUjiH5WOCJAztlSlmZEXysAlZlyzG/09gz4tpBAg==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.2.1.tgz",
"integrity": "sha512-2VwRPsN3+LmaBrD8TCN1t1ni/Vc9CxAfl/SApDjZYwE7zOieQT4ZHt+nkgF0F4I3xSgvvyHDjmOonhjBIrT6xA==",
"dependencies": {
"@embedpdf/engines": "1.3.0",
"@embedpdf/models": "1.3.0"
"@embedpdf/engines": "1.2.1",
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"preact": "^10.26.4",
@@ -505,13 +503,13 @@
}
},
"node_modules/@embedpdf/engines": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.3.0.tgz",
"integrity": "sha512-6WbYwxtCCjOazEMGKbhKRkos6S1VkzI4R2u6dUuIsUw9G2HLP4bwJCBKj9A0FuMAJkKQ3VL5eVCSGfqaCaRoyQ==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-1.2.1.tgz",
"integrity": "sha512-nhycZ7Buq2B34dcpo6n7RdFwdhwTvKzvnRy7QX+uU00Dz5vftkCG4OK+pBVzxE4y7vAu+Yb4wNpdc7HmIj3B6w==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.0",
"@embedpdf/pdfium": "1.3.0"
"@embedpdf/models": "1.2.1",
"@embedpdf/pdfium": "1.2.1"
},
"peerDependencies": {
"preact": "^10.26.4",
@@ -521,43 +519,26 @@
}
},
"node_modules/@embedpdf/models": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.3.0.tgz",
"integrity": "sha512-LIY6T+nQoc1hi6nq1NlH6sR43J3PYOg9Bux8ouEnKjEGiZMgyd1cMxhBfrrY+Ft6DsSkqqujFOVEwjeYQYy3dg==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-1.2.1.tgz",
"integrity": "sha512-FzJU51jsqihfgt50B00FEpgyym87/Dn2iGmMq4++Vu/oO6qBx/y69m4/cCAh4p4KkTJsvKNWC7T7dwSKa0FjHA==",
"license": "MIT"
},
"node_modules/@embedpdf/pdfium": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.3.0.tgz",
"integrity": "sha512-rSBFYjxwQ58L/HcqR0l5Vv4G5t+CCOKlFYrDReTZYNN7fhzKPUWbXUn4ARahZWCNmF8svHumV2P4ArakJJviuw==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-1.2.1.tgz",
"integrity": "sha512-QWf1jg7EqUlku2q6KYhlXCNfk5IAykFerPuzKJepHTeAEaRcAfu84fJgEsoUTCK4D6dfzVNp2Iuxw6Kv7MpSeg==",
"license": "MIT"
},
"node_modules/@embedpdf/plugin-export": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-1.3.0.tgz",
"integrity": "sha512-R6VItLmXmXbb0/4AsH1YGUZd0c64K/8kxQd0XAvgUJwcL7Z4s8bLsqRx4sVQqwVllaPEJbAdFn1CC/ymkGB+fg==",
"license": "MIT",
"dependencies": {
"@embedpdf/models": "1.3.0"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
"vue": ">=3.2.0"
}
},
"node_modules/@embedpdf/plugin-interaction-manager": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.3.0.tgz",
"integrity": "sha512-iMG7mW+4YpNjBeSAcC5kK9VnjwmNu71HTxVtKnN73t3EBfukbMH4y7Tp2ds+4I97H6vc18RK5xuUCSesEOBgww==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.2.1.tgz",
"integrity": "sha512-HhEBuDjDNMH6wu76Eo3yHwjG01U1lNZShkOsFoib/rtx8HByTgZS8iVpovaOprr6gfS04ZLqWcsN1nt5qAH90w==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/core": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -565,15 +546,14 @@
}
},
"node_modules/@embedpdf/plugin-loader": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.3.0.tgz",
"integrity": "sha512-tkOa1UwFOimueSxxm2hRAAh64K75itDvUO6wHjb5X5s0Hx4DccfrJ7KusDhxBkeQLFXtZknPG0Q2/9T+joAqeQ==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.2.1.tgz",
"integrity": "sha512-VblKErfEiHcVao18TfCmc0UJlKAkqxE29DaLJrXQHGUw/qc+pC9HlvMVpDz3+Eb13UafYS6ZUZuEng2/fQ+JJw==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/core": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -581,17 +561,16 @@
}
},
"node_modules/@embedpdf/plugin-pan": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.3.0.tgz",
"integrity": "sha512-tZxUpX9dvd/VDHCTqM9Yjss4M8pkJWFUA5GDNmPkExRXIASuB98wEP8fh0rQt13TEZ30rV77cEsNXngju56kjg==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-1.2.1.tgz",
"integrity": "sha512-/BTOyRl31tvnCmoLs4qNPROMRLaG34jGYNyMQquB0uPUXZjwdMloikriwos91qCOLUrhvs4SaDpC3Ghv2BO5kA==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/plugin-interaction-manager": "1.3.0",
"@embedpdf/plugin-viewport": "1.3.0",
"@embedpdf/core": "1.2.1",
"@embedpdf/plugin-interaction-manager": "1.2.1",
"@embedpdf/plugin-viewport": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -599,15 +578,14 @@
}
},
"node_modules/@embedpdf/plugin-render": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.3.0.tgz",
"integrity": "sha512-ZyxoGIIUa2HBLt1IB64EdWqBxHh01AX/1HJ7/cnoQK1h/oKXRbMAX6Mb23JCh2PGa4sGeyV3psoFMt037Eew3g==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.2.1.tgz",
"integrity": "sha512-iMfuVJqttJmm7Zb8oOaqNVNrC3NS57bDNNAc4MIc2f2TxIFSznvBPlwWN+PN45qNcTQiGzFc1ZMqIQDOG4qFnQ==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/core": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -615,15 +593,14 @@
}
},
"node_modules/@embedpdf/plugin-rotate": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.3.0.tgz",
"integrity": "sha512-EyLLwf9VKQCsMRTe0KwGe+ZAaFqmcYS5WW/qqPBNfvSuBaybNpdI+C72IQFr41X7cYQV58OgEL3bfDb1MBPGHQ==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-1.2.1.tgz",
"integrity": "sha512-UhHds5donLDXm3i9nKrhSmo3yawVtjb6gID0MDrhj3+Lci/YQ3wDvGUhk7dNmgLcOt7G8pMa0wesnnpVWirUXA==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/core": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -631,16 +608,15 @@
}
},
"node_modules/@embedpdf/plugin-scroll": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.3.0.tgz",
"integrity": "sha512-o1n6Mkoc92BHAkoCX0mSLXgOj4uAkokNbvP+2QMijShzTsl95gU5UzK6siZ5o6WgZBznJcceYmLuPR5ODqZDWg==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.2.1.tgz",
"integrity": "sha512-I1haDXIOzs59uhOWEP6UvP5jzjcQHMLQuQbfRVJM0zdWU6t3jwSfcwPUI7iv4CAAepbuyJKL328yc8736r/FYw==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/plugin-viewport": "1.3.0",
"@embedpdf/core": "1.2.1",
"@embedpdf/plugin-viewport": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -648,16 +624,15 @@
}
},
"node_modules/@embedpdf/plugin-search": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.3.0.tgz",
"integrity": "sha512-DilSRfPQR38picjx7eyyuXNeduD7hcW/PjT9DZrjXxfLrAQtd17CXJs7HtJevl1wErh/CCSvZlHhjp1++O6GAg==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-1.2.1.tgz",
"integrity": "sha512-sl9FBQzbOBtdmPpf6UI0bnWCTPWDkj47rTxyK07bpnGfGuFof4zhcxmMaFdyP7zqBh4Y9XqGu4A0uMTO2d/t7g==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/plugin-loader": "1.3.0",
"@embedpdf/core": "1.2.1",
"@embedpdf/plugin-loader": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -665,17 +640,16 @@
}
},
"node_modules/@embedpdf/plugin-selection": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.3.0.tgz",
"integrity": "sha512-1PEtreNofysaLxZvgO2CSNCxXhevjYnBdu4IHTFeJKXoq3ckKwkX8fJjyyN4D6+6uXZsnFkHhewl1yKCfKWAWw==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.2.1.tgz",
"integrity": "sha512-wgG1X1sl6sed3pv7WLIO74SX0x3389/ax+/OLMty/LFbDNYMRO+n8ZQss8aUM700HARIqkPJy7UoSQt91o4nwA==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/plugin-interaction-manager": "1.3.0",
"@embedpdf/plugin-viewport": "1.3.0",
"@embedpdf/core": "1.2.1",
"@embedpdf/plugin-interaction-manager": "1.2.1",
"@embedpdf/plugin-viewport": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -683,16 +657,15 @@
}
},
"node_modules/@embedpdf/plugin-spread": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.3.0.tgz",
"integrity": "sha512-oRLimcod8RhdknN94CQeG+0QndQeiZKIhFUCXDIGxN1Z/qvspZCUty2TC+1kc3G318nZi55pWWphq9sB7ZpqEw==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-1.2.1.tgz",
"integrity": "sha512-rpadnutT1wSdBQV7RQz40zYdKgCRgmJde/tamgB8oHQypcnZGQcAG6/ZfX5j12s9pG18hKwwL+KmMoBnXD9IjQ==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/plugin-loader": "1.3.0",
"@embedpdf/core": "1.2.1",
"@embedpdf/plugin-loader": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -700,34 +673,32 @@
}
},
"node_modules/@embedpdf/plugin-thumbnail": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.3.0.tgz",
"integrity": "sha512-w2wzL7m6/sUF54sMVEi8Y8+7VE3BcZqI8THDqobkEkno4Dgmb77FHNPFD6YtAhaRmIoyPnlZf05RDd6Z8ohhkQ==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-1.2.1.tgz",
"integrity": "sha512-TjHPkK8p3+FDMLcUdb3/4VREjm+liVooufLPVZ3FCXHbiC0PeUkqnwAxpCS2Jw1n+EtkY8pefRdRJeZhO6plOQ==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/plugin-render": "1.3.0",
"@embedpdf/core": "1.2.1",
"@embedpdf/plugin-render": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@embedpdf/plugin-tiling": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.3.0.tgz",
"integrity": "sha512-huYi4BJa9KSfqC424bEHw72KBLCR2rfApMeKnpUzAFSdWA6MSYmVBSk8ghnU7XbcLuL6fFBarNsziNrSSnVWTw==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-1.2.1.tgz",
"integrity": "sha512-C9uOGVIsoxUw+uQMXfJFZ8ibRLQeNOnaKC2izjx967iGu0ZoecAv+mKtH/Ge0vMEKYM1109AlF5T2EGwKQW2YA==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/plugin-render": "1.3.0",
"@embedpdf/plugin-scroll": "1.3.0",
"@embedpdf/plugin-viewport": "1.3.0",
"@embedpdf/core": "1.2.1",
"@embedpdf/plugin-render": "1.2.1",
"@embedpdf/plugin-scroll": "1.2.1",
"@embedpdf/plugin-viewport": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -735,15 +706,14 @@
}
},
"node_modules/@embedpdf/plugin-viewport": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.3.0.tgz",
"integrity": "sha512-AZ7U8DEgEQ8nK5kdrqtukLl5au9NE3mIlFmloyo6Ddrt2rN/Jw1Lt9dsl6wU20GcFQX+hWsg9uAJboLq6AdOCA==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.2.1.tgz",
"integrity": "sha512-yvftOis7FLBjM3w2VYO5LXVKXoHkmFV/SPy7U6SbuLJTX126F4ohSij9euMHJjaqOgr5tBNvrf4xemVRglxM9w==",
"dependencies": {
"@embedpdf/models": "1.3.0"
"@embedpdf/models": "1.2.1"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/core": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
@@ -751,19 +721,18 @@
}
},
"node_modules/@embedpdf/plugin-zoom": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.3.0.tgz",
"integrity": "sha512-1VA9aFxoP+BoEpwlR0//jtlD9ESS8nhU8OGGHBRu7IgoWzIx4GqOHgpgXVxzFl9IaLOv69E9DVmwe/yaC6F+0g==",
"license": "MIT",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-1.2.1.tgz",
"integrity": "sha512-hsp/nM4C8q0FM9P6FkpQLbU8IYawUgmiYgD3HXqHWBVRk30OIaXs4N0KC9vsHwn8ZAiyLl7jhlAXpgoacH5xEQ==",
"dependencies": {
"@embedpdf/models": "1.3.0",
"@embedpdf/models": "1.2.1",
"hammerjs": "^2.0.8"
},
"peerDependencies": {
"@embedpdf/core": "1.3.0",
"@embedpdf/plugin-interaction-manager": "1.3.0",
"@embedpdf/plugin-scroll": "1.3.0",
"@embedpdf/plugin-viewport": "1.3.0",
"@embedpdf/core": "1.2.1",
"@embedpdf/plugin-interaction-manager": "1.2.1",
"@embedpdf/plugin-scroll": "1.2.1",
"@embedpdf/plugin-viewport": "1.2.1",
"preact": "^10.26.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0",
+14 -15
View File
@@ -6,22 +6,21 @@
"proxy": "http://localhost:8080",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
"@embedpdf/core": "^1.3.0",
"@embedpdf/core": "^1.2.1",
"@embedpdf/engines": "^1.2.1",
"@embedpdf/plugin-export": "^1.3.0",
"@embedpdf/plugin-interaction-manager": "^1.3.0",
"@embedpdf/plugin-loader": "^1.3.0",
"@embedpdf/plugin-pan": "^1.3.0",
"@embedpdf/plugin-render": "^1.3.0",
"@embedpdf/plugin-rotate": "^1.3.0",
"@embedpdf/plugin-scroll": "^1.3.0",
"@embedpdf/plugin-search": "^1.3.0",
"@embedpdf/plugin-selection": "^1.3.0",
"@embedpdf/plugin-spread": "^1.3.0",
"@embedpdf/plugin-thumbnail": "^1.3.0",
"@embedpdf/plugin-tiling": "^1.3.0",
"@embedpdf/plugin-viewport": "^1.3.0",
"@embedpdf/plugin-zoom": "^1.3.0",
"@embedpdf/plugin-interaction-manager": "^1.2.1",
"@embedpdf/plugin-loader": "^1.2.1",
"@embedpdf/plugin-pan": "^1.2.1",
"@embedpdf/plugin-render": "^1.2.1",
"@embedpdf/plugin-rotate": "^1.2.1",
"@embedpdf/plugin-scroll": "^1.2.1",
"@embedpdf/plugin-search": "^1.2.1",
"@embedpdf/plugin-selection": "^1.2.1",
"@embedpdf/plugin-spread": "^1.2.1",
"@embedpdf/plugin-thumbnail": "^1.2.1",
"@embedpdf/plugin-tiling": "^1.2.1",
"@embedpdf/plugin-viewport": "^1.2.1",
"@embedpdf/plugin-zoom": "^1.2.1",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@iconify/react": "^6.0.2",
+23 -76
View File
@@ -579,9 +579,9 @@
"title": "API Documentation",
"desc": "View API documentation and test endpoints"
},
"scannerEffect": {
"fakeScan": {
"tags": "scan,simulate,create",
"title": "Scanner Effect",
"title": "Fake Scan",
"desc": "Create a PDF that looks like it was scanned"
},
"editTableOfContents": {
@@ -619,7 +619,8 @@
"title": "Auto Split by Size/Count",
"desc": "Automatically split PDFs by file size or page count"
},
"replaceColor": {
"replaceColorPdf": {
"tags": "color,replace,invert",
"title": "Replace & Invert Colour",
"desc": "Replace or invert colours in PDF documents"
},
@@ -958,7 +959,7 @@
"header": "PDF Page Organiser",
"submit": "Rearrange Pages",
"mode": {
"_value": "Organization mode",
"_value": "Mode",
"1": "Custom Page Order",
"2": "Reverse Order",
"3": "Duplex Sort",
@@ -971,19 +972,6 @@
"10": "Odd-Even Merge",
"11": "Duplicate all pages"
},
"desc": {
"CUSTOM": "Use a custom sequence of page numbers or expressions to define a new order.",
"REVERSE_ORDER": "Flip the document so the last page becomes first and so on.",
"DUPLEX_SORT": "Interleave fronts then backs as if a duplex scanner scanned all fronts, then all backs (1, n, 2, n-1, …).",
"BOOKLET_SORT": "Arrange pages for booklet printing (last, first, second, second last, …).",
"SIDE_STITCH_BOOKLET_SORT": "Arrange pages for side‑stitch booklet printing (optimised for binding on the side).",
"ODD_EVEN_SPLIT": "Split the document into two outputs: all odd pages and all even pages.",
"ODD_EVEN_MERGE": "Merge two PDFs by alternating pages: odd from the first, even from the second.",
"DUPLICATE": "Duplicate each page according to the custom order count (e.g., 4 duplicates each page 4×).",
"REMOVE_FIRST": "Remove the first page from the document.",
"REMOVE_LAST": "Remove the last page from the document.",
"REMOVE_FIRST_AND_LAST": "Remove both the first and last pages from the document."
},
"placeholder": "(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)"
},
"addImage": {
@@ -1608,13 +1596,7 @@
"header": "Extract Images",
"selectText": "Select image format to convert extracted images to",
"allowDuplicates": "Save duplicate images",
"submit": "Extract",
"settings": {
"title": "Settings"
},
"error": {
"failed": "An error occurred while extracting images from the PDF."
}
"submit": "Extract"
},
"pdfToPDFA": {
"tags": "archive,long-term,standard,conversion,storage,preservation",
@@ -2551,48 +2533,25 @@
},
"selectCustomCert": "Custom Certificate File X.509 (Optional)"
},
"replaceColor": {
"labels": {
"settings": "Settings",
"colourOperation": "Colour operation"
"replace-color": {
"title": "Advanced Colour options",
"header": "Replace-Invert Colour PDF",
"selectText": {
"1": "Replace or Invert colour Options",
"2": "Default(Default high contrast colours)",
"3": "Custom(Customised colours)",
"4": "Full-Invert(Invert all colours)",
"5": "High contrast colour options",
"6": "white text on black background",
"7": "Black text on white background",
"8": "Yellow text on black background",
"9": "Green text on black background",
"10": "Choose text Colour",
"11": "Choose background Colour"
},
"options": {
"highContrast": "High contrast",
"invertAll": "Invert all colours",
"custom": "Custom"
},
"tooltip": {
"header": {
"title": "Replace & Invert Colour Settings Overview"
},
"description": {
"title": "Description",
"text": "Transform PDF colours to improve readability and accessibility. Choose from high contrast presets, invert all colours, or create custom colour schemes."
},
"highContrast": {
"title": "High Contrast",
"text": "Apply predefined high contrast colour combinations designed for better readability and accessibility compliance.",
"bullet1": "White text on black background - Classic dark mode",
"bullet2": "Black text on white background - Standard high contrast",
"bullet3": "Yellow text on black background - High visibility option",
"bullet4": "Green text on black background - Alternative high contrast"
},
"invertAll": {
"title": "Invert All Colours",
"text": "Completely invert all colours in the PDF, creating a negative-like effect. Useful for creating dark mode versions of documents or reducing eye strain in low-light conditions."
},
"custom": {
"title": "Custom Colours",
"text": "Define your own text and background colours using the colour pickers. Perfect for creating branded documents or specific accessibility requirements.",
"bullet1": "Text colour - Choose the colour for text elements",
"bullet2": "Background colour - Set the background colour for the document"
}
},
"error": {
"failed": "An error occurred while processing the colour replacement."
}
"submit": "Replace"
},
"replaceColor": {
"replaceColorPdf": {
"tags": "Replace Colour,Page operations,Back end,server side"
},
"login": {
@@ -3425,18 +3384,6 @@
"generateError": "We couldn't generate your API key."
}
},
"AddAttachmentsRequest": {
"attachments": "Select Attachments",
"info": "Select files to attach to your PDF. These files will be embedded and accessible through the PDF's attachment panel.",
"selectFiles": "Select Files to Attach",
"placeholder": "Choose files...",
"addMoreFiles": "Add more files...",
"selectedFiles": "Selected Files",
"submit": "Add Attachments",
"results": {
"title": "Attachment Results"
}
},
"termsAndConditions": "Terms & Conditions",
"logOut": "Log out"
}
+8 -50
View File
@@ -769,7 +769,7 @@
"header": "PDF Page Organizer",
"submit": "Rearrange Pages",
"mode": {
"_value": "Organization mode",
"_value": "Mode",
"1": "Custom Page Order",
"2": "Reverse Order",
"3": "Duplex Sort",
@@ -782,19 +782,6 @@
"10": "Odd-Even Merge",
"11": "Duplicate all pages"
},
"desc": {
"CUSTOM": "Use a custom sequence of page numbers or expressions to define a new order.",
"REVERSE_ORDER": "Flip the document so the last page becomes first and so on.",
"DUPLEX_SORT": "Interleave fronts then backs as if a duplex scanner scanned all fronts, then all backs (1, n, 2, n-1, …).",
"BOOKLET_SORT": "Arrange pages for booklet printing (last, first, second, second last, …).",
"SIDE_STITCH_BOOKLET_SORT": "Arrange pages for side‑stitch booklet printing (optimized for binding on the side).",
"ODD_EVEN_SPLIT": "Split the document into two outputs: all odd pages and all even pages.",
"ODD_EVEN_MERGE": "Merge two PDFs by alternating pages: odd from the first, even from the second.",
"DUPLICATE": "Duplicate each page according to the custom order count (e.g., 4 duplicates each page 4×).",
"REMOVE_FIRST": "Remove the first page from the document.",
"REMOVE_LAST": "Remove the last page from the document.",
"REMOVE_FIRST_AND_LAST": "Remove both the first and last pages from the document."
},
"placeholder": "(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)"
},
"addImage": {
@@ -1069,13 +1056,7 @@
"header": "Extract Images",
"selectText": "Select image format to convert extracted images to",
"allowDuplicates": "Save duplicate images",
"submit": "Extract",
"settings": {
"title": "Settings"
},
"error": {
"failed": "An error occurred while extracting images from the PDF."
}
"submit": "Extract"
},
"pdfToPDFA": {
"tags": "archive,long-term,standard,conversion,storage,preservation",
@@ -1823,16 +1804,10 @@
}
},
"removeImage": {
"title": "Remove Images",
"header": "Remove Images",
"removeImage": "Remove Images",
"submit": "Remove Images",
"results": {
"title": "Remove Images Results"
},
"error": {
"failed": "Failed to remove images from the PDF."
}
"title": "Remove image",
"header": "Remove image",
"removeImage": "Remove image",
"submit": "Remove image"
},
"splitByChapters": {
"title": "Split PDF by Chapters",
@@ -1877,7 +1852,7 @@
"title": "How we use Cookies",
"description": {
"1": "We use cookies and other technologies to make Stirling PDF work better for you—helping us improve our tools and keep building features you'll love.",
"2": "If you'd rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly."
"2": "If you’d rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly."
},
"acceptAllBtn": "Okay",
"acceptNecessaryBtn": "No Thanks",
@@ -1901,7 +1876,7 @@
"1": "Strictly Necessary Cookies",
"2": "Always Enabled"
},
"description": "These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they can't be turned off."
"description": "These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they can’t be turned off."
},
"analytics": {
"title": "Analytics",
@@ -2381,22 +2356,5 @@
},
"automate": {
"copyToSaved": "Copy to Saved"
},
"AddAttachmentsRequest": {
"attachments": "Select Attachments",
"info": "Select files to attach to your PDF. These files will be embedded and accessible through the PDF's attachment panel.",
"selectFiles": "Select Files to Attach",
"placeholder": "Choose files...",
"addMoreFiles": "Add more files...",
"selectedFiles": "Selected Files",
"submit": "Add Attachments",
"results": {
"title": "Attachment Results"
}
},
"addAttachments": {
"error": {
"failed": "An error occurred while adding attachments to the PDF."
}
}
}
+3 -11
View File
@@ -66,9 +66,6 @@ export default function RightRail() {
const { totalItems, selectedCount } = getSelectionState();
// Get export state for viewer mode
const exportState = viewerContext?.getExportState?.();
const handleSelectAll = useCallback(() => {
if (currentView === 'fileEditor' || currentView === 'viewer') {
// Select all file IDs
@@ -99,10 +96,7 @@ export default function RightRail() {
}, [currentView, setSelectedFiles, pageEditorFunctions]);
const handleExportAll = useCallback(() => {
if (currentView === 'viewer') {
// Use EmbedPDF export functionality for viewer mode
viewerContext?.exportActions?.download();
} else if (currentView === 'fileEditor') {
if (currentView === 'fileEditor' || currentView === 'viewer') {
// Download selected files (or all if none selected)
const filesToDownload = selectedFiles.length > 0 ? selectedFiles : activeFiles;
@@ -119,7 +113,7 @@ export default function RightRail() {
// Export all pages (not just selected)
pageEditorFunctions?.onExportAll?.();
}
}, [currentView, activeFiles, selectedFiles, pageEditorFunctions, viewerContext]);
}, [currentView, activeFiles, selectedFiles, pageEditorFunctions]);
const handleCloseSelected = useCallback(() => {
if (currentView !== 'fileEditor') return;
@@ -451,9 +445,7 @@ export default function RightRail() {
radius="md"
className="right-rail-icon"
onClick={handleExportAll}
disabled={
currentView === 'viewer' ? !exportState?.canExport : totalItems === 0
}
disabled={currentView === 'viewer' || totalItems === 0}
>
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />
</ActionIcon>
@@ -1,46 +0,0 @@
import { useTranslation } from 'react-i18next';
import { Stack, Select, Checkbox } from '@mantine/core';
import { ExtractImagesParameters } from '../../../hooks/tools/extractImages/useExtractImagesParameters';
interface ExtractImagesSettingsProps {
parameters: ExtractImagesParameters;
onParameterChange: <K extends keyof ExtractImagesParameters>(key: K, value: ExtractImagesParameters[K]) => void;
disabled?: boolean;
}
const ExtractImagesSettings = ({
parameters,
onParameterChange,
disabled = false
}: ExtractImagesSettingsProps) => {
const { t } = useTranslation();
return (
<Stack gap="md">
<Select
label={t('extractImages.selectText', 'Output Format')}
value={parameters.format}
onChange={(value) => {
const allowedFormats = ['png', 'jpg', 'gif'] as const;
const format = allowedFormats.includes(value as any) ? (value as typeof allowedFormats[number]) : 'png';
onParameterChange('format', format);
}}
data={[
{ value: 'png', label: 'PNG' },
{ value: 'jpg', label: 'JPG' },
{ value: 'gif', label: 'GIF' },
]}
disabled={disabled}
/>
<Checkbox
label={t('extractImages.allowDuplicates', 'Allow Duplicate Images')}
checked={parameters.allowDuplicates}
onChange={(event) => onParameterChange('allowDuplicates', event.currentTarget.checked)}
disabled={disabled}
/>
</Stack>
);
};
export default ExtractImagesSettings;
@@ -1,65 +0,0 @@
import React from 'react';
import { Divider, Select, Stack, TextInput } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { ReorganizePagesParameters } from '../../../hooks/tools/reorganizePages/useReorganizePagesParameters';
import { getReorganizePagesModeData } from './constants';
export default function ReorganizePagesSettings({
parameters,
onParameterChange,
disabled,
}: {
parameters: ReorganizePagesParameters;
onParameterChange: <K extends keyof ReorganizePagesParameters>(
key: K,
value: ReorganizePagesParameters[K]
) => void;
disabled?: boolean;
}) {
const { t } = useTranslation();
const modeData = getReorganizePagesModeData(t);
const requiresOrder = parameters.customMode === '' || parameters.customMode === 'DUPLICATE';
const selectedMode = modeData.find(mode => mode.value === parameters.customMode) || modeData[0];
return (
<Stack gap="sm">
<Select
label={t('pdfOrganiser.mode._value', 'Organization mode')}
data={modeData}
value={parameters.customMode}
onChange={(v) => onParameterChange('customMode', v ?? '')}
disabled={disabled}
/>
{selectedMode && (
<div
style={{
backgroundColor: 'var(--information-text-bg)',
color: 'var(--information-text-color)',
padding: '8px 12px',
borderRadius: '8px',
marginTop: '4px',
fontSize: '0.75rem',
textAlign: 'center'
}}
>
{selectedMode.description}
</div>
)}
{requiresOrder && (
<>
<Divider/>
<TextInput
label={t('pageOrderPrompt', 'Page order / ranges')}
placeholder={t('pdfOrganiser.placeholder', 'e.g. 1,3,2,4-6')}
value={parameters.pageNumbers}
onChange={(e) => onParameterChange('pageNumbers', e.currentTarget.value)}
disabled={disabled}
/>
</>
)}
</Stack>
);
}
@@ -1,59 +0,0 @@
import { TFunction } from 'i18next';
export const getReorganizePagesModeData = (t: TFunction) => [
{
value: '',
label: t('pdfOrganiser.mode.1', 'Custom Page Order'),
description: t('pdfOrganiser.mode.desc.CUSTOM', 'Use a custom sequence of page numbers or expressions to define a new order.')
},
{
value: 'REVERSE_ORDER',
label: t('pdfOrganiser.mode.2', 'Reverse Order'),
description: t('pdfOrganiser.mode.desc.REVERSE_ORDER', 'Flip the document so the last page becomes first and so on.')
},
{
value: 'DUPLEX_SORT',
label: t('pdfOrganiser.mode.3', 'Duplex Sort'),
description: t('pdfOrganiser.mode.desc.DUPLEX_SORT', 'Interleave fronts then backs as if a duplex scanner scanned all fronts, then all backs (1, n, 2, n-1, …).')
},
{
value: 'BOOKLET_SORT',
label: t('pdfOrganiser.mode.4', 'Booklet Sort'),
description: t('pdfOrganiser.mode.desc.BOOKLET_SORT', 'Arrange pages for booklet printing (last, first, second, second last, …).')
},
{
value: 'SIDE_STITCH_BOOKLET_SORT',
label: t('pdfOrganiser.mode.5', 'Side Stitch Booklet Sort'),
description: t('pdfOrganiser.mode.desc.SIDE_STITCH_BOOKLET_SORT', 'Arrange pages for side‑stitch booklet printing (optimized for binding on the side).')
},
{
value: 'ODD_EVEN_SPLIT',
label: t('pdfOrganiser.mode.6', 'Odd-Even Split'),
description: t('pdfOrganiser.mode.desc.ODD_EVEN_SPLIT', 'Split the document into two outputs: all odd pages and all even pages.')
},
{
value: 'ODD_EVEN_MERGE',
label: t('pdfOrganiser.mode.10', 'Odd-Even Merge'),
description: t('pdfOrganiser.mode.desc.ODD_EVEN_MERGE', 'Merge two PDFs by alternating pages: odd from the first, even from the second.')
},
{
value: 'DUPLICATE',
label: t('pdfOrganiser.mode.11', 'Duplicate all pages'),
description: t('pdfOrganiser.mode.desc.DUPLICATE', 'Duplicate each page according to the custom order count (e.g., 4 duplicates each page 4×).')
},
{
value: 'REMOVE_FIRST',
label: t('pdfOrganiser.mode.7', 'Remove First'),
description: t('pdfOrganiser.mode.desc.REMOVE_FIRST', 'Remove the first page from the document.')
},
{
value: 'REMOVE_LAST',
label: t('pdfOrganiser.mode.8', 'Remove Last'),
description: t('pdfOrganiser.mode.desc.REMOVE_LAST', 'Remove the last page from the document.')
},
{
value: 'REMOVE_FIRST_AND_LAST',
label: t('pdfOrganiser.mode.9', 'Remove First and Last'),
description: t('pdfOrganiser.mode.desc.REMOVE_FIRST_AND_LAST', 'Remove both the first and last pages from the document.')
},
];
@@ -1,108 +0,0 @@
import React from "react";
import { Stack, Text, Select, ColorInput } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { ReplaceColorParameters } from "../../../hooks/tools/replaceColor/useReplaceColorParameters";
interface ReplaceColorSettingsProps {
parameters: ReplaceColorParameters;
onParameterChange: <K extends keyof ReplaceColorParameters>(key: K, value: ReplaceColorParameters[K]) => void;
disabled?: boolean;
}
const ReplaceColorSettings = ({ parameters, onParameterChange, disabled = false }: ReplaceColorSettingsProps) => {
const { t } = useTranslation();
const replaceAndInvertOptions = [
{
value: 'HIGH_CONTRAST_COLOR',
label: t('replaceColor.options.highContrast', 'High contrast')
},
{
value: 'FULL_INVERSION',
label: t('replaceColor.options.invertAll', 'Invert all colours')
},
{
value: 'CUSTOM_COLOR',
label: t('replaceColor.options.custom', 'Custom')
}
];
const highContrastOptions = [
{
value: 'WHITE_TEXT_ON_BLACK',
label: t('replace-color.selectText.6', 'White text on black background')
},
{
value: 'BLACK_TEXT_ON_WHITE',
label: t('replace-color.selectText.7', 'Black text on white background')
},
{
value: 'YELLOW_TEXT_ON_BLACK',
label: t('replace-color.selectText.8', 'Yellow text on black background')
},
{
value: 'GREEN_TEXT_ON_BLACK',
label: t('replace-color.selectText.9', 'Green text on black background')
}
];
return (
<Stack gap="md">
<Stack gap="xs">
<Text size="sm" fw={500}>
{t('replaceColor.labels.colourOperation', 'Colour operation')}
</Text>
<Select
value={parameters.replaceAndInvertOption}
onChange={(value) => value && onParameterChange('replaceAndInvertOption', value as ReplaceColorParameters['replaceAndInvertOption'])}
data={replaceAndInvertOptions}
disabled={disabled}
/>
</Stack>
{parameters.replaceAndInvertOption === 'HIGH_CONTRAST_COLOR' && (
<Stack gap="xs">
<Text size="sm" fw={500}>
{t('replace-color.selectText.5', 'High contrast color options')}
</Text>
<Select
value={parameters.highContrastColorCombination}
onChange={(value) => value && onParameterChange('highContrastColorCombination', value as ReplaceColorParameters['highContrastColorCombination'])}
data={highContrastOptions}
disabled={disabled}
/>
</Stack>
)}
{parameters.replaceAndInvertOption === 'CUSTOM_COLOR' && (
<>
<Stack gap="xs">
<Text size="sm" fw={500}>
{t('replace-color.selectText.10', 'Choose text Color')}
</Text>
<ColorInput
value={parameters.textColor}
onChange={(value) => onParameterChange('textColor', value)}
format="hex"
disabled={disabled}
/>
</Stack>
<Stack gap="xs">
<Text size="sm" fw={500}>
{t('replace-color.selectText.11', 'Choose background Color')}
</Text>
<ColorInput
value={parameters.backGroundColor}
onChange={(value) => onParameterChange('backGroundColor', value)}
format="hex"
disabled={disabled}
/>
</Stack>
</>
)}
</Stack>
);
};
export default ReplaceColorSettings;
@@ -1,40 +0,0 @@
import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';
export const useReplaceColorTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("replaceColor.tooltip.header.title", "Replace & Invert Colour Settings Overview")
},
tips: [
{
title: t("replaceColor.tooltip.description.title", "Description"),
description: t("replaceColor.tooltip.description.text", "Transform PDF colours to improve readability and accessibility. Choose from high contrast presets, invert all colours, or create custom colour schemes.")
},
{
title: t("replaceColor.tooltip.highContrast.title", "High Contrast"),
description: t("replaceColor.tooltip.highContrast.text", "Apply predefined high contrast colour combinations designed for better readability and accessibility compliance."),
bullets: [
t("replaceColor.tooltip.highContrast.bullet1", "White text on black background - Classic dark mode"),
t("replaceColor.tooltip.highContrast.bullet2", "Black text on white background - Standard high contrast"),
t("replaceColor.tooltip.highContrast.bullet3", "Yellow text on black background - High visibility option"),
t("replaceColor.tooltip.highContrast.bullet4", "Green text on black background - Alternative high contrast")
]
},
{
title: t("replaceColor.tooltip.invertAll.title", "Invert All Colours"),
description: t("replaceColor.tooltip.invertAll.text", "Completely invert all colours in the PDF, creating a negative-like effect. Useful for creating dark mode versions of documents or reducing eye strain in low-light conditions.")
},
{
title: t("replaceColor.tooltip.custom.title", "Custom Colours"),
description: t("replaceColor.tooltip.custom.text", "Define your own text and background colours using the colour pickers. Perfect for creating branded documents or specific accessibility requirements."),
bullets: [
t("replaceColor.tooltip.custom.bullet1", "Text colour - Choose the colour for text elements"),
t("replaceColor.tooltip.custom.bullet2", "Background colour - Set the background colour for the document")
]
}
]
};
};
@@ -1,25 +0,0 @@
import { useEffect } from 'react';
import { useExportCapability } from '@embedpdf/plugin-export/react';
import { useViewer } from '../../contexts/ViewerContext';
/**
* Component that runs inside EmbedPDF context and provides export functionality
*/
export function ExportAPIBridge() {
const { provides: exportApi } = useExportCapability();
const { registerBridge } = useViewer();
useEffect(() => {
if (exportApi) {
// Register this bridge with ViewerContext
registerBridge('export', {
state: {
canExport: true,
},
api: exportApi
});
}
}, [exportApi, registerBridge]);
return null;
}
@@ -17,7 +17,6 @@ import { SpreadPluginPackage, SpreadMode } from '@embedpdf/plugin-spread/react';
import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
import { ExportPluginPackage } from '@embedpdf/plugin-export/react';
import { Rotation } from '@embedpdf/models';
import { CustomSearchLayer } from './CustomSearchLayer';
import { ZoomAPIBridge } from './ZoomAPIBridge';
@@ -30,7 +29,6 @@ import { SpreadAPIBridge } from './SpreadAPIBridge';
import { SearchAPIBridge } from './SearchAPIBridge';
import { ThumbnailAPIBridge } from './ThumbnailAPIBridge';
import { RotateAPIBridge } from './RotateAPIBridge';
import { ExportAPIBridge } from './ExportAPIBridge';
interface LocalEmbedPDFProps {
file?: File | Blob;
@@ -114,11 +112,6 @@ export function LocalEmbedPDF({ file, url }: LocalEmbedPDFProps) {
createPluginRegistration(RotatePluginPackage, {
defaultRotation: Rotation.Degree0, // Start with no rotation
}),
// Register export plugin for downloading PDFs
createPluginRegistration(ExportPluginPackage, {
defaultFileName: 'document.pdf',
}),
];
}, [pdfUrl]);
@@ -177,7 +170,6 @@ export function LocalEmbedPDF({ file, url }: LocalEmbedPDFProps) {
<SearchAPIBridge />
<ThumbnailAPIBridge />
<RotateAPIBridge />
<ExportAPIBridge />
<GlobalPointerProvider>
<Viewport
style={{
-47
View File
@@ -51,11 +51,6 @@ interface ThumbnailAPIWrapper {
renderThumb: (pageIndex: number, scale: number) => { toPromise: () => Promise<Blob> };
}
interface ExportAPIWrapper {
download: () => void;
saveAsCopy: () => { toPromise: () => Promise<ArrayBuffer> };
}
// State interfaces - represent the shape of data from each bridge
interface ScrollState {
@@ -98,10 +93,6 @@ interface SearchState {
activeIndex: number;
}
interface ExportState {
canExport: boolean;
}
// Bridge registration interface - bridges register with state and API
interface BridgeRef<TState = unknown, TApi = unknown> {
state: TState;
@@ -131,7 +122,6 @@ interface ViewerContextType {
getRotationState: () => RotationState;
getSearchState: () => SearchState;
getThumbnailAPI: () => ThumbnailAPIWrapper | null;
getExportState: () => ExportState;
// Immediate update callbacks
registerImmediateZoomUpdate: (callback: (percent: number) => void) => void;
@@ -189,11 +179,6 @@ interface ViewerContextType {
clear: () => void;
};
exportActions: {
download: () => void;
saveAsCopy: () => Promise<ArrayBuffer | null>;
};
// Bridge registration - internal use by bridges
registerBridge: (type: string, ref: BridgeRef) => void;
}
@@ -218,7 +203,6 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
spread: null as BridgeRef<SpreadState, SpreadAPIWrapper> | null,
rotation: null as BridgeRef<RotationState, RotationAPIWrapper> | null,
thumbnail: null as BridgeRef<unknown, ThumbnailAPIWrapper> | null,
export: null as BridgeRef<ExportState, ExportAPIWrapper> | null,
});
// Immediate zoom callback for responsive display updates
@@ -254,9 +238,6 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
case 'thumbnail':
bridgeRefs.current.thumbnail = ref as BridgeRef<unknown, ThumbnailAPIWrapper>;
break;
case 'export':
bridgeRefs.current.export = ref as BridgeRef<ExportState, ExportAPIWrapper>;
break;
}
};
@@ -297,10 +278,6 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
return bridgeRefs.current.thumbnail?.api || null;
};
const getExportState = (): ExportState => {
return bridgeRefs.current.export?.state || { canExport: false };
};
// Action handlers - call APIs directly
const scrollActions = {
scrollToPage: (page: number) => {
@@ -496,28 +473,6 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
}
};
const exportActions = {
download: () => {
const api = bridgeRefs.current.export?.api;
if (api?.download) {
api.download();
}
},
saveAsCopy: async () => {
const api = bridgeRefs.current.export?.api;
if (api?.saveAsCopy) {
try {
const result = api.saveAsCopy();
return await result.toPromise();
} catch (error) {
console.error('Failed to save PDF copy:', error);
return null;
}
}
return null;
}
};
const registerImmediateZoomUpdate = (callback: (percent: number) => void) => {
immediateZoomUpdateCallback.current = callback;
};
@@ -552,7 +507,6 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
getRotationState,
getSearchState,
getThumbnailAPI,
getExportState,
// Immediate updates
registerImmediateZoomUpdate,
@@ -568,7 +522,6 @@ export const ViewerProvider: React.FC<ViewerProviderProps> = ({ children }) => {
spreadActions,
rotationActions,
searchActions,
exportActions,
// Bridge registration
registerBridge,
+49 -44
View File
@@ -10,21 +10,17 @@ import AddPassword from "../tools/AddPassword";
import ChangePermissions from "../tools/ChangePermissions";
import RemoveBlanks from "../tools/RemoveBlanks";
import RemovePages from "../tools/RemovePages";
import ReorganizePages from "../tools/ReorganizePages";
import { reorganizePagesOperationConfig } from "../hooks/tools/reorganizePages/useReorganizePagesOperation";
import RemovePassword from "../tools/RemovePassword";
import { SubcategoryId, ToolCategoryId, ToolRegistry } from "./toolsTaxonomy";
import { getSynonyms } from "../utils/toolSynonyms";
import AddWatermark from "../tools/AddWatermark";
import AddStamp from "../tools/AddStamp";
import AddAttachments from "../tools/AddAttachments";
import Merge from '../tools/Merge';
import Repair from "../tools/Repair";
import AutoRename from "../tools/AutoRename";
import SingleLargePage from "../tools/SingleLargePage";
import UnlockPdfForms from "../tools/UnlockPdfForms";
import RemoveCertificateSign from "../tools/RemoveCertificateSign";
import RemoveImage from "../tools/RemoveImage";
import CertSign from "../tools/CertSign";
import BookletImposition from "../tools/BookletImposition";
import Flatten from "../tools/Flatten";
@@ -39,7 +35,6 @@ import { sanitizeOperationConfig } from "../hooks/tools/sanitize/useSanitizeOper
import { repairOperationConfig } from "../hooks/tools/repair/useRepairOperation";
import { addWatermarkOperationConfig } from "../hooks/tools/addWatermark/useAddWatermarkOperation";
import { addStampOperationConfig } from "../components/tools/addStamp/useAddStampOperation";
import { addAttachmentsOperationConfig } from "../hooks/tools/addAttachments/useAddAttachmentsOperation";
import { unlockPdfFormsOperationConfig } from "../hooks/tools/unlockPdfForms/useUnlockPdfFormsOperation";
import { singleLargePageOperationConfig } from "../hooks/tools/singleLargePage/useSingleLargePageOperation";
import { ocrOperationConfig } from "../hooks/tools/ocr/useOCROperation";
@@ -55,8 +50,6 @@ import { redactOperationConfig } from "../hooks/tools/redact/useRedactOperation"
import { rotateOperationConfig } from "../hooks/tools/rotate/useRotateOperation";
import { changeMetadataOperationConfig } from "../hooks/tools/changeMetadata/useChangeMetadataOperation";
import { cropOperationConfig } from "../hooks/tools/crop/useCropOperation";
import { extractImagesOperationConfig } from "../hooks/tools/extractImages/useExtractImagesOperation";
import { replaceColorOperationConfig } from "../hooks/tools/replaceColor/useReplaceColorOperation";
import CompressSettings from "../components/tools/compress/CompressSettings";
import SplitSettings from "../components/tools/split/SplitSettings";
import AddPasswordSettings from "../components/tools/addPassword/AddPasswordSettings";
@@ -75,7 +68,6 @@ import RedactSingleStepSettings from "../components/tools/redact/RedactSingleSte
import RotateSettings from "../components/tools/rotate/RotateSettings";
import Redact from "../tools/Redact";
import AdjustPageScale from "../tools/AdjustPageScale";
import ReplaceColor from "../tools/ReplaceColor";
import ScannerImageSplit from "../tools/ScannerImageSplit";
import { ToolId } from "../types/toolId";
import MergeSettings from '../components/tools/merge/MergeSettings';
@@ -85,9 +77,6 @@ import AdjustPageScaleSettings from "../components/tools/adjustPageScale/AdjustP
import ScannerImageSplitSettings from "../components/tools/scannerImageSplit/ScannerImageSplitSettings";
import ChangeMetadataSingleStep from "../components/tools/changeMetadata/ChangeMetadataSingleStep";
import CropSettings from "../components/tools/crop/CropSettings";
import ExtractImages from "../tools/ExtractImages";
import ExtractImagesSettings from "../components/tools/extractImages/ExtractImagesSettings";
import ReplaceColorSettings from "../components/tools/replaceColor/ReplaceColorSettings";
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
@@ -282,6 +271,18 @@ export function useFlatToolRegistry(): ToolRegistry {
settingsComponent: UnlockPdfFormsSettings,
synonyms: getSynonyms(t, "unlockPDFForms"),
},
manageCertificates: {
icon: <LocalIcon icon="license-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.manageCertificates.title", "Manage Certificates"),
component: null,
description: t(
"home.manageCertificates.desc",
"Import, export, or delete digital certificate files used for signing PDFs."
),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
synonyms: getSynonyms(t, "manageCertificates"),
},
changePermissions: {
icon: <LocalIcon icon="lock-outline" width="1.5rem" height="1.5rem" />,
name: t("home.changePermissions.title", "Change Permissions"),
@@ -383,15 +384,14 @@ export function useFlatToolRegistry(): ToolRegistry {
reorganizePages: {
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.reorganizePages.title", "Reorganize Pages"),
component: ReorganizePages,
component: null,
workbench: "pageEditor",
description: t(
"home.reorganizePages.desc",
"Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING,
endpoints: ["rearrange-pages"],
operationConfig: reorganizePagesOperationConfig,
synonyms: getSynonyms(t, "reorganizePages")
},
scalePages: {
@@ -455,14 +455,12 @@ export function useFlatToolRegistry(): ToolRegistry {
addAttachments: {
icon: <LocalIcon icon="attachment-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.addAttachments.title", "Add Attachments"),
component: AddAttachments,
component: null,
description: t("home.addAttachments.desc", "Add or remove embedded files (attachments) to/from a PDF"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING,
synonyms: getSynonyms(t, "addAttachments"),
maxFiles: 1,
endpoints: ["add-attachments"],
operationConfig: addAttachmentsOperationConfig,
synonyms: getSynonyms(t, "addAttachments")
},
// Extraction
@@ -477,16 +475,12 @@ export function useFlatToolRegistry(): ToolRegistry {
synonyms: getSynonyms(t, "extractPages")
},
extractImages: {
icon: <LocalIcon icon="photo-library-rounded" width="1.5rem" height="1.5rem" />,
icon: <LocalIcon icon="filter-alt" width="1.5rem" height="1.5rem" />,
name: t("home.extractImages.title", "Extract Images"),
component: ExtractImages,
component: null,
description: t("home.extractImages.desc", "Extract images from PDF documents"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.EXTRACTION,
maxFiles: -1,
endpoints: ["extract-images"],
operationConfig: extractImagesOperationConfig,
settingsComponent: ExtractImagesSettings,
synonyms: getSynonyms(t, "extractImages")
},
@@ -525,14 +519,11 @@ export function useFlatToolRegistry(): ToolRegistry {
},
removeImage: {
icon: <LocalIcon icon="remove-selection-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removeImage.title", "Remove Images"),
component: RemoveImage,
description: t("home.removeImage.desc", "Remove all images from a PDF document"),
name: t("home.removeImage.title", "Remove Image"),
component: null,
description: t("home.removeImage.desc", "Remove images from PDF documents"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.REMOVAL,
maxFiles: -1,
endpoints: ["remove-image-pdf"],
operationConfig: undefined,
synonyms: getSynonyms(t, "removeImage"),
},
removePassword: {
@@ -590,6 +581,24 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.AUTOMATION,
synonyms: getSynonyms(t, "autoRename"),
},
autoSplitPDF: {
icon: <LocalIcon icon="split-scene-right-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.autoSplitPDF.title", "Auto Split Pages"),
component: null,
description: t("home.autoSplitPDF.desc", "Automatically split PDF pages based on content detection"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.AUTOMATION,
synonyms: getSynonyms(t, "autoSplitPDF"),
},
autoSizeSplitPDF: {
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.autoSizeSplitPDF.title", "Auto Split by Size/Count"),
component: null,
description: t("home.autoSizeSplitPDF.desc", "Automatically split PDFs by file size or page count"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.AUTOMATION,
synonyms: getSynonyms(t, "autoSizeSplitPDF"),
},
// Advanced Formatting
@@ -637,18 +646,14 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
synonyms: getSynonyms(t, "overlayPdfs"),
},
replaceColor: {
replaceColorPdf: {
icon: <LocalIcon icon="format-color-fill-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.replaceColor.title", "Replace & Invert Color"),
component: ReplaceColor,
description: t("home.replaceColor.desc", "Replace or invert colors in PDF documents"),
name: t("home.replaceColorPdf.title", "Replace & Invert Color"),
component: null,
description: t("home.replaceColorPdf.desc", "Replace or invert colors in PDF documents"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
maxFiles: -1,
endpoints: ["replace-invert-pdf"],
operationConfig: replaceColorOperationConfig,
settingsComponent: ReplaceColorSettings,
synonyms: getSynonyms(t, "replaceColor"),
synonyms: getSynonyms(t, "replaceColorPdf"),
},
addImage: {
icon: <LocalIcon icon="image-rounded" width="1.5rem" height="1.5rem" />,
@@ -668,14 +673,14 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
synonyms: getSynonyms(t, "editTableOfContents"),
},
scannerEffect: {
fakeScan: {
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.scannerEffect.title", "Scanner Effect"),
name: t("home.fakeScan.title", "Scanner Effect"),
component: null,
description: t("home.scannerEffect.desc", "Create a PDF that looks like it was scanned"),
description: t("home.fakeScan.desc", "Create a PDF that looks like it was scanned"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
synonyms: getSynonyms(t, "scannerEffect"),
synonyms: getSynonyms(t, "fakeScan"),
},
// Developer Tools
@@ -1,37 +0,0 @@
import { useTranslation } from 'react-i18next';
import { useToolOperation, ToolOperationConfig, ToolType } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { AddAttachmentsParameters } from './useAddAttachmentsParameters';
const buildFormData = (parameters: AddAttachmentsParameters, file: File): FormData => {
const formData = new FormData();
// Add the main PDF file (single file per request in singleFile mode)
if (file) {
formData.append("fileInput", file);
}
// Add attachment files
(parameters.attachments || []).forEach((attachment) => {
if (attachment) formData.append("attachments", attachment);
});
return formData;
};
// Operation configuration for automation
export const addAttachmentsOperationConfig: ToolOperationConfig<AddAttachmentsParameters> = {
toolType: ToolType.singleFile,
buildFormData,
operationType: 'addAttachments',
endpoint: '/api/v1/misc/add-attachments',
};
export const useAddAttachmentsOperation = () => {
const { t } = useTranslation();
return useToolOperation<AddAttachmentsParameters>({
...addAttachmentsOperationConfig,
getErrorMessage: createStandardErrorHandler(t('addAttachments.error.failed', 'An error occurred while adding attachments to the PDF.'))
});
};
@@ -1,35 +0,0 @@
import { useState } from 'react';
export interface AddAttachmentsParameters {
attachments: File[];
}
const defaultParameters: AddAttachmentsParameters = {
attachments: []
};
export const useAddAttachmentsParameters = () => {
const [parameters, setParameters] = useState<AddAttachmentsParameters>(defaultParameters);
const updateParameter = <K extends keyof AddAttachmentsParameters>(
key: K,
value: AddAttachmentsParameters[K]
) => {
setParameters(prev => ({ ...prev, [key]: value }));
};
const resetParameters = () => {
setParameters(defaultParameters);
};
const validateParameters = (): boolean => {
return parameters.attachments.length > 0;
};
return {
parameters,
updateParameter,
resetParameters,
validateParameters
};
};
@@ -1,51 +0,0 @@
import { useTranslation } from 'react-i18next';
import { useToolOperation, ToolType } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { ExtractImagesParameters, defaultParameters } from './useExtractImagesParameters';
import JSZip from 'jszip';
// Static configuration that can be used by both the hook and automation executor
export const buildExtractImagesFormData = (parameters: ExtractImagesParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
formData.append("format", parameters.format);
formData.append("allowDuplicates", parameters.allowDuplicates.toString());
return formData;
};
// Response handler for extract-images which returns a ZIP file
const extractImagesResponseHandler = async (responseData: Blob, _originalFiles: File[]): Promise<File[]> => {
const zip = new JSZip();
const zipContent = await zip.loadAsync(responseData);
const extractedFiles: File[] = [];
for (const [filename, file] of Object.entries(zipContent.files)) {
if (!file.dir) {
const blob = await file.async('blob');
const extractedFile = new File([blob], filename, { type: blob.type });
extractedFiles.push(extractedFile);
}
}
return extractedFiles;
};
// Static configuration object
export const extractImagesOperationConfig = {
toolType: ToolType.singleFile,
buildFormData: buildExtractImagesFormData,
operationType: 'extractImages',
endpoint: '/api/v1/misc/extract-images',
defaultParameters,
// Extract-images returns a ZIP file containing multiple image files
responseHandler: extractImagesResponseHandler,
} as const;
export const useExtractImagesOperation = () => {
const { t } = useTranslation();
return useToolOperation<ExtractImagesParameters>({
...extractImagesOperationConfig,
getErrorMessage: createStandardErrorHandler(t('extractImages.error.failed', 'An error occurred while extracting images from the PDF.'))
});
};
@@ -1,19 +0,0 @@
import { useBaseParameters } from '../shared/useBaseParameters';
export interface ExtractImagesParameters {
format: 'png' | 'jpg' | 'gif';
allowDuplicates: boolean;
}
export const defaultParameters: ExtractImagesParameters = {
format: 'png',
allowDuplicates: false,
};
export const useExtractImagesParameters = () => {
return useBaseParameters<ExtractImagesParameters>({
defaultParameters,
endpointName: 'extract-images',
validateFn: () => true, // All parameters have valid defaults
});
};
@@ -1,30 +0,0 @@
import { useTranslation } from 'react-i18next';
import { useToolOperation, ToolOperationConfig, ToolType } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import type { RemoveImageParameters } from './useRemoveImageParameters';
export const buildRemoveImageFormData = (_params: RemoveImageParameters, file: File): FormData => {
const formData = new FormData();
formData.append('fileInput', file);
return formData;
};
export const removeImageOperationConfig: ToolOperationConfig<RemoveImageParameters> = {
toolType: ToolType.singleFile,
buildFormData: buildRemoveImageFormData,
operationType: 'removeImage',
endpoint: '/api/v1/general/remove-image-pdf',
};
export const useRemoveImageOperation = () => {
const { t } = useTranslation();
return useToolOperation<RemoveImageParameters>({
...removeImageOperationConfig,
getErrorMessage: createStandardErrorHandler(
t('removeImage.error.failed', 'Failed to remove images from the PDF.')
),
});
};
@@ -1,17 +0,0 @@
import { useBaseParameters } from '../shared/useBaseParameters';
import type { BaseParametersHook } from '../shared/useBaseParameters';
export type RemoveImageParameters = Record<string, never>;
export const defaultParameters: RemoveImageParameters = {};
export type RemoveImageParametersHook = BaseParametersHook<RemoveImageParameters>;
export const useRemoveImageParameters = (): RemoveImageParametersHook => {
return useBaseParameters({
defaultParameters,
endpointName: 'remove-image-pdf',
});
};
@@ -1,36 +0,0 @@
import { useTranslation } from 'react-i18next';
import { ToolOperationConfig, ToolType, useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { ReorganizePagesParameters } from './useReorganizePagesParameters';
const buildFormData = (parameters: ReorganizePagesParameters, file: File): FormData => {
const formData = new FormData();
formData.append('fileInput', file);
if (parameters.customMode) {
formData.append('customMode', parameters.customMode);
}
if (parameters.pageNumbers) {
const cleaned = parameters.pageNumbers.replace(/\s+/g, '');
formData.append('pageNumbers', cleaned);
}
return formData;
};
export const reorganizePagesOperationConfig: ToolOperationConfig<ReorganizePagesParameters> = {
toolType: ToolType.singleFile,
buildFormData,
operationType: 'reorganizePages',
endpoint: '/api/v1/general/rearrange-pages',
};
export const useReorganizePagesOperation = () => {
const { t } = useTranslation();
return useToolOperation<ReorganizePagesParameters>({
...reorganizePagesOperationConfig,
getErrorMessage: createStandardErrorHandler(
t('reorganizePages.error.failed', 'Failed to reorganize pages')
)
});
};
@@ -1,39 +0,0 @@
import { useState } from 'react';
export interface ReorganizePagesParameters {
customMode: string; // empty string means custom order using pageNumbers
pageNumbers: string; // e.g. "1,3,2,4-6"
}
export const defaultReorganizePagesParameters: ReorganizePagesParameters = {
customMode: '',
pageNumbers: '',
};
export const useReorganizePagesParameters = () => {
const [parameters, setParameters] = useState<ReorganizePagesParameters>(defaultReorganizePagesParameters);
const updateParameter = <K extends keyof ReorganizePagesParameters>(
key: K,
value: ReorganizePagesParameters[K]
) => {
setParameters(prev => ({ ...prev, [key]: value }));
};
const resetParameters = () => setParameters(defaultReorganizePagesParameters);
// If customMode is '' (custom) or 'DUPLICATE', a page order is required; otherwise it's optional/ignored
const validateParameters = (): boolean => {
const requiresOrder = parameters.customMode === '' || parameters.customMode === 'DUPLICATE';
return requiresOrder ? parameters.pageNumbers.trim().length > 0 : true;
};
return {
parameters,
updateParameter,
resetParameters,
validateParameters,
};
};
@@ -1,38 +0,0 @@
import { useTranslation } from 'react-i18next';
import { ToolType, useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { ReplaceColorParameters, defaultParameters } from './useReplaceColorParameters';
export const buildReplaceColorFormData = (parameters: ReplaceColorParameters, file: File): FormData => {
const formData = new FormData();
formData.append('fileInput', file);
formData.append('replaceAndInvertOption', parameters.replaceAndInvertOption);
if (parameters.replaceAndInvertOption === 'HIGH_CONTRAST_COLOR') {
formData.append('highContrastColorCombination', parameters.highContrastColorCombination);
} else if (parameters.replaceAndInvertOption === 'CUSTOM_COLOR') {
formData.append('textColor', parameters.textColor);
formData.append('backGroundColor', parameters.backGroundColor);
}
return formData;
};
export const replaceColorOperationConfig = {
toolType: ToolType.singleFile,
buildFormData: buildReplaceColorFormData,
operationType: 'replaceColor',
endpoint: '/api/v1/misc/replace-invert-pdf',
multiFileEndpoint: false,
defaultParameters,
} as const;
export const useReplaceColorOperation = () => {
const { t } = useTranslation();
return useToolOperation<ReplaceColorParameters>({
...replaceColorOperationConfig,
getErrorMessage: createStandardErrorHandler(t('replaceColor.error.failed', 'An error occurred while processing the color replacement.'))
});
};
@@ -1,29 +0,0 @@
import { BaseParameters } from '../../../types/parameters';
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
export interface ReplaceColorParameters extends BaseParameters {
replaceAndInvertOption: 'HIGH_CONTRAST_COLOR' | 'CUSTOM_COLOR' | 'FULL_INVERSION';
highContrastColorCombination: 'WHITE_TEXT_ON_BLACK' | 'BLACK_TEXT_ON_WHITE' | 'YELLOW_TEXT_ON_BLACK' | 'GREEN_TEXT_ON_BLACK';
textColor: string;
backGroundColor: string;
}
export const defaultParameters: ReplaceColorParameters = {
replaceAndInvertOption: 'HIGH_CONTRAST_COLOR',
highContrastColorCombination: 'WHITE_TEXT_ON_BLACK',
textColor: '#000000',
backGroundColor: '#ffffff',
};
export type ReplaceColorParametersHook = BaseParametersHook<ReplaceColorParameters>;
export const useReplaceColorParameters = (): ReplaceColorParametersHook => {
return useBaseParameters({
defaultParameters,
endpointName: 'replace-invert-pdf',
validateFn: () => {
// All parameters are always valid as they have defaults
return true;
},
});
};
-197
View File
@@ -1,197 +0,0 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useFileSelection } from "../contexts/FileContext";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { BaseToolProps, ToolComponent } from "../types/tool";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useAddAttachmentsParameters } from "../hooks/tools/addAttachments/useAddAttachmentsParameters";
import { useAddAttachmentsOperation } from "../hooks/tools/addAttachments/useAddAttachmentsOperation";
import { Stack, Text, Group, ActionIcon, Alert, ScrollArea, Button } from "@mantine/core";
import LocalIcon from "../components/shared/LocalIcon";
import { useAccordionSteps } from "../hooks/tools/shared/useAccordionSteps";
// Removed FitText for two-line wrapping with clamping
const AddAttachments = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation();
const { selectedFiles } = useFileSelection();
const params = useAddAttachmentsParameters();
const operation = useAddAttachmentsOperation();
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("add-attachments");
useEffect(() => {
operation.resetResults();
onPreviewFile?.(null);
}, [params.parameters]);
const handleExecute = async () => {
try {
await operation.executeOperation(params.parameters, selectedFiles);
if (operation.files && onComplete) {
onComplete(operation.files);
}
} catch (error: any) {
onError?.(error?.message || t("AddAttachmentsRequest.error.failed", "Add attachments operation failed"));
}
};
const hasFiles = selectedFiles.length > 0;
const hasResults = operation.files.length > 0 || operation.downloadUrl !== null;
enum AddAttachmentsStep {
NONE = 'none',
ATTACHMENTS = 'attachments'
}
const accordion = useAccordionSteps<AddAttachmentsStep>({
noneValue: AddAttachmentsStep.NONE,
initialStep: AddAttachmentsStep.ATTACHMENTS,
stateConditions: {
hasFiles,
hasResults: false // Don't collapse when there are results for add attachments
},
afterResults: () => {
operation.resetResults();
onPreviewFile?.(null);
}
});
const getSteps = () => {
const steps: any[] = [];
// Step 1: Attachments Selection
steps.push({
title: t("AddAttachmentsRequest.attachments", "Select Attachments"),
isCollapsed: accordion.getCollapsedState(AddAttachmentsStep.ATTACHMENTS),
onCollapsedClick: () => accordion.handleStepToggle(AddAttachmentsStep.ATTACHMENTS),
isVisible: true,
content: (
<Stack gap="md">
<Alert color="blue" variant="light">
<Text size="sm">
{t("AddAttachmentsRequest.info", "Select files to attach to your PDF. These files will be embedded and accessible through the PDF's attachment panel.")}
</Text>
</Alert>
<Stack gap="xs">
<Text size="sm" fw={500}>
{t("AddAttachmentsRequest.selectFiles", "Select Files to Attach")}
</Text>
<input
type="file"
multiple
onChange={(e) => {
const files = Array.from(e.target.files || []);
// Append to existing attachments instead of replacing
const newAttachments = [...params.parameters.attachments, ...files];
params.updateParameter('attachments', newAttachments);
// Reset the input so the same file can be selected again
e.target.value = '';
}}
disabled={endpointLoading}
style={{ display: 'none' }}
id="attachments-input"
/>
<Button
size="xs"
color="blue"
component="label"
htmlFor="attachments-input"
disabled={endpointLoading}
leftSection={<LocalIcon icon="plus" width="14" height="14" />}
>
{params.parameters.attachments.length > 0
? t("AddAttachmentsRequest.addMoreFiles", "Add more files...")
: t("AddAttachmentsRequest.placeholder", "Choose files...")
}
</Button>
</Stack>
{params.parameters.attachments && params.parameters.attachments.length > 0 && (
<Stack gap="xs">
<Text size="sm" fw={500}>
{t("AddAttachmentsRequest.selectedFiles", "Selected Files")} ({params.parameters.attachments.length})
</Text>
<ScrollArea.Autosize mah={300} type="scroll" offsetScrollbars styles={{ viewport: { overflowX: 'hidden' } }}>
<Stack gap="xs">
{params.parameters.attachments.map((file, index) => (
<Group key={index} justify="space-between" p="xs" style={{ border: '1px solid var(--mantine-color-gray-3)', borderRadius: 'var(--mantine-radius-sm)', alignItems: 'flex-start' }}>
<Group gap="xs" style={{ flex: 1, minWidth: 0, alignItems: 'flex-start' }}>
{/* Filename (two-line clamp, wraps, no icon on the left) */}
<div style={{ flex: 1, minWidth: 0 }}>
<div
style={{
fontSize: 'var(--mantine-font-size-sm)',
fontWeight: 400,
lineHeight: 1.2,
display: '-webkit-box',
WebkitLineClamp: 2 as any,
WebkitBoxOrient: 'vertical' as any,
overflow: 'hidden',
whiteSpace: 'normal',
wordBreak: 'break-word',
}}
title={file.name}
>
{file.name}
</div>
</div>
<Text size="xs" c="dimmed" style={{ flexShrink: 0 }}>
({(file.size / 1024).toFixed(1)} KB)
</Text>
</Group>
<ActionIcon
size="sm"
variant="subtle"
color="red"
style={{ flexShrink: 0 }}
onClick={() => {
const newAttachments = params.parameters.attachments.filter((_, i) => i !== index);
params.updateParameter('attachments', newAttachments);
}}
>
<LocalIcon icon="close-rounded" width="14" height="14" />
</ActionIcon>
</Group>
))}
</Stack>
</ScrollArea.Autosize>
</Stack>
)}
</Stack>
),
});
return steps;
};
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasResults,
},
steps: getSteps(),
executeButton: {
text: t('AddAttachmentsRequest.submit', 'Add Attachments'),
isVisible: !hasResults,
loadingText: t('loading'),
onClick: handleExecute,
disabled: !params.validateParameters() || !hasFiles || !endpointEnabled,
},
review: {
isVisible: hasResults,
operation: operation,
title: t('AddAttachmentsRequest.results.title', 'Attachment Results'),
onFileClick: (file) => onPreviewFile?.(file),
onUndo: async () => {
await operation.undoOperation();
onPreviewFile?.(null);
},
},
});
};
AddAttachments.tool = () => useAddAttachmentsOperation;
export default AddAttachments as ToolComponent;
-55
View File
@@ -1,55 +0,0 @@
import { useTranslation } from "react-i18next";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import ExtractImagesSettings from "../components/tools/extractImages/ExtractImagesSettings";
import { useExtractImagesParameters } from "../hooks/tools/extractImages/useExtractImagesParameters";
import { useExtractImagesOperation } from "../hooks/tools/extractImages/useExtractImagesOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const ExtractImages = (props: BaseToolProps) => {
const { t } = useTranslation();
const base = useBaseTool(
'extractImages',
useExtractImagesParameters,
useExtractImagesOperation,
props
);
return createToolFlow({
files: {
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
},
steps: [
{
title: t("extractImages.settings.title", "Settings"),
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
content: (
<ExtractImagesSettings
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
],
executeButton: {
text: t("extractImages.submit", "Extract Images"),
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: base.hasResults,
operation: base.operation,
title: t("extractImages.title", "Extracted Images"),
onFileClick: base.handleThumbnailClick,
onUndo: base.handleUndo,
},
});
};
export default ExtractImages as ToolComponent;
-45
View File
@@ -1,45 +0,0 @@
import { useTranslation } from "react-i18next";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { useRemoveImageParameters } from "../hooks/tools/removeImage/useRemoveImageParameters";
import { useRemoveImageOperation } from "../hooks/tools/removeImage/useRemoveImageOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
const RemoveImage = (props: BaseToolProps) => {
const { t } = useTranslation();
const base = useBaseTool(
'removeImage',
useRemoveImageParameters,
useRemoveImageOperation,
props
);
return createToolFlow({
files: {
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
},
steps: [],
executeButton: {
text: t("removeImage.submit", "Remove Images"),
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: base.hasResults,
operation: base.operation,
title: t("removeImage.results.title", "Remove Images Results"),
onFileClick: base.handleThumbnailClick,
onUndo: base.handleUndo,
},
});
};
RemoveImage.tool = () => useRemoveImageOperation;
export default RemoveImage as ToolComponent;
-104
View File
@@ -1,104 +0,0 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import { BaseToolProps, ToolComponent } from "../types/tool";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
import { useFileSelection } from "../contexts/FileContext";
import { useAccordionSteps } from "../hooks/tools/shared/useAccordionSteps";
import ReorganizePagesSettings from "../components/tools/reorganizePages/ReorganizePagesSettings";
import { useReorganizePagesParameters } from "../hooks/tools/reorganizePages/useReorganizePagesParameters";
import { useReorganizePagesOperation } from "../hooks/tools/reorganizePages/useReorganizePagesOperation";
const ReorganizePages = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation();
const { selectedFiles } = useFileSelection();
const params = useReorganizePagesParameters();
const operation = useReorganizePagesOperation();
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("rearrange-pages");
useEffect(() => {
operation.resetResults();
onPreviewFile?.(null);
}, [params.parameters]);
const handleExecute = async () => {
try {
await operation.executeOperation(params.parameters, selectedFiles);
if (operation.files && onComplete) {
onComplete(operation.files);
}
} catch (error: any) {
onError?.(error?.message || t("reorganizePages.error.failed", "Failed to reorganize pages"));
}
};
const hasFiles = selectedFiles.length > 0;
const hasResults = operation.files.length > 0 || operation.downloadUrl !== null;
enum Step {
NONE = 'none',
SETTINGS = 'settings'
}
const accordion = useAccordionSteps<Step>({
noneValue: Step.NONE,
initialStep: Step.SETTINGS,
stateConditions: {
hasFiles,
hasResults
},
afterResults: () => {
operation.resetResults();
onPreviewFile?.(null);
}
});
const steps = [
{
title: t("reorganizePages.settings.title", "Settings"),
isCollapsed: accordion.getCollapsedState(Step.SETTINGS),
onCollapsedClick: () => accordion.handleStepToggle(Step.SETTINGS),
isVisible: true,
content: (
<ReorganizePagesSettings
parameters={params.parameters}
onParameterChange={params.updateParameter}
disabled={endpointLoading}
/>
),
}
];
return createToolFlow({
files: {
selectedFiles,
isCollapsed: hasResults,
},
steps,
executeButton: {
text: t('reorganizePages.submit', 'Reorganize Pages'),
isVisible: !hasResults,
loadingText: t('loading'),
onClick: handleExecute,
disabled: !params.validateParameters() || !hasFiles || !endpointEnabled,
},
review: {
isVisible: hasResults,
operation: operation,
title: t('reorganizePages.results.title', 'Pages Reorganized'),
onFileClick: (file) => onPreviewFile?.(file),
onUndo: async () => {
await operation.undoOperation();
onPreviewFile?.(null);
},
},
});
};
(ReorganizePages as any).tool = () => useReorganizePagesOperation;
export default ReorganizePages as ToolComponent;
-58
View File
@@ -1,58 +0,0 @@
import { useTranslation } from "react-i18next";
import { createToolFlow } from "../components/tools/shared/createToolFlow";
import ReplaceColorSettings from "../components/tools/replaceColor/ReplaceColorSettings";
import { useReplaceColorParameters } from "../hooks/tools/replaceColor/useReplaceColorParameters";
import { useReplaceColorOperation } from "../hooks/tools/replaceColor/useReplaceColorOperation";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool";
import { useReplaceColorTips } from "../components/tooltips/useReplaceColorTips";
const ReplaceColor = (props: BaseToolProps) => {
const { t } = useTranslation();
const replaceColorTips = useReplaceColorTips();
const base = useBaseTool(
'replaceColor',
useReplaceColorParameters,
useReplaceColorOperation,
props
);
return createToolFlow({
files: {
selectedFiles: base.selectedFiles,
isCollapsed: base.hasResults,
},
steps: [
{
title: t("replaceColor.labels.settings", "Settings"),
isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
tooltip: replaceColorTips,
content: (
<ReplaceColorSettings
parameters={base.params.parameters}
onParameterChange={base.params.updateParameter}
disabled={base.endpointLoading}
/>
),
},
],
executeButton: {
text: t("replace-color.submit", "Replace"),
isVisible: !base.hasResults,
loadingText: t("loading"),
onClick: base.handleExecute,
disabled: !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled,
},
review: {
isVisible: base.hasResults,
operation: base.operation,
title: t("replace-color.title", "Replace-Invert-Color"),
onFileClick: base.handleThumbnailClick,
onUndo: base.handleUndo,
},
});
};
export default ReplaceColor as ToolComponent;
+5 -2
View File
@@ -11,6 +11,8 @@ export const TOOL_IDS = [
'changePermissions',
'watermark',
'sanitize',
'autoSplitPDF',
'autoSizeSplitPDF',
'split',
'merge',
'convert',
@@ -19,7 +21,7 @@ export const TOOL_IDS = [
'rotate',
'scannerImageSplit',
'editTableOfContents',
'scannerEffect',
'fakeScan',
'autoRename',
'pageLayout',
'scalePages',
@@ -42,11 +44,12 @@ export const TOOL_IDS = [
'addAttachments',
'changeMetadata',
'overlayPdfs',
'manageCertificates',
'getPdfInfo',
'validateSignature',
'read',
'automate',
'replaceColor',
'replaceColorPdf',
'showJS',
'devApi',
'devFolderScanning',
+10 -9
View File
@@ -39,6 +39,7 @@ export const URL_TO_TOOL_MAP: Record<string, ToolId> = {
'/remove-cert-sign': 'removeCertSign',
'/unlock-pdf-forms': 'unlockPDFForms',
'/validate-signature': 'validateSignature',
'/manage-certificates': 'manageCertificates',
// Content manipulation
'/sanitize': 'sanitize',
@@ -63,8 +64,8 @@ export const URL_TO_TOOL_MAP: Record<string, ToolId> = {
'/booklet-imposition': 'bookletImposition',
// Splitting tools
'/auto-split-pdf': 'split',
'/auto-size-split-pdf': 'split',
'/auto-split-pdf': 'autoSplitPDF',
'/auto-size-split-pdf': 'autoSizeSplitPDF',
'/scanner-image-split': 'scannerImageSplit',
// Annotation and content removal
@@ -74,8 +75,8 @@ export const URL_TO_TOOL_MAP: Record<string, ToolId> = {
// Image and visual tools
'/extract-images': 'extractImages',
'/adjust-contrast': 'adjustContrast',
'/fake-scan': 'scannerEffect',
'/replace-color-pdf': 'replaceColor',
'/fake-scan': 'fakeScan',
'/replace-color-pdf': 'replaceColorPdf',
// Metadata and info
'/change-metadata': 'changeMetadata',
@@ -115,13 +116,13 @@ export const URL_TO_TOOL_MAP: Record<string, ToolId> = {
'/view-pdf': 'read',
'/get-info-on-pdf': 'getPdfInfo',
'/remove-image-pdf': 'removeImage',
'/replace-and-invert-color-pdf': 'replaceColor',
'/replace-and-invert-color-pdf': 'replaceColorPdf',
'/pipeline': 'automate',
'/extract-image-scans': 'scannerImageSplit',
'/show-javascript': 'showJS',
'/scanner-effect': 'scannerEffect',
'/split-by-size-or-count': 'split',
'/scanner-effect': 'fakeScan',
'/split-by-size-or-count': 'autoSizeSplitPDF',
'/overlay-pdf': 'overlayPdfs',
'/split-pdf-by-sections': 'split',
'/split-pdf-by-chapters': 'split',
'/split-pdf-by-sections': 'autoSplitPDF',
'/split-pdf-by-chapters': 'autoSplitPDF',
};