Compare commits

...
15 Commits
22 changed files with 388 additions and 292 deletions
+128 -41
View File
@@ -102,6 +102,7 @@
"postcss-preset-mantine": "^1.18.0",
"postcss-simple-vars": "^7.0.1",
"puppeteer": "^24.25.0",
"rollup-plugin-visualizer": "^6.0.5",
"typescript": "^5.9.2",
"typescript-eslint": "^8.44.1",
"vite": "^7.1.7",
@@ -457,7 +458,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@@ -501,7 +501,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@@ -582,7 +581,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.5.0.tgz",
"integrity": "sha512-Yrh9XoVaT8cUgzgqpJ7hx5wg6BqQrCFirqqlSwVb+Ly9oNn4fZbR9GycIWmzJOU5XBnaOJjXfQSaDyoNP0woNA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@embedpdf/engines": "1.5.0",
"@embedpdf/models": "1.5.0"
@@ -682,7 +680,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.5.0.tgz",
"integrity": "sha512-p7PTNNaIr4gH3jLwX+eLJe1DeUXgi21kVGN6SRx/pocH8esg4jqoOeD/YiRRZoZnPOiy0jBXVhkPkwSmY7a2hQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@embedpdf/models": "1.5.0"
},
@@ -699,7 +696,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.5.0.tgz",
"integrity": "sha512-ckHgTfvkW6c5Ta7Mc+Dl9C2foVnvEpqEJ84wyBnqrU0OWbe/jsiPhyKBVeartMGqNI/kVfaQTXupyrKhekAVmg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@embedpdf/models": "1.5.0"
},
@@ -717,7 +713,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.5.0.tgz",
"integrity": "sha512-P4YpIZfaW69etYIjphyaL4cGl2pB14h3OdTE0tRQ2pZYZHFLTvlt4q9B3PVSdhlSrHK5nob7jfLGon2U7xCslg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@embedpdf/models": "1.5.0"
},
@@ -771,7 +766,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.5.0.tgz",
"integrity": "sha512-ywwSj0ByrlkvrJIHKRzqxARkOZriki8VJUC+T4MV8fGyF4CzvCRJyKlPktahFz+VxhoodqTh7lBCib68dH+GvA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@embedpdf/models": "1.5.0"
},
@@ -806,7 +800,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.5.0.tgz",
"integrity": "sha512-RNmTZCZ8X1mA8cw9M7TMDuhO9GtkOalGha2bBL3En3D1IlDRS7PzNNMSMV7eqT7OQICSTltlpJ8p8Qi5esvL/Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"@embedpdf/models": "1.5.0"
},
@@ -843,7 +836,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.5.0.tgz",
"integrity": "sha512-zrxLBAZQoPswDuf9q9DrYaQc6B0Ysc2U1hueTjNH/4+ydfl0BFXZkKR63C2e3YmWtXvKjkoIj0GyPzsiBORLUw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@embedpdf/models": "1.5.0"
},
@@ -919,7 +911,6 @@
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.5.0.tgz",
"integrity": "sha512-G8GDyYRhfehw72+r4qKkydnA5+AU8qH67g01Y12b0DzI0VIzymh/05Z4dK8DsY3jyWPXJfw2hlg5+KDHaMBHgQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@embedpdf/models": "1.5.0"
},
@@ -1075,7 +1066,6 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -1119,7 +1109,6 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@@ -2150,7 +2139,6 @@
"resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.6.tgz",
"integrity": "sha512-paTl+0x+O/QtgMtqVJaG8maD8sfiOdgPmLOyG485FmeGZ1L3KMdEkhxZtmdGlDFsLXhmMGQ57ducT90bvhXX5A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@floating-ui/react": "^0.27.16",
"clsx": "^2.1.1",
@@ -2201,7 +2189,6 @@
"resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.6.tgz",
"integrity": "sha512-liHfaWXHAkLjJy+Bkr29UsCwAoDQ/a64WrM67lksx8F0qqyjR5RQH8zVlhuOjdpQnwtlUkE/YiTvbJiPcoI0bw==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"react": "^18.x || ^19.x"
}
@@ -2269,7 +2256,6 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz",
"integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.28.4",
"@mui/core-downloads-tracker": "^7.3.5",
@@ -3202,7 +3188,6 @@
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.9.0.tgz",
"integrity": "sha512-ggs5k+/0FUJcIgNY08aZTqpBTtbExkJMYMLSMwyucrhtWexVOEY1KJmhBsxf+E/Q15f5rbwBpj+t0t2AW2oCsQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12.16"
}
@@ -3321,6 +3306,7 @@
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
"integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"acorn": "^8.9.0"
}
@@ -4097,7 +4083,6 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -4426,7 +4411,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4437,7 +4421,6 @@
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -4507,7 +4490,6 @@
"integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.3",
"@typescript-eslint/types": "8.46.3",
@@ -5221,6 +5203,7 @@
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
"integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/shared": "3.5.24"
}
@@ -5230,6 +5213,7 @@
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
"integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.24",
"@vue/shared": "3.5.24"
@@ -5240,6 +5224,7 @@
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
"integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/reactivity": "3.5.24",
"@vue/runtime-core": "3.5.24",
@@ -5252,6 +5237,7 @@
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
"integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-ssr": "3.5.24",
"@vue/shared": "3.5.24"
@@ -5278,7 +5264,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5686,6 +5671,7 @@
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">= 0.4"
}
@@ -5962,7 +5948,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.19",
"caniuse-lite": "^1.0.30001751",
@@ -6758,6 +6743,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
@@ -7010,8 +7005,7 @@
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz",
"integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true
"license": "BSD-3-Clause"
},
"node_modules/dezalgo": {
"version": "1.0.4",
@@ -7406,7 +7400,6 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -7577,7 +7570,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -7744,7 +7736,8 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/espree": {
"version": "10.4.0",
@@ -7809,6 +7802,7 @@
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz",
"integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
}
@@ -8899,7 +8893,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.27.6"
},
@@ -9222,6 +9215,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true,
"license": "MIT",
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -9376,6 +9385,7 @@
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "^1.0.6"
}
@@ -9580,6 +9590,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -9696,7 +9719,6 @@
"integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@acemir/cssom": "^0.9.19",
"@asamuzakjp/dom-selector": "^6.7.3",
@@ -10283,7 +10305,8 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/locate-path": {
"version": "6.0.0",
@@ -10961,6 +10984,24 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/open": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
"is-wsl": "^2.2.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -11442,7 +11483,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -11722,7 +11762,6 @@
"resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz",
"integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==",
"license": "MIT",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@@ -12105,7 +12144,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -12115,7 +12153,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -12728,6 +12765,60 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rollup-plugin-visualizer": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.5.tgz",
"integrity": "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==",
"dev": true,
"license": "MIT",
"dependencies": {
"open": "^8.0.0",
"picomatch": "^4.0.2",
"source-map": "^0.7.4",
"yargs": "^17.5.1"
},
"bin": {
"rollup-plugin-visualizer": "dist/bin/cli.js"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"rolldown": "1.x || ^1.0.0-beta",
"rollup": "2.x || 3.x || 4.x"
},
"peerDependenciesMeta": {
"rolldown": {
"optional": true
},
"rollup": {
"optional": true
}
}
},
"node_modules/rollup-plugin-visualizer/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/rollup-plugin-visualizer/node_modules/source-map": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
"integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">= 12"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -13627,6 +13718,7 @@
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">= 0.4"
}
@@ -13835,7 +13927,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -14137,7 +14228,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -14219,7 +14309,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"napi-postinstall": "^0.3.0"
},
@@ -14424,7 +14513,6 @@
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -14595,7 +14683,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -14609,7 +14696,6 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -15221,7 +15307,8 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/zod": {
"version": "3.25.76",
+2
View File
@@ -68,6 +68,7 @@
"scripts": {
"predev": "npm run generate-icons",
"dev": "vite",
"clean": "rm -rf dist",
"prebuild": "npm run generate-icons",
"lint": "eslint --max-warnings=0",
"build": "vite build",
@@ -149,6 +150,7 @@
"postcss-preset-mantine": "^1.18.0",
"postcss-simple-vars": "^7.0.1",
"puppeteer": "^24.25.0",
"rollup-plugin-visualizer": "^6.0.5",
"typescript": "^5.9.2",
"typescript-eslint": "^8.44.1",
"vite": "^7.1.7",
@@ -1,5 +1,7 @@
import { FileId } from '@app/types/file';
import { PDFDocument, PDFPage, PageBreakSettings } from '@app/types/pageEditor';
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
import { thumbnailGenerationService } from '@app/services/thumbnailGenerationService';
// V1-style DOM-first command system (replaces the old React state commands)
export abstract class DOMCommand {
@@ -727,8 +729,6 @@ export class InsertFilesCommand extends DOMCommand {
private async generateThumbnailsForInsertedPages(updatedDocument: PDFDocument): Promise<void> {
try {
const { thumbnailGenerationService } = await import('@app/services/thumbnailGenerationService');
// Group pages by file ID to generate thumbnails efficiently
const pagesByFileId = new Map<FileId, PDFPage[]>();
@@ -809,7 +809,6 @@ export class InsertFilesCommand extends DOMCommand {
const clonedArrayBuffer = arrayBuffer.slice(0);
// Use PDF.js via the worker manager to extract pages
const { pdfWorkerManager } = await import('@app/services/pdfWorkerManager');
const pdf = await pdfWorkerManager.createDocument(clonedArrayBuffer);
const pageCount = pdf.numPages;
@@ -1,4 +1,5 @@
import { Dispatch, SetStateAction, useCallback } from "react";
import JSZip from "jszip";
import type {
useFileActions,
@@ -194,8 +195,7 @@ export const usePageEditorExport = ({
);
if (files.length > 1) {
const JSZip = await import("jszip");
const zip = new JSZip.default();
const zip = new JSZip();
files.forEach((file) => {
zip.file(file.name, file);
@@ -1,4 +1,4 @@
import React, { useMemo, useState, useEffect, useCallback } from 'react';
import React, { useMemo, useState, useEffect, useCallback, Suspense } from 'react';
import { Modal, Text, ActionIcon, Tooltip, Group } from '@mantine/core';
import { useNavigate, useLocation } from 'react-router-dom';
import LocalIcon from '@app/components/shared/LocalIcon';
@@ -223,7 +223,9 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({ opened, onClose })
</ActionIcon>
</div>
<div className="modal-body">
{activeComponent}
<Suspense fallback={<div style={{ padding: '2rem', textAlign: 'center' }}>Loading...</div>}>
{activeComponent}
</Suspense>
</div>
</div>
</div>
@@ -1,4 +1,4 @@
import React, { useState, useRef, forwardRef, useEffect } from "react";
import React, { useState, useRef, forwardRef, useEffect, lazy, Suspense } from "react";
import { Stack, Divider, Menu, Indicator } from "@mantine/core";
import { useTranslation } from 'react-i18next';
import { useNavigate, useLocation } from 'react-router-dom';
@@ -14,8 +14,10 @@ import '@app/components/shared/quickAccessBar/QuickAccessBar.css';
import { Tooltip } from '@app/components/shared/Tooltip';
import AllToolsNavButton from '@app/components/shared/AllToolsNavButton';
import ActiveToolButton from "@app/components/shared/quickAccessBar/ActiveToolButton";
import AppConfigModal from '@app/components/shared/AppConfigModal';
import { useAppConfig } from '@app/contexts/AppConfigContext';
// Lazy-load AppConfigModal
const AppConfigModal = lazy(() => import('@app/components/shared/AppConfigModal'));
import { useLicenseAlert } from "@app/hooks/useLicenseAlert";
import { requestStartTour } from '@app/constants/events';
import QuickAccessButton from '@app/components/shared/quickAccessBar/QuickAccessButton';
@@ -365,10 +367,12 @@ const QuickAccessBar = forwardRef<HTMLDivElement>((_, ref) => {
</div>
</div>
<AppConfigModal
opened={configModalOpen}
onClose={() => setConfigModalOpen(false)}
/>
<Suspense fallback={null}>
<AppConfigModal
opened={configModalOpen}
onClose={() => setConfigModalOpen(false)}
/>
</Suspense>
</div>
);
});
@@ -17,7 +17,7 @@ export default function LoginRequiredBanner({ show }: LoginRequiredBannerProps)
return (
<Alert
icon={<LocalIcon icon="lock-rounded" width={20} height={20} />}
icon={<LocalIcon icon="lock" width={20} height={20} />}
title={t('admin.settings.loginDisabled.title', 'Login Mode Required')}
color="blue"
variant="light"
@@ -1,8 +1,10 @@
import React from 'react';
import React, { lazy } from 'react';
import { useTranslation } from 'react-i18next';
import { NavKey } from '@app/components/shared/config/types';
import HotkeysSection from '@app/components/shared/config/configSections/HotkeysSection';
import GeneralSection from '@app/components/shared/config/configSections/GeneralSection';
// Lazy load config sections
const HotkeysSection = lazy(() => import('@app/components/shared/config/configSections/HotkeysSection'));
const GeneralSection = lazy(() => import('@app/components/shared/config/configSections/GeneralSection'));
export interface ConfigNavItem {
key: NavKey;
@@ -193,7 +193,7 @@ const GeneralSection: React.FC<GeneralSectionProps> = ({ hideTitle = false }) =>
size="sm"
color={updateSummary.max_priority === 'urgent' ? 'red' : 'blue'}
onClick={() => setUpdateModalOpened(true)}
leftSection={<LocalIcon icon="system-update-rounded" width="1rem" height="1rem" />}
leftSection={<LocalIcon icon="system-update-alt-rounded" width="1rem" height="1rem" />}
>
{t('settings.general.updates.viewDetails', 'View Details')}
</Button>
@@ -63,7 +63,7 @@ export default function ToastRenderer() {
{/* Icon */}
<div className="toast-icon">
{t.icon ?? (
<LocalIcon icon={`material-symbols:${getDefaultIconName(t)}`} width={20} height={20} />
<LocalIcon icon={getDefaultIconName(t)} width={20} height={20} />
)}
</div>
@@ -86,7 +86,7 @@ export default function ToastRenderer() {
}}
className={`toast-button toast-expand-button ${t.isExpanded ? 'toast-expand-button--expanded' : ''}`}
>
<LocalIcon icon="material-symbols:expand-more-rounded" />
<LocalIcon icon="expand-more-rounded" />
</button>
)}
<button
@@ -94,7 +94,7 @@ export default function ToastRenderer() {
onClick={() => dismiss(t.id)}
className="toast-button"
>
<LocalIcon icon="material-symbols:close-rounded" width={20} height={20} />
<LocalIcon icon="close-rounded" width={20} height={20} />
</button>
</div>
</div>
@@ -42,7 +42,7 @@ const AddAttachmentsSettings = ({ parameters, onParameterChange, disabled = fals
component="label"
htmlFor="attachments-input"
disabled={disabled}
leftSection={<LocalIcon icon="plus" width="14" height="14" />}
leftSection={<LocalIcon icon="add-rounded" width="14" height="14" />}
>
{parameters.attachments?.length > 0
? t("AddAttachmentsRequest.addMoreFiles", "Add more files...")
@@ -234,7 +234,7 @@ export const SavedSignaturesSection = ({
{groupedSignatures.personal.length > 0 && activePersonalSignature && (
<Stack gap="xs">
<Group gap="xs">
<LocalIcon icon="material-symbols:person-rounded" width={18} height={18} />
<LocalIcon icon="person-rounded" width={18} height={18} />
<Text fw={600} size="sm">
{translate('saved.personalHeading', 'Personal Signatures')}
</Text>
@@ -257,7 +257,7 @@ export const SavedSignaturesSection = ({
onClick={() => setActivePersonalIndex(prev => Math.max(0, prev - 1))}
disabled={disabled || activePersonalIndex === 0}
>
<LocalIcon icon="material-symbols:chevron-left-rounded" width={18} height={18} />
<LocalIcon icon="chevron-left-rounded" width={18} height={18} />
</ActionIcon>
<ActionIcon
variant="light"
@@ -265,7 +265,7 @@ export const SavedSignaturesSection = ({
onClick={() => setActivePersonalIndex(prev => Math.min(groupedSignatures.personal.length - 1, prev + 1))}
disabled={disabled || activePersonalIndex >= groupedSignatures.personal.length - 1}
>
<LocalIcon icon="material-symbols:chevron-right-rounded" width={18} height={18} />
<LocalIcon icon="chevron-right-rounded" width={18} height={18} />
</ActionIcon>
</Group>
</Group>
@@ -284,7 +284,7 @@ export const SavedSignaturesSection = ({
onClick={() => onUseSignature(activePersonalSignature)}
disabled={disabled}
>
<LocalIcon icon="material-symbols:check-circle-outline-rounded" width={18} height={18} />
<LocalIcon icon="check-circle-outline-rounded" width={18} height={18} />
</ActionIcon>
<Tooltip label={translate('saved.delete', 'Remove')}>
<ActionIcon
@@ -294,7 +294,7 @@ export const SavedSignaturesSection = ({
onClick={() => onDeleteSignature(activePersonalSignature)}
disabled={disabled}
>
<LocalIcon icon="material-symbols:delete-outline-rounded" width={18} height={18} />
<LocalIcon icon="delete-outline-rounded" width={18} height={18} />
</ActionIcon>
</Tooltip>
</Group>
@@ -317,7 +317,7 @@ export const SavedSignaturesSection = ({
{groupedSignatures.shared.length > 0 && activeSharedSignature && (
<Stack gap="xs">
<Group gap="xs">
<LocalIcon icon="material-symbols:groups-rounded" width={18} height={18} />
<LocalIcon icon="groups-rounded" width={18} height={18} />
<Text fw={600} size="sm">
{translate('saved.sharedHeading', 'Shared Signatures')}
</Text>
@@ -340,7 +340,7 @@ export const SavedSignaturesSection = ({
onClick={() => setActiveSharedIndex(prev => Math.max(0, prev - 1))}
disabled={disabled || activeSharedIndex === 0}
>
<LocalIcon icon="material-symbols:chevron-left-rounded" width={18} height={18} />
<LocalIcon icon="chevron-left-rounded" width={18} height={18} />
</ActionIcon>
<ActionIcon
variant="light"
@@ -348,7 +348,7 @@ export const SavedSignaturesSection = ({
onClick={() => setActiveSharedIndex(prev => Math.min(groupedSignatures.shared.length - 1, prev + 1))}
disabled={disabled || activeSharedIndex >= groupedSignatures.shared.length - 1}
>
<LocalIcon icon="material-symbols:chevron-right-rounded" width={18} height={18} />
<LocalIcon icon="chevron-right-rounded" width={18} height={18} />
</ActionIcon>
</Group>
</Group>
@@ -367,7 +367,7 @@ export const SavedSignaturesSection = ({
onClick={() => onUseSignature(activeSharedSignature)}
disabled={disabled}
>
<LocalIcon icon="material-symbols:check-circle-outline-rounded" width={18} height={18} />
<LocalIcon icon="check-circle-outline-rounded" width={18} height={18} />
</ActionIcon>
{isAdmin && (
<Tooltip label={translate('saved.delete', 'Remove')}>
@@ -378,7 +378,7 @@ export const SavedSignaturesSection = ({
onClick={() => onDeleteSignature(activeSharedSignature)}
disabled={disabled}
>
<LocalIcon icon="material-symbols:delete-outline-rounded" width={18} height={18} />
<LocalIcon icon="delete-outline-rounded" width={18} height={18} />
</ActionIcon>
</Tooltip>
)}
@@ -424,7 +424,7 @@ export const SavedSignaturesSection = ({
onClick={() => setActiveLocalStorageIndex(prev => Math.max(0, prev - 1))}
disabled={disabled || activeLocalStorageIndex === 0}
>
<LocalIcon icon="material-symbols:chevron-left-rounded" width={18} height={18} />
<LocalIcon icon="chevron-left-rounded" width={18} height={18} />
</ActionIcon>
<ActionIcon
variant="light"
@@ -432,7 +432,7 @@ export const SavedSignaturesSection = ({
onClick={() => setActiveLocalStorageIndex(prev => Math.min(groupedSignatures.localStorage.length - 1, prev + 1))}
disabled={disabled || activeLocalStorageIndex >= groupedSignatures.localStorage.length - 1}
>
<LocalIcon icon="material-symbols:chevron-right-rounded" width={18} height={18} />
<LocalIcon icon="chevron-right-rounded" width={18} height={18} />
</ActionIcon>
</Group>
</Group>
@@ -451,7 +451,7 @@ export const SavedSignaturesSection = ({
onClick={() => onUseSignature(activeLocalStorageSignature)}
disabled={disabled}
>
<LocalIcon icon="material-symbols:check-circle-outline-rounded" width={18} height={18} />
<LocalIcon icon="check-circle-outline-rounded" width={18} height={18} />
</ActionIcon>
<Tooltip label={translate('saved.delete', 'Remove')}>
<ActionIcon
@@ -461,7 +461,7 @@ export const SavedSignaturesSection = ({
onClick={() => onDeleteSignature(activeLocalStorageSignature)}
disabled={disabled}
>
<LocalIcon icon="material-symbols:delete-outline-rounded" width={18} height={18} />
<LocalIcon icon="delete-outline-rounded" width={18} height={18} />
</ActionIcon>
</Tooltip>
</Group>
@@ -370,7 +370,7 @@ const SignSettings = ({
isReady,
onClick,
'personal',
'material-symbols:person-rounded',
'person-rounded',
translate('saved.savePersonal', 'Save Personal')
);
@@ -379,7 +379,7 @@ const SignSettings = ({
isReady,
onClick,
'shared',
'material-symbols:groups-rounded',
'groups-rounded',
translate('saved.saveShared', 'Save Shared')
);
@@ -964,7 +964,7 @@ const SignSettings = ({
gap: '0.4rem',
}}
>
<LocalIcon icon="material-symbols:pause-rounded" width={20} height={20} />
<LocalIcon icon="pause-rounded" width={20} height={20} />
<Text component="span" size="sm" fw={500}>
{translate('mode.pause', 'Pause placement')}
</Text>
@@ -987,7 +987,7 @@ const SignSettings = ({
gap: '0.4rem',
}}
>
<LocalIcon icon="material-symbols:play-arrow-rounded" width={20} height={20} />
<LocalIcon icon="play-arrow-rounded" width={20} height={20} />
<Text component="span" size="sm" fw={500}>
{translate('mode.resume', 'Resume placement')}
</Text>
@@ -13,7 +13,7 @@ import {
ProcessedFileMetadata,
} from '@app/types/fileContext';
import { FileId, ToolOperation } from '@app/types/file';
import { generateThumbnailWithMetadata } from '@app/utils/thumbnailUtils';
import { generateThumbnailWithMetadata, generateThumbnailForFile } from '@app/utils/thumbnailUtils';
import { FileLifecycleManager } from '@app/contexts/file/lifecycle';
import { buildQuickKeySet } from '@app/contexts/file/fileSelectors';
import { StirlingFile } from '@app/types/fileContext';
@@ -296,7 +296,6 @@ export async function addFiles(
// Non-PDF files: simple thumbnail generation, no processedFile metadata
try {
if (DEBUG) console.log(`📄 Generating simple thumbnail for non-PDF file ${file.name}`);
const { generateThumbnailForFile } = await import('@app/utils/thumbnailUtils');
thumbnail = await generateThumbnailForFile(file);
if (DEBUG) console.log(`📄 Generated simple thumbnail for ${file.name}: no page count, thumbnail: SUCCESS`);
} catch (error) {
@@ -1,19 +1,8 @@
import { useMemo } from "react";
import { useMemo, lazy } from "react";
import LocalIcon from "@app/components/shared/LocalIcon";
import { useTranslation } from "react-i18next";
import { devApiLink } from "@app/constants/links";
import SplitPdfPanel from "@app/tools/Split";
import CompressPdfPanel from "@app/tools/Compress";
import OCRPanel from "@app/tools/OCR";
import ConvertPanel from "@app/tools/Convert";
import Sanitize from "@app/tools/Sanitize";
import AddPassword from "@app/tools/AddPassword";
import ChangePermissions from "@app/tools/ChangePermissions";
import RemoveBlanks from "@app/tools/RemoveBlanks";
import RemovePages from "@app/tools/RemovePages";
import ReorganizePages from "@app/tools/ReorganizePages";
import { reorganizePagesOperationConfig } from "@app/hooks/tools/reorganizePages/useReorganizePagesOperation";
import RemovePassword from "@app/tools/RemovePassword";
import {
SubcategoryId,
ToolCategoryId,
@@ -23,34 +12,9 @@ import {
LinkToolRegistry,
} from "@app/data/toolsTaxonomy";
import { isSuperToolId, isLinkToolId } from '@app/types/toolId';
import AdjustContrast from "@app/tools/AdjustContrast";
import AdjustContrastSingleStepSettings from "@app/components/tools/adjustContrast/AdjustContrastSingleStepSettings";
import { adjustContrastOperationConfig } from "@app/hooks/tools/adjustContrast/useAdjustContrastOperation";
import { getSynonyms } from "@app/utils/toolSynonyms";
import { useProprietaryToolRegistry } from "@app/data/useProprietaryToolRegistry";
import GetPdfInfo from "@app/tools/GetPdfInfo";
import AddWatermark from "@app/tools/AddWatermark";
import AddStamp from "@app/tools/AddStamp";
import AddAttachments from "@app/tools/AddAttachments";
import Merge from '@app/tools/Merge';
import EditTableOfContents from '@app/tools/EditTableOfContents';
import Repair from "@app/tools/Repair";
import AutoRename from "@app/tools/AutoRename";
import SingleLargePage from "@app/tools/SingleLargePage";
import PageLayout from "@app/tools/PageLayout";
import UnlockPdfForms from "@app/tools/UnlockPdfForms";
import RemoveCertificateSign from "@app/tools/RemoveCertificateSign";
import RemoveImage from "@app/tools/RemoveImage";
import CertSign from "@app/tools/CertSign";
import BookletImposition from "@app/tools/BookletImposition";
import Flatten from "@app/tools/Flatten";
import Rotate from "@app/tools/Rotate";
import PdfTextEditor from "@app/tools/pdfTextEditor/PdfTextEditor";
import ChangeMetadata from "@app/tools/ChangeMetadata";
import Crop from "@app/tools/Crop";
import Sign from "@app/tools/Sign";
import AddText from "@app/tools/AddText";
import AddImage from "@app/tools/AddImage";
import { compressOperationConfig } from "@app/hooks/tools/compress/useCompressOperation";
import { splitOperationConfig } from "@app/hooks/tools/split/useSplitOperation";
import { addPasswordOperationConfig } from "@app/hooks/tools/addPassword/useAddPasswordOperation";
@@ -88,50 +52,7 @@ import { scannerImageSplitOperationConfig } from "@app/hooks/tools/scannerImageS
import { addPageNumbersOperationConfig } from "@app/components/tools/addPageNumbers/useAddPageNumbersOperation";
import { extractPagesOperationConfig } from "@app/hooks/tools/extractPages/useExtractPagesOperation";
import { ENDPOINTS as SPLIT_ENDPOINT_NAMES } from '@app/constants/splitConstants';
import CompressSettings from "@app/components/tools/compress/CompressSettings";
import AddPasswordSettings from "@app/components/tools/addPassword/AddPasswordSettings";
import RemovePasswordSettings from "@app/components/tools/removePassword/RemovePasswordSettings";
import SanitizeSettings from "@app/components/tools/sanitize/SanitizeSettings";
import AddWatermarkSingleStepSettings from "@app/components/tools/addWatermark/AddWatermarkSingleStepSettings";
import OCRSettings from "@app/components/tools/ocr/OCRSettings";
import ConvertSettings from "@app/components/tools/convert/ConvertSettings";
import ChangePermissionsSettings from "@app/components/tools/changePermissions/ChangePermissionsSettings";
import BookletImpositionSettings from "@app/components/tools/bookletImposition/BookletImpositionSettings";
import FlattenSettings from "@app/components/tools/flatten/FlattenSettings";
import RedactSingleStepSettings from "@app/components/tools/redact/RedactSingleStepSettings";
import Redact from "@app/tools/Redact";
import AdjustPageScale from "@app/tools/AdjustPageScale";
import ReplaceColor from "@app/tools/ReplaceColor";
import ScannerImageSplit from "@app/tools/ScannerImageSplit";
import OverlayPdfs from "@app/tools/OverlayPdfs";
import { ToolId } from "@app/types/toolId";
import MergeSettings from '@app/components/tools/merge/MergeSettings';
import AdjustPageScaleSettings from "@app/components/tools/adjustPageScale/AdjustPageScaleSettings";
import ScannerImageSplitSettings from "@app/components/tools/scannerImageSplit/ScannerImageSplitSettings";
import ChangeMetadataSingleStep from "@app/components/tools/changeMetadata/ChangeMetadataSingleStep";
import SignSettings from "@app/components/tools/sign/SignSettings";
import AddPageNumbers from "@app/tools/AddPageNumbers";
import RemoveAnnotations from "@app/tools/RemoveAnnotations";
import PageLayoutSettings from "@app/components/tools/pageLayout/PageLayoutSettings";
import ExtractImages from "@app/tools/ExtractImages";
import ExtractPages from "@app/tools/ExtractPages";
import ExtractImagesSettings from "@app/components/tools/extractImages/ExtractImagesSettings";
import ExtractPagesSettings from "@app/components/tools/extractPages/ExtractPagesSettings";
import ReplaceColorSettings from "@app/components/tools/replaceColor/ReplaceColorSettings";
import AddStampAutomationSettings from "@app/components/tools/addStamp/AddStampAutomationSettings";
import CertSignAutomationSettings from "@app/components/tools/certSign/CertSignAutomationSettings";
import CropAutomationSettings from "@app/components/tools/crop/CropAutomationSettings";
import RotateAutomationSettings from "@app/components/tools/rotate/RotateAutomationSettings";
import SplitAutomationSettings from "@app/components/tools/split/SplitAutomationSettings";
import AddAttachmentsSettings from "@app/components/tools/addAttachments/AddAttachmentsSettings";
import RemovePagesSettings from "@app/components/tools/removePages/RemovePagesSettings";
import RemoveBlanksSettings from "@app/components/tools/removeBlanks/RemoveBlanksSettings";
import AddPageNumbersAutomationSettings from "@app/components/tools/addPageNumbers/AddPageNumbersAutomationSettings";
import OverlayPdfsSettings from "@app/components/tools/overlayPdfs/OverlayPdfsSettings";
import ValidateSignature from "@app/tools/ValidateSignature";
import ShowJS from "@app/tools/ShowJS";
import Automate from "@app/tools/Automate";
import Compare from "@app/tools/Compare";
import { CONVERT_SUPPORTED_FORMATS } from "@app/constants/convertSupportedFornats";
@@ -156,7 +77,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
pdfTextEditor: {
icon: <LocalIcon icon="edit-square-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.pdfTextEditor.title", "PDF Text Editor"),
component: PdfTextEditor,
component: lazy(() => import("@app/tools/pdfTextEditor/PdfTextEditor")),
description: t(
"home.pdfTextEditor.desc",
"Review and edit text and images in PDFs with grouped text editing and PDF regeneration"
@@ -186,21 +107,21 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
merge: {
icon: <LocalIcon icon="library-add-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.merge.title", "Merge"),
component: Merge,
component: lazy(() => import("@app/tools/Merge")),
description: t("home.merge.desc", "Merge multiple PDFs into a single document"),
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
maxFiles: -1,
endpoints: ["merge-pdfs"],
operationConfig: mergeOperationConfig,
automationSettings: MergeSettings,
automationSettings: lazy(() => import("@app/components/tools/merge/MergeSettings")),
synonyms: getSynonyms(t, "merge")
},
// Signing
certSign: {
icon: <LocalIcon icon="workspace-premium-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.certSign.title", "Certificate Sign"),
component: CertSign,
component: lazy(() => import("@app/tools/CertSign")),
description: t("home.certSign.desc", "Sign PDF documents using digital certificates"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.SIGNING,
@@ -208,24 +129,24 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
maxFiles: -1,
endpoints: ["cert-sign"],
operationConfig: certSignOperationConfig,
automationSettings: CertSignAutomationSettings,
automationSettings: lazy(() => import("@app/components/tools/certSign/CertSignAutomationSettings")),
},
sign: {
icon: <LocalIcon icon="signature-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.sign.title", "Sign"),
component: Sign,
component: lazy(() => import("@app/tools/Sign")),
description: t("home.sign.desc", "Adds signature to PDF by drawing, text or image"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.SIGNING,
operationConfig: signOperationConfig,
automationSettings: SignSettings, // TODO:: not all settings shown, suggested next tools shown
automationSettings: lazy(() => import("@app/components/tools/sign/SignSettings")), // TODO:: not all settings shown, suggested next tools shown
synonyms: getSynonyms(t, "sign"),
supportsAutomate: false, //TODO make support Sign
},
addText: {
icon: <LocalIcon icon="material-symbols:text-fields-rounded" width="1.5rem" height="1.5rem" />,
icon: <LocalIcon icon="text-fields-rounded" width="1.5rem" height="1.5rem" />,
name: t('home.addText.title', 'Add Text'),
component: AddText,
component: lazy(() => import("@app/tools/AddText")),
description: t('home.addText.desc', 'Add custom text anywhere in your PDF'),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
@@ -237,7 +158,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
addImage: {
icon: <LocalIcon icon="image-rounded" width="1.5rem" height="1.5rem" />,
name: t('home.addImage.title', 'Add Image'),
component: AddImage,
component: lazy(() => import("@app/tools/AddImage")),
description: t('home.addImage.desc', 'Add images anywhere in your PDF'),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
@@ -252,33 +173,33 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
addPassword: {
icon: <LocalIcon icon="password-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.addPassword.title", "Add Password"),
component: AddPassword,
component: lazy(() => import("@app/tools/AddPassword")),
description: t("home.addPassword.desc", "Add password protection and restrictions to PDF files"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
maxFiles: -1,
endpoints: ["add-password"],
operationConfig: addPasswordOperationConfig,
automationSettings: AddPasswordSettings,
automationSettings: lazy(() => import("@app/components/tools/addPassword/AddPasswordSettings")),
synonyms: getSynonyms(t, "addPassword")
},
watermark: {
icon: <LocalIcon icon="branding-watermark-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.watermark.title", "Add Watermark"),
component: AddWatermark,
component: lazy(() => import("@app/tools/AddWatermark")),
maxFiles: -1,
description: t("home.watermark.desc", "Add a custom watermark to your PDF document."),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
endpoints: ["add-watermark"],
operationConfig: addWatermarkOperationConfig,
automationSettings: AddWatermarkSingleStepSettings,
automationSettings: lazy(() => import("@app/components/tools/addWatermark/AddWatermarkSingleStepSettings")),
synonyms: getSynonyms(t, "watermark")
},
addStamp: {
icon: <LocalIcon icon="approval-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.addStamp.title", "Add Stamp to PDF"),
component: AddStamp,
component: lazy(() => import("@app/tools/AddStamp")),
description: t("home.addStamp.desc", "Add text or add image stamps at set locations"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
@@ -286,38 +207,38 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
maxFiles: -1,
endpoints: ["add-stamp"],
operationConfig: addStampOperationConfig,
automationSettings: AddStampAutomationSettings,
automationSettings: lazy(() => import("@app/components/tools/addStamp/AddStampAutomationSettings")),
},
sanitize: {
icon: <LocalIcon icon="cleaning-services-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.sanitize.title", "Sanitize"),
component: Sanitize,
component: lazy(() => import("@app/tools/Sanitize")),
maxFiles: -1,
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
description: t("home.sanitize.desc", "Remove potentially harmful elements from PDF files"),
endpoints: ["sanitize-pdf"],
operationConfig: sanitizeOperationConfig,
automationSettings: SanitizeSettings,
automationSettings: lazy(() => import("@app/components/tools/sanitize/SanitizeSettings")),
synonyms: getSynonyms(t, "sanitize")
},
flatten: {
icon: <LocalIcon icon="layers-clear-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.flatten.title", "Flatten"),
component: Flatten,
component: lazy(() => import("@app/tools/Flatten")),
description: t("home.flatten.desc", "Remove all interactive elements and forms from a PDF"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
maxFiles: -1,
endpoints: ["flatten"],
operationConfig: flattenOperationConfig,
automationSettings: FlattenSettings,
automationSettings: lazy(() => import("@app/components/tools/flatten/FlattenSettings")),
synonyms: getSynonyms(t, "flatten")
},
unlockPDFForms: {
icon: <LocalIcon icon="preview-off-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.unlockPDFForms.title", "Unlock PDF Forms"),
component: UnlockPdfForms,
component: lazy(() => import("@app/tools/UnlockPdfForms")),
description: t("home.unlockPDFForms.desc", "Remove read-only property of form fields in a PDF document."),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
@@ -330,20 +251,20 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
changePermissions: {
icon: <LocalIcon icon="lock-outline" width="1.5rem" height="1.5rem" />,
name: t("home.changePermissions.title", "Change Permissions"),
component: ChangePermissions,
component: lazy(() => import("@app/tools/ChangePermissions")),
description: t("home.changePermissions.desc", "Change document restrictions and permissions"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.DOCUMENT_SECURITY,
maxFiles: -1,
endpoints: ["add-password"],
operationConfig: changePermissionsOperationConfig,
automationSettings: ChangePermissionsSettings,
automationSettings: lazy(() => import("@app/components/tools/changePermissions/ChangePermissionsSettings")),
synonyms: getSynonyms(t, "changePermissions"),
},
getPdfInfo: {
icon: <LocalIcon icon="fact-check-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.getPdfInfo.title", "Get ALL Info on PDF"),
component: GetPdfInfo,
component: lazy(() => import("@app/tools/GetPdfInfo")),
description: t("home.getPdfInfo.desc", "Grabs any and all information possible on PDFs"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.VERIFICATION,
@@ -356,7 +277,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
validateSignature: {
icon: <LocalIcon icon="verified-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.validateSignature.title", "Validate PDF Signature"),
component: ValidateSignature,
component: lazy(() => import("@app/tools/ValidateSignature")),
description: t("home.validateSignature.desc", "Verify digital signatures and certificates in PDF documents"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.VERIFICATION,
@@ -386,20 +307,20 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
changeMetadata: {
icon: <LocalIcon icon="assignment-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.changeMetadata.title", "Change Metadata"),
component: ChangeMetadata,
component: lazy(() => import("@app/tools/ChangeMetadata")),
description: t("home.changeMetadata.desc", "Change/Remove/Add metadata from a PDF document"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.DOCUMENT_REVIEW,
maxFiles: -1,
endpoints: ["update-metadata"],
operationConfig: changeMetadataOperationConfig,
automationSettings: ChangeMetadataSingleStep,
automationSettings: lazy(() => import("@app/components/tools/changeMetadata/ChangeMetadataSingleStep")),
synonyms: getSynonyms(t, "changeMetadata")
},
editTableOfContents: {
icon: <LocalIcon icon="toc-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.editTableOfContents.title", "Edit Table of Contents"),
component: EditTableOfContents,
component: lazy(() => import("@app/tools/EditTableOfContents")),
description: t(
"home.editTableOfContents.desc",
"Add or edit bookmarks and table of contents in PDF documents"
@@ -418,44 +339,44 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
crop: {
icon: <LocalIcon icon="crop-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.crop.title", "Crop PDF"),
component: Crop,
component: lazy(() => import("@app/tools/Crop")),
description: t("home.crop.desc", "Crop a PDF to reduce its size (maintains text!)"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING,
maxFiles: -1,
endpoints: ["crop"],
operationConfig: cropOperationConfig,
automationSettings: CropAutomationSettings,
automationSettings: lazy(() => import("@app/components/tools/crop/CropAutomationSettings")),
},
rotate: {
icon: <LocalIcon icon="rotate-right-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.rotate.title", "Rotate"),
component: Rotate,
component: lazy(() => import("@app/tools/Rotate")),
description: t("home.rotate.desc", "Easily rotate your PDFs."),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING,
maxFiles: -1,
endpoints: ["rotate-pdf"],
operationConfig: rotateOperationConfig,
automationSettings: RotateAutomationSettings,
automationSettings: lazy(() => import("@app/components/tools/rotate/RotateAutomationSettings")),
synonyms: getSynonyms(t, "rotate")
},
split: {
icon: <LocalIcon icon="content-cut-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.split.title", "Split"),
component: SplitPdfPanel,
component: lazy(() => import("@app/tools/Split")),
description: t("home.split.desc", "Split PDFs into multiple documents"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING,
endpoints: Array.from(new Set(Object.values(SPLIT_ENDPOINT_NAMES))),
operationConfig: splitOperationConfig,
automationSettings: SplitAutomationSettings,
automationSettings: lazy(() => import("@app/components/tools/split/SplitAutomationSettings")),
synonyms: getSynonyms(t, "split")
},
reorganizePages: {
icon: <LocalIcon icon="move-down-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.reorganizePages.title", "Reorganize Pages"),
component: ReorganizePages,
component: lazy(() => import("@app/tools/ReorganizePages")),
description: t(
"home.reorganizePages.desc",
"Rearrange, duplicate, or delete PDF pages with visual drag-and-drop control."
@@ -471,24 +392,24 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
scalePages: {
icon: <LocalIcon icon="crop-free-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.scalePages.title", "Adjust page size/scale"),
component: AdjustPageScale,
component: lazy(() => import("@app/tools/AdjustPageScale")),
description: t("home.scalePages.desc", "Change the size/scale of a page and/or its contents."),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING,
maxFiles: -1,
endpoints: ["scale-pages"],
operationConfig: adjustPageScaleOperationConfig,
automationSettings: AdjustPageScaleSettings,
automationSettings: lazy(() => import("@app/components/tools/adjustPageScale/AdjustPageScaleSettings")),
synonyms: getSynonyms(t, "scalePages")
},
addPageNumbers: {
icon: <LocalIcon icon="123-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.addPageNumbers.title", "Add Page Numbers"),
component: AddPageNumbers,
component: lazy(() => import("@app/tools/AddPageNumbers")),
description: t("home.addPageNumbers.desc", "Add Page numbers throughout a document in a set location"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING,
automationSettings: AddPageNumbersAutomationSettings,
automationSettings: lazy(() => import("@app/components/tools/addPageNumbers/AddPageNumbersAutomationSettings")),
maxFiles: -1,
endpoints: ["add-page-numbers"],
operationConfig: addPageNumbersOperationConfig,
@@ -497,21 +418,21 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
pageLayout: {
icon: <LocalIcon icon="dashboard-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.pageLayout.title", "Multi-Page Layout"),
component: PageLayout,
component: lazy(() => import("@app/tools/PageLayout")),
description: t("home.pageLayout.desc", "Merge multiple pages of a PDF document into a single page"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING,
maxFiles: -1,
endpoints: ["multi-page-layout"],
automationSettings: PageLayoutSettings,
automationSettings: lazy(() => import("@app/components/tools/pageLayout/PageLayoutSettings")),
synonyms: getSynonyms(t, "pageLayout")
},
bookletImposition: {
icon: <LocalIcon icon="menu-book-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.bookletImposition.title", "Booklet Imposition"),
component: BookletImposition,
component: lazy(() => import("@app/tools/BookletImposition")),
operationConfig: bookletImpositionOperationConfig,
automationSettings: BookletImpositionSettings,
automationSettings: lazy(() => import("@app/components/tools/bookletImposition/BookletImpositionSettings")),
description: t("home.bookletImposition.desc", "Create booklets with proper page ordering and multi-page layout for printing and binding"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING,
@@ -521,7 +442,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
icon: <LocalIcon icon="looks-one-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.pdfToSinglePage.title", "PDF to Single Large Page"),
component: SingleLargePage,
component: lazy(() => import("@app/tools/SingleLargePage")),
description: t("home.pdfToSinglePage.desc", "Merges all PDF pages into one large single page"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
@@ -535,7 +456,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
addAttachments: {
icon: <LocalIcon icon="attachment-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.addAttachments.title", "Add Attachments"),
component: AddAttachments,
component: lazy(() => import("@app/tools/AddAttachments")),
description: t("home.addAttachments.desc", "Add or remove embedded files (attachments) to/from a PDF"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.PAGE_FORMATTING,
@@ -543,7 +464,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
maxFiles: 1,
endpoints: ["add-attachments"],
operationConfig: addAttachmentsOperationConfig,
automationSettings: AddAttachmentsSettings,
automationSettings: lazy(() => import("@app/components/tools/addAttachments/AddAttachmentsSettings")),
},
// Extraction
@@ -551,26 +472,26 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
extractPages: {
icon: <LocalIcon icon="upload-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.extractPages.title", "Extract Pages"),
component: ExtractPages,
component: lazy(() => import("@app/tools/ExtractPages")),
description: t("home.extractPages.desc", "Extract specific pages from a PDF document"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.EXTRACTION,
synonyms: getSynonyms(t, "extractPages"),
automationSettings: ExtractPagesSettings,
automationSettings: lazy(() => import("@app/components/tools/extractPages/ExtractPagesSettings")),
operationConfig: extractPagesOperationConfig,
endpoints: ["rearrange-pages"],
},
extractImages: {
icon: <LocalIcon icon="photo-library-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.extractImages.title", "Extract Images"),
component: ExtractImages,
component: lazy(() => import("@app/tools/ExtractImages")),
description: t("home.extractImages.desc", "Extract images from PDF documents"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.EXTRACTION,
maxFiles: -1,
endpoints: ["extract-images"],
operationConfig: extractImagesOperationConfig,
automationSettings: ExtractImagesSettings,
automationSettings: lazy(() => import("@app/components/tools/extractImages/ExtractImagesSettings")),
synonyms: getSynonyms(t, "extractImages")
},
@@ -579,7 +500,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
removePages: {
icon: <LocalIcon icon="delete-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removePages.title", "Remove Pages"),
component: RemovePages,
component: lazy(() => import("@app/tools/RemovePages")),
description: t("home.removePages.desc", "Remove specific pages from a PDF document"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.REMOVAL,
@@ -587,12 +508,12 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
endpoints: ["remove-pages"],
synonyms: getSynonyms(t, "removePages"),
operationConfig: removePagesOperationConfig,
automationSettings: RemovePagesSettings,
automationSettings: lazy(() => import("@app/components/tools/removePages/RemovePagesSettings")),
},
removeBlanks: {
icon: <LocalIcon icon="scan-delete-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removeBlanks.title", "Remove Blank Pages"),
component: RemoveBlanks,
component: lazy(() => import("@app/tools/RemoveBlanks")),
description: t("home.removeBlanks.desc", "Remove blank pages from PDF documents"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.REMOVAL,
@@ -600,12 +521,12 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
endpoints: ["remove-blanks"],
synonyms: getSynonyms(t, "removeBlanks"),
operationConfig: removeBlanksOperationConfig,
automationSettings: RemoveBlanksSettings,
automationSettings: lazy(() => import("@app/components/tools/removeBlanks/RemoveBlanksSettings")),
},
removeAnnotations: {
icon: <LocalIcon icon="thread-unread-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removeAnnotations.title", "Remove Annotations"),
component: RemoveAnnotations,
component: lazy(() => import("@app/tools/RemoveAnnotations")),
description: t("home.removeAnnotations.desc", "Remove annotations and comments from PDF documents"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.REMOVAL,
@@ -618,7 +539,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
removeImage: {
icon: <LocalIcon icon="remove-selection-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removeImage.title", "Remove Images"),
component: RemoveImage,
component: lazy(() => import("@app/tools/RemoveImage")),
description: t("home.removeImage.desc", "Remove all images from a PDF document"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.REMOVAL,
@@ -631,20 +552,20 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
removePassword: {
icon: <LocalIcon icon="lock-open-right-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removePassword.title", "Remove Password"),
component: RemovePassword,
component: lazy(() => import("@app/tools/RemovePassword")),
description: t("home.removePassword.desc", "Remove password protection from PDF documents"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.REMOVAL,
endpoints: ["remove-password"],
maxFiles: -1,
operationConfig: removePasswordOperationConfig,
automationSettings: RemovePasswordSettings,
automationSettings: lazy(() => import("@app/components/tools/removePassword/RemovePasswordSettings")),
synonyms: getSynonyms(t, "removePassword")
},
removeCertSign: {
icon: <LocalIcon icon="remove-moderator-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.removeCertSign.title", "Remove Certificate Sign"),
component: RemoveCertificateSign,
component: lazy(() => import("@app/tools/RemoveCertificateSign")),
description: t("home.removeCertSign.desc", "Remove digital signature from PDF documents"),
categoryId: ToolCategoryId.STANDARD_TOOLS,
subcategoryId: SubcategoryId.REMOVAL,
@@ -660,7 +581,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
automate: {
icon: <LocalIcon icon="automation-outline" width="1.5rem" height="1.5rem" />,
name: t("home.automate.title", "Automate"),
component: Automate,
component: lazy(() => import("@app/tools/Automate")),
description: t(
"home.automate.desc",
"Build multi-step workflows by chaining together PDF actions. Ideal for recurring tasks."
@@ -676,7 +597,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
autoRename: {
icon: <LocalIcon icon="match-word-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.autoRename.title", "Auto Rename PDF File"),
component: AutoRename,
component: lazy(() => import("@app/tools/AutoRename")),
maxFiles: -1,
endpoints: ["auto-rename"],
operationConfig: autoRenameOperationConfig,
@@ -692,18 +613,18 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
adjustContrast: {
icon: <LocalIcon icon="palette" width="1.5rem" height="1.5rem" />,
name: t("home.adjustContrast.title", "Adjust Colors/Contrast"),
component: AdjustContrast,
component: lazy(() => import("@app/tools/AdjustContrast")),
description: t("home.adjustContrast.desc", "Adjust colors and contrast of PDF documents"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
operationConfig: adjustContrastOperationConfig,
automationSettings: AdjustContrastSingleStepSettings,
automationSettings: lazy(() => import("@app/components/tools/adjustContrast/AdjustContrastSingleStepSettings")),
synonyms: getSynonyms(t, "adjustContrast"),
},
repair: {
icon: <LocalIcon icon="build-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.repair.title", "Repair"),
component: Repair,
component: lazy(() => import("@app/tools/Repair")),
description: t("home.repair.desc", "Repair corrupted or damaged PDF files"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
@@ -716,39 +637,39 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
scannerImageSplit: {
icon: <LocalIcon icon="scanner-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.scannerImageSplit.title", "Detect & Split Scanned Photos"),
component: ScannerImageSplit,
component: lazy(() => import("@app/tools/ScannerImageSplit")),
description: t("home.scannerImageSplit.desc", "Detect and split scanned photos into separate pages"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
maxFiles: -1,
endpoints: ["extract-image-scans"],
operationConfig: scannerImageSplitOperationConfig,
automationSettings: ScannerImageSplitSettings,
automationSettings: lazy(() => import("@app/components/tools/scannerImageSplit/ScannerImageSplitSettings")),
synonyms: getSynonyms(t, "ScannerImageSplit"),
},
overlayPdfs: {
icon: <LocalIcon icon="layers-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.overlay-pdfs.title", "Overlay PDFs"),
component: OverlayPdfs,
component: lazy(() => import("@app/tools/OverlayPdfs")),
description: t("home.overlay-pdfs.desc", "Overlay one PDF on top of another"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
endpoints: ["overlay-pdf"],
operationConfig: overlayPdfsOperationConfig,
synonyms: getSynonyms(t, "overlay-pdfs"),
automationSettings: OverlayPdfsSettings
automationSettings: lazy(() => import("@app/components/tools/overlayPdfs/OverlayPdfsSettings"))
},
replaceColor: {
icon: <LocalIcon icon="format-color-fill-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.replaceColor.title", "Replace & Invert Color"),
component: ReplaceColor,
component: lazy(() => import("@app/tools/ReplaceColor")),
description: t("home.replaceColor.desc", "Replace or invert colors in PDF documents"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
maxFiles: -1,
endpoints: ["replace-invert-pdf"],
operationConfig: replaceColorOperationConfig,
automationSettings: ReplaceColorSettings,
automationSettings: lazy(() => import("@app/components/tools/replaceColor/ReplaceColorSettings")),
synonyms: getSynonyms(t, "replaceColor"),
},
scannerEffect: {
@@ -768,7 +689,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
showJS: {
icon: <LocalIcon icon="javascript-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.showJS.title", "Show JavaScript"),
component: ShowJS,
component: lazy(() => import("@app/tools/ShowJS")),
description: t("home.showJS.desc", "Extract and display JavaScript code from PDF documents"),
categoryId: ToolCategoryId.ADVANCED_TOOLS,
subcategoryId: SubcategoryId.DEVELOPER_TOOLS,
@@ -831,7 +752,7 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
compare: {
icon: <LocalIcon icon="compare-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.compare.title", "Compare"),
component: Compare,
component: lazy(() => import("@app/tools/Compare")),
description: t("home.compare.desc", "Compare two PDF documents and highlight differences"),
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
@@ -844,20 +765,20 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
compress: {
icon: <LocalIcon icon="zoom-in-map-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.compress.title", "Compress"),
component: CompressPdfPanel,
component: lazy(() => import("@app/tools/Compress")),
description: t("home.compress.desc", "Compress PDFs to reduce their file size."),
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
maxFiles: -1,
endpoints: ["compress-pdf"],
operationConfig: compressOperationConfig,
automationSettings: CompressSettings,
automationSettings: lazy(() => import("@app/components/tools/compress/CompressSettings")),
synonyms: getSynonyms(t, "compress")
},
convert: {
icon: <LocalIcon icon="sync-alt-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.convert.title", "Convert"),
component: ConvertPanel,
component: lazy(() => import("@app/tools/Convert")),
description: t("home.convert.desc", "Convert files to and from PDF format"),
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
@@ -881,34 +802,34 @@ export function useTranslatedToolCatalog(): TranslatedToolCatalog {
],
operationConfig: convertOperationConfig,
automationSettings: ConvertSettings,
automationSettings: lazy(() => import("@app/components/tools/convert/ConvertSettings")),
synonyms: getSynonyms(t, "convert")
},
ocr: {
icon: <LocalIcon icon="quick-reference-all-outline-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.ocr.title", "OCR"),
component: OCRPanel,
component: lazy(() => import("@app/tools/OCR")),
description: t("home.ocr.desc", "Extract text from scanned PDFs using Optical Character Recognition"),
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
maxFiles: -1,
endpoints: ["ocr-pdf"],
operationConfig: ocrOperationConfig,
automationSettings: OCRSettings,
automationSettings: lazy(() => import("@app/components/tools/ocr/OCRSettings")),
synonyms: getSynonyms(t, "ocr")
},
redact: {
icon: <LocalIcon icon="visibility-off-rounded" width="1.5rem" height="1.5rem" />,
name: t("home.redact.title", "Redact"),
component: Redact,
component: lazy(() => import("@app/tools/Redact")),
description: t("home.redact.desc", "Permanently remove sensitive information from PDF documents"),
categoryId: ToolCategoryId.RECOMMENDED_TOOLS,
subcategoryId: SubcategoryId.GENERAL,
maxFiles: -1,
endpoints: ["auto-redact"],
operationConfig: redactOperationConfig,
automationSettings: RedactSingleStepSettings,
automationSettings: lazy(() => import("@app/components/tools/redact/RedactSingleStepSettings")),
synonyms: getSynonyms(t, "redact")
},
};
@@ -1,5 +1,6 @@
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import JSZip from 'jszip';
import { OCRParameters, defaultParameters } from '@app/hooks/tools/ocr/useOCRParameters';
import { useToolOperation, ToolOperationConfig, ToolType } from '@app/hooks/tools/shared/useToolOperation';
import { createStandardErrorHandler } from '@app/utils/toolErrorHandler';
@@ -18,8 +19,7 @@ function getMimeType(filename: string): string {
// Lightweight ZIP extractor (keep or replace with a shared util if you have one)
async function extractZipFile(zipBlob: Blob): Promise<File[]> {
const JSZip = await import('jszip');
const zip = new JSZip.default();
const zip = new JSZip();
const zipContent = await zip.loadAsync(await zipBlob.arrayBuffer());
const out: File[] = [];
for (const [filename, file] of Object.entries(zipContent.files)) {
+25 -12
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState, lazy, Suspense } from "react";
import { useTranslation } from "react-i18next";
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
import { Group, useMantineColorScheme } from "@mantine/core";
@@ -15,13 +15,16 @@ import { useViewer } from "@app/contexts/ViewerContext";
import AppsIcon from '@mui/icons-material/AppsRounded';
import ToolPanel from "@app/components/tools/ToolPanel";
import Workbench from "@app/components/layout/Workbench";
import QuickAccessBar from "@app/components/shared/QuickAccessBar";
import RightRail from "@app/components/shared/RightRail";
import FileManager from "@app/components/FileManager";
import LocalIcon from "@app/components/shared/LocalIcon";
// Lazy-load heavy components that aren't needed on initial render
const Workbench = lazy(() => import("@app/components/layout/Workbench"));
const FileManager = lazy(() => import("@app/components/FileManager"));
const AppConfigModal = lazy(() => import("@app/components/shared/AppConfigModal"));
import { useFilesModalContext } from "@app/contexts/FilesModalContext";
import AppConfigModal from "@app/components/shared/AppConfigModal";
import "@app/pages/HomePage.css";
@@ -218,7 +221,9 @@ export default function HomePage() {
<div className="mobile-slide" aria-label={t('home.mobile.workbenchSlide', 'Workspace panel')}>
<div className="mobile-slide-content">
<div className="flex-1 min-h-0 flex">
<Workbench />
<Suspense fallback={<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>Loading...</div>}>
<Workbench />
</Suspense>
<RightRail />
</div>
</div>
@@ -268,11 +273,15 @@ export default function HomePage() {
<span className="mobile-bottom-button-label">{t('quickAccess.config', 'Config')}</span>
</button>
</div>
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
<AppConfigModal
opened={configModalOpen}
onClose={() => setConfigModalOpen(false)}
/>
<Suspense fallback={null}>
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
</Suspense>
<Suspense fallback={null}>
<AppConfigModal
opened={configModalOpen}
onClose={() => setConfigModalOpen(false)}
/>
</Suspense>
</div>
) : (
<Group
@@ -283,9 +292,13 @@ export default function HomePage() {
>
<QuickAccessBar ref={quickAccessRef} />
<ToolPanel />
<Workbench />
<Suspense fallback={<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>Loading...</div>}>
<Workbench />
</Suspense>
<RightRail />
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
<Suspense fallback={null}>
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
</Suspense>
</Group>
)}
</div>
+22 -6
View File
@@ -3,9 +3,14 @@
*
* Prevents infinite worker creation by managing PDF.js workers globally
* and ensuring proper cleanup when operations complete.
*
* Uses dynamic imports to avoid bundling PDF.js in main bundle.
*/
import { GlobalWorkerOptions, getDocument, PDFDocumentProxy } from 'pdfjs-dist/legacy/build/pdf.mjs';
// Dynamic import types for TypeScript
type PDFDocumentProxy = import('pdfjs-dist').PDFDocumentProxy;
type GlobalWorkerOptions = typeof import('pdfjs-dist').GlobalWorkerOptions;
type GetDocumentFn = typeof import('pdfjs-dist').getDocument;
class PDFWorkerManager {
private static instance: PDFWorkerManager;
@@ -13,9 +18,10 @@ class PDFWorkerManager {
private workerCount = 0;
private maxWorkers = 10; // Limit concurrent workers
private isInitialized = false;
private pdfjsLib: { GlobalWorkerOptions: GlobalWorkerOptions; getDocument: GetDocumentFn } | null = null;
private constructor() {
this.initializeWorker();
// Don't initialize worker in constructor - defer until first use
}
static getInstance(): PDFWorkerManager {
@@ -26,11 +32,14 @@ class PDFWorkerManager {
}
/**
* Initialize PDF.js worker once globally
* Lazy-load PDF.js library and initialize worker once globally
*/
private initializeWorker(): void {
private async initializeWorker(): Promise<void> {
if (!this.isInitialized) {
GlobalWorkerOptions.workerSrc = new URL(
// Dynamic import - only loads when first PDF operation is needed
this.pdfjsLib = await import('pdfjs-dist/legacy/build/pdf.mjs');
this.pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/legacy/build/pdf.worker.min.mjs',
import.meta.url
).toString();
@@ -51,6 +60,13 @@ class PDFWorkerManager {
verbosity?: number;
} = {}
): Promise<PDFDocumentProxy> {
// Lazy-load PDF.js on first use
await this.initializeWorker();
if (!this.pdfjsLib) {
throw new Error('Failed to load PDF.js library');
}
// Wait if we've hit the worker limit
if (this.activeDocuments.size >= this.maxWorkers) {
await this.waitForAvailableWorker();
@@ -68,7 +84,7 @@ class PDFWorkerManager {
pdfData = data; // Pass through as-is
}
const loadingTask = getDocument(
const loadingTask = this.pdfjsLib.getDocument(
typeof pdfData === 'string' ? {
url: pdfData,
disableAutoFetch: options.disableAutoFetch ?? true,
@@ -1,4 +1,5 @@
import { invoke, isTauri } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
export interface FileOpenService {
getOpenedFiles(): Promise<string[]>;
@@ -60,8 +61,6 @@ class TauriFileOpenService implements FileOpenService {
// Only import if in Tauri environment
if (isTauri()) {
const { listen } = await import('@tauri-apps/api/event');
// Check again after async import
if (isCleanedUp) {
return;
+14 -8
View File
@@ -1,14 +1,18 @@
import { Suspense } from "react";
import { Suspense, lazy } from "react";
import { Routes, Route } from "react-router-dom";
import { AppProviders } from "@app/components/AppProviders";
import { AppLayout } from "@app/components/AppLayout";
import { LoadingFallback } from "@app/components/shared/LoadingFallback";
import Landing from "@app/routes/Landing";
import Login from "@app/routes/Login";
import Signup from "@app/routes/Signup";
import AuthCallback from "@app/routes/AuthCallback";
import InviteAccept from "@app/routes/InviteAccept";
import Onboarding from "@app/components/onboarding/Onboarding";
// Lazy-load auth routes - only loaded when user navigates to them
const Landing = lazy(() => import("@app/routes/Landing"));
const Login = lazy(() => import("@app/routes/Login"));
const Signup = lazy(() => import("@app/routes/Signup"));
const AuthCallback = lazy(() => import("@app/routes/AuthCallback"));
const InviteAccept = lazy(() => import("@app/routes/InviteAccept"));
// Lazy-load Onboarding - only shown once for first-time users
const Onboarding = lazy(() => import("@app/components/onboarding/Onboarding"));
// Import global styles
import "@app/styles/tailwind.css";
@@ -34,7 +38,9 @@ export default function App() {
{/* Main app routes - Landing handles auth logic */}
<Route path="/*" element={<Landing />} />
</Routes>
<Onboarding />
<Suspense fallback={null}>
<Onboarding />
</Suspense>
</AppLayout>
</AppProviders>
</Suspense>
@@ -1,23 +1,25 @@
import React from 'react';
import React, { lazy } from 'react';
import { useTranslation } from 'react-i18next';
import { useConfigNavSections as useCoreConfigNavSections, createConfigNavSections as createCoreConfigNavSections, ConfigNavSection } from '@core/components/shared/config/configNavSections';
import PeopleSection from '@app/components/shared/config/configSections/PeopleSection';
import TeamsSection from '@app/components/shared/config/configSections/TeamsSection';
import AdminGeneralSection from '@app/components/shared/config/configSections/AdminGeneralSection';
import AdminSecuritySection from '@app/components/shared/config/configSections/AdminSecuritySection';
import AdminConnectionsSection from '@app/components/shared/config/configSections/AdminConnectionsSection';
import AdminPrivacySection from '@app/components/shared/config/configSections/AdminPrivacySection';
import AdminDatabaseSection from '@app/components/shared/config/configSections/AdminDatabaseSection';
import AdminAdvancedSection from '@app/components/shared/config/configSections/AdminAdvancedSection';
import AdminLegalSection from '@app/components/shared/config/configSections/AdminLegalSection';
import AdminPlanSection from '@app/components/shared/config/configSections/AdminPlanSection';
import AdminFeaturesSection from '@app/components/shared/config/configSections/AdminFeaturesSection';
import AdminEndpointsSection from '@app/components/shared/config/configSections/AdminEndpointsSection';
import AdminAuditSection from '@app/components/shared/config/configSections/AdminAuditSection';
import AdminUsageSection from '@app/components/shared/config/configSections/AdminUsageSection';
import ApiKeys from '@app/components/shared/config/configSections/ApiKeys';
import AccountSection from '@app/components/shared/config/configSections/AccountSection';
import GeneralSection from '@app/components/shared/config/configSections/GeneralSection';
// Lazy load all config sections
const PeopleSection = lazy(() => import('@app/components/shared/config/configSections/PeopleSection'));
const TeamsSection = lazy(() => import('@app/components/shared/config/configSections/TeamsSection'));
const AdminGeneralSection = lazy(() => import('@app/components/shared/config/configSections/AdminGeneralSection'));
const AdminSecuritySection = lazy(() => import('@app/components/shared/config/configSections/AdminSecuritySection'));
const AdminConnectionsSection = lazy(() => import('@app/components/shared/config/configSections/AdminConnectionsSection'));
const AdminPrivacySection = lazy(() => import('@app/components/shared/config/configSections/AdminPrivacySection'));
const AdminDatabaseSection = lazy(() => import('@app/components/shared/config/configSections/AdminDatabaseSection'));
const AdminAdvancedSection = lazy(() => import('@app/components/shared/config/configSections/AdminAdvancedSection'));
const AdminLegalSection = lazy(() => import('@app/components/shared/config/configSections/AdminLegalSection'));
const AdminPlanSection = lazy(() => import('@app/components/shared/config/configSections/AdminPlanSection'));
const AdminFeaturesSection = lazy(() => import('@app/components/shared/config/configSections/AdminFeaturesSection'));
const AdminEndpointsSection = lazy(() => import('@app/components/shared/config/configSections/AdminEndpointsSection'));
const AdminAuditSection = lazy(() => import('@app/components/shared/config/configSections/AdminAuditSection'));
const AdminUsageSection = lazy(() => import('@app/components/shared/config/configSections/AdminUsageSection'));
const ApiKeys = lazy(() => import('@app/components/shared/config/configSections/ApiKeys'));
const AccountSection = lazy(() => import('@app/components/shared/config/configSections/AccountSection'));
const GeneralSection = lazy(() => import('@app/components/shared/config/configSections/GeneralSection'));
/**
* Hook version of proprietary config nav sections with proper i18n support
+45 -1
View File
@@ -2,6 +2,29 @@ import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import tsconfigPaths from 'vite-tsconfig-paths';
import { viteStaticCopy } from 'vite-plugin-static-copy';
import { visualizer } from "rollup-plugin-visualizer";
import type { OutputBundle } from "rollup";
const maxChunkSize = 500;
function failOnLargeChunks(limitKb: number) {
return {
name: "fail-on-large-chunks",
generateBundle(_: unknown, bundle: OutputBundle) {
for (const [fileName, chunk] of Object.entries(bundle)) {
if (chunk.type === "chunk") {
const sizeKb = Buffer.byteLength(chunk.code) / 1024;
if (sizeKb > limitKb) {
throw new Error(
`❌ Chunk "${fileName}" is ${sizeKb.toFixed(1)} KB (limit: ${limitKb} KB)`
);
}
}
}
},
};
}
export default defineConfig(({ mode }) => {
// When DISABLE_ADDITIONAL_FEATURES is false (or unset), enable proprietary features
@@ -17,6 +40,10 @@ export default defineConfig(({ mode }) => {
return {
plugins: [
visualizer({
open: true,
filename: "stats.html"
}),
react(),
tsconfigPaths({
projects: [tsconfigProject],
@@ -29,8 +56,25 @@ export default defineConfig(({ mode }) => {
dest: 'pdfium'
}
]
})
}),
failOnLargeChunks(maxChunkSize),
],
build: {
chunkSizeWarningLimit: maxChunkSize,
rollupOptions: {
output: {
// Manual chunks for better code splitting
manualChunks: {
// Large UI libraries
'vendor-ui': ['@mantine/core', '@mantine/hooks', '@mantine/dates', '@mantine/dropzone'],
// React and related
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
// Other large dependencies
'vendor-utils': ['jszip'],
},
},
},
},
server: {
host: true,
// make sure this port matches the devUrl port in tauri.conf.json file