mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-14 19:45:02 +00:00
Basic homepage with compress and split
This commit is contained in:
parent
b567f4b110
commit
d669964975
546
frontend/package-lock.json
generated
546
frontend/package-lock.json
generated
@ -8,10 +8,15 @@
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.9.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
@ -2352,6 +2357,167 @@
|
||||
"postcss-selector-parser": "^6.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin": {
|
||||
"version": "11.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.16.7",
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/hash": "^0.9.2",
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"convert-source-map": "^1.5.0",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"find-root": "^1.1.0",
|
||||
"source-map": "^0.5.7",
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin/node_modules/source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/cache": {
|
||||
"version": "11.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
|
||||
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/sheet": "^1.4.0",
|
||||
"@emotion/utils": "^1.4.2",
|
||||
"@emotion/weak-memoize": "^0.4.0",
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/hash": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
|
||||
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
|
||||
"integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/memoize": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
|
||||
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/react": {
|
||||
"version": "11.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/cache": "^11.14.0",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||
"@emotion/utils": "^1.4.2",
|
||||
"@emotion/weak-memoize": "^0.4.0",
|
||||
"hoist-non-react-statics": "^3.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/serialize": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
||||
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/hash": "^0.9.2",
|
||||
"@emotion/memoize": "^0.9.0",
|
||||
"@emotion/unitless": "^0.10.0",
|
||||
"@emotion/utils": "^1.4.2",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/sheet": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
|
||||
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/styled": {
|
||||
"version": "11.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
|
||||
"integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/is-prop-valid": "^1.3.0",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||
"@emotion/utils": "^1.4.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.0.0-rc.0",
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/unitless": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
|
||||
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
|
||||
"integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/utils": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
|
||||
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emotion/weak-memoize": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
|
||||
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
||||
@ -2964,6 +3130,251 @@
|
||||
"integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz",
|
||||
"integrity": "sha512-E0OqhZv548Qdc0PwWhLVA2zmjJZSTvaL4ZhoswmI8NJEC1tpW2js6LLP827jrW9MEiXYdz3QS6+hask83w74yQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/icons-material": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.1.0.tgz",
|
||||
"integrity": "sha512-1mUPMAZ+Qk3jfgL5ftRR06ATH/Esi0izHl1z56H+df6cwIlCWG66RXciUqeJCttbOXOQ5y2DCjLZI/4t3Yg3LA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^7.1.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.0.tgz",
|
||||
"integrity": "sha512-ahUJdrhEv+mCp4XHW+tHIEYzZMSRLg8z4AjUOsj44QpD1ZaMxQoVOG2xiHvLFdcsIPbgSRx1bg1eQSheHBgvtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/core-downloads-tracker": "^7.1.0",
|
||||
"@mui/system": "^7.1.0",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@mui/utils": "^7.1.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"clsx": "^2.1.1",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^19.1.0",
|
||||
"react-transition-group": "^4.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/material-pigment-css": "^7.1.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@mui/material-pigment-css": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/react-is": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
|
||||
"integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mui/private-theming": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz",
|
||||
"integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/utils": "^7.1.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/styled-engine": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz",
|
||||
"integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@emotion/cache": "^11.13.5",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/sheet": "^1.4.0",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.0.tgz",
|
||||
"integrity": "sha512-iedAWgRJMCxeMHvkEhsDlbvkK+qKf9me6ofsf7twk/jfT4P1ImVf7Rwb5VubEA0sikrVL+1SkoZM41M4+LNAVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/private-theming": "^7.1.0",
|
||||
"@mui/styled-engine": "^7.1.0",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@mui/utils": "^7.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/types": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.2.tgz",
|
||||
"integrity": "sha512-edRc5JcLPsrlNFYyTPxds+d5oUovuUxnnDtpJUbP6WMeV4+6eaX/mqai1ZIWT62lCOe0nlrON0s9HDiv5en5bA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
|
||||
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@types/prop-types": "^15.7.14",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^19.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils/node_modules/react-is": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
|
||||
"integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
|
||||
"version": "5.1.1-v1",
|
||||
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
||||
@ -3088,6 +3499,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-babel": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
||||
@ -3813,6 +4234,12 @@
|
||||
"integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/q": {
|
||||
"version": "1.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz",
|
||||
@ -3831,6 +4258,25 @@
|
||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz",
|
||||
"integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||
@ -4895,6 +5341,32 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
@ -5633,6 +6105,15 @@
|
||||
"wrap-ansi": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/co": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
@ -6363,6 +6844,12 @@
|
||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
@ -6689,6 +7176,16 @@
|
||||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
|
||||
@ -8157,6 +8654,12 @@
|
||||
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/find-root": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
@ -8842,6 +9345,21 @@
|
||||
"he": "bin/he"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/hoopy": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
|
||||
@ -13627,6 +14145,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||
@ -14038,6 +14562,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@ -15614,6 +16154,12 @@
|
||||
"postcss": "^8.2.15"
|
||||
}
|
||||
},
|
||||
"node_modules/stylis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sucrase": {
|
||||
"version": "3.35.0",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||
|
@ -4,10 +4,15 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:8080",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.9.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
|
6
frontend/postcss.config.js
Normal file
6
frontend/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
@ -10,34 +10,13 @@
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,3 +1,7 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
|
@ -1,97 +1,230 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import ConstructionIcon from '@mui/icons-material/Construction';
|
||||
import AddToPhotosIcon from '@mui/icons-material/AddToPhotos';
|
||||
import ContentCutIcon from '@mui/icons-material/ContentCut';
|
||||
import RotateRightIcon from '@mui/icons-material/RotateRight';
|
||||
import CropIcon from '@mui/icons-material/Crop';
|
||||
import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
||||
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
||||
import LooksOneIcon from '@mui/icons-material/LooksOne';
|
||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
|
||||
import LinkIcon from '@mui/icons-material/Link';
|
||||
import CodeIcon from '@mui/icons-material/Code';
|
||||
import TableChartIcon from '@mui/icons-material/TableChart';
|
||||
import IntegrationInstructionsIcon from '@mui/icons-material/IntegrationInstructions';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import LockOpenIcon from '@mui/icons-material/LockOpen';
|
||||
import EditNoteIcon from '@mui/icons-material/EditNote';
|
||||
import WorkspacePremiumIcon from '@mui/icons-material/WorkspacePremium';
|
||||
import VerifiedIcon from '@mui/icons-material/Verified';
|
||||
import RemoveModeratorIcon from '@mui/icons-material/RemoveModerator';
|
||||
import SanitizerIcon from '@mui/icons-material/Sanitizer';
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import DrawIcon from '@mui/icons-material/Draw';
|
||||
import ApprovalIcon from '@mui/icons-material/Approval';
|
||||
import WaterDropIcon from '@mui/icons-material/WaterDrop';
|
||||
import MenuBookIcon from '@mui/icons-material/MenuBook';
|
||||
import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate';
|
||||
import AssignmentIcon from '@mui/icons-material/Assignment';
|
||||
import CollectionsIcon from '@mui/icons-material/Collections';
|
||||
import LayersClearIcon from '@mui/icons-material/LayersClear';
|
||||
import ScannerIcon from '@mui/icons-material/Scanner';
|
||||
import NoteAltIcon from '@mui/icons-material/NoteAlt';
|
||||
import CompareIcon from '@mui/icons-material/Compare';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import HighlightOffIcon from '@mui/icons-material/HighlightOff';
|
||||
import InvertColorsIcon from '@mui/icons-material/InvertColors';
|
||||
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
||||
import PaletteIcon from '@mui/icons-material/Palette';
|
||||
import ZoomInMapIcon from '@mui/icons-material/ZoomInMap';
|
||||
import BuildIcon from '@mui/icons-material/Build';
|
||||
import DriveFileRenameOutlineIcon from '@mui/icons-material/DriveFileRenameOutline';
|
||||
import JavascriptIcon from '@mui/icons-material/Javascript';
|
||||
import SegmentIcon from '@mui/icons-material/Segment';
|
||||
import LayersIcon from '@mui/icons-material/Layers';
|
||||
import GridOnIcon from '@mui/icons-material/GridOn';
|
||||
import AutoStoriesIcon from '@mui/icons-material/AutoStories';
|
||||
import Icon from '@mui/material/Icon';
|
||||
|
||||
export default function HomePage() {
|
||||
const [homeText, setHomeText] = useState("");
|
||||
const [showSurvey, setShowSurvey] = useState(true);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [favoritesVisible, setFavoritesVisible] = useState(true);
|
||||
import SplitPdfPanel from "../tools/Split";
|
||||
import CompressPdfPanel from "../tools/Compress-pdf";
|
||||
|
||||
useEffect(() => {
|
||||
setHomeText("Your document tools in one secure place");
|
||||
}, []);
|
||||
const toolRegistry = {
|
||||
"split-pdf": { icon: <PictureAsPdfIcon />, name: "Split PDF", component: SplitPdfPanel },
|
||||
"compress-pdf": { icon: <ZoomInMapIcon />, name: "Compress PDF", component: CompressPdfPanel }
|
||||
};
|
||||
|
||||
const features = [
|
||||
{
|
||||
id: "redact",
|
||||
icon: "🖊️",
|
||||
title: "Redact PDF",
|
||||
description: "Remove sensitive content",
|
||||
tags: ["security"]
|
||||
},
|
||||
{
|
||||
id: "multi-tool",
|
||||
icon: "🛠️",
|
||||
title: "Multi-Tool",
|
||||
description: "Bundle many tools together",
|
||||
tags: ["organize"]
|
||||
},
|
||||
{
|
||||
id: "validate-signature",
|
||||
icon: "✔️",
|
||||
title: "Validate Signature",
|
||||
description: "Check document authenticity",
|
||||
tags: ["security"]
|
||||
}
|
||||
];
|
||||
|
||||
const filteredFeatures = features.filter(f =>
|
||||
f.title.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
const tools = Object.entries(toolRegistry).map(([id, { icon, name }]) => ({ id, icon, name }));
|
||||
|
||||
// Example tool panels
|
||||
function ToolPanel({ selectedTool }) {
|
||||
if (!selectedTool) {
|
||||
return (
|
||||
<div style={{ padding: "2rem" }}>
|
||||
<h1>{homeText}</h1>
|
||||
|
||||
<div style={{ margin: "1rem 0" }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search tools..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
style={{ padding: "0.5rem", width: "200px" }}
|
||||
/>
|
||||
|
||||
<select style={{ marginLeft: "1rem", padding: "0.5rem" }}>
|
||||
<option value="alphabetical">Alphabetical</option>
|
||||
<option value="global">Global Popularity</option>
|
||||
</select>
|
||||
|
||||
<button
|
||||
onClick={() => setFavoritesVisible(!favoritesVisible)}
|
||||
style={{ marginLeft: "1rem", padding: "0.5rem" }}
|
||||
>
|
||||
{favoritesVisible ? "Hide" : "Show"} Favorites
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{favoritesVisible && (
|
||||
<div style={{ margin: "1rem 0" }}>
|
||||
<h2>Favorite Tools</h2>
|
||||
<p>(You can add favorites here later)</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h2>Recent Features</h2>
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: "1rem" }}>
|
||||
{filteredFeatures.map((f) => (
|
||||
<div
|
||||
key={f.id}
|
||||
style={{
|
||||
border: "1px solid #ccc",
|
||||
padding: "1rem",
|
||||
borderRadius: "8px",
|
||||
width: "200px"
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: "2rem" }}>{f.icon}</div>
|
||||
<h3>{f.title}</h3>
|
||||
<p>{f.description}</p>
|
||||
<small>{f.tags.join(", ")}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="p-2 border rounded bg-white shadow-sm">
|
||||
<p className="text-sm">Select a tool to begin interacting with the PDF.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="p-2 border rounded bg-white shadow-sm">
|
||||
<h3 className="font-semibold text-sm mb-2">{selectedTool.name}</h3>
|
||||
<p className="text-xs text-gray-600">This is the panel for {selectedTool.name}.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
const tools = [
|
||||
{ id: "multi-tool", icon: <ConstructionIcon />, name: "Multi-Tool" },
|
||||
{ id: "merge-pdfs", icon: <AddToPhotosIcon />, name: "Merge PDFs" },
|
||||
{ id: "split-pdf", icon: <ContentCutIcon />, name: "Split PDF" },
|
||||
{ id: "rotate-pdf", icon: <RotateRightIcon />, name: "Rotate Pages" },
|
||||
{ id: "crop", icon: <CropIcon />, name: "Crop PDF" },
|
||||
{ id: "pdf-organizer", icon: <FormatListBulletedIcon />, name: "PDF Organizer" },
|
||||
{ id: "remove-pages", icon: <DeleteIcon />, name: "Remove Pages" },
|
||||
{ id: "multi-page-layout", icon: <DashboardIcon />, name: "Page Layout" },
|
||||
{ id: "scale-pages", icon: <FullscreenIcon />, name: "Scale Pages" },
|
||||
{ id: "extract-page", icon: <FileUploadIcon />, name: "Extract Page" },
|
||||
{ id: "pdf-to-single-page", icon: <LooksOneIcon />, name: "PDF to Single Page" },
|
||||
{ id: "img-to-pdf", icon: <PictureAsPdfIcon />, name: "Image to PDF" },
|
||||
{ id: "file-to-pdf", icon: <InsertDriveFileIcon />, name: "File to PDF" },
|
||||
{ id: "url-to-pdf", icon: <LinkIcon />, name: "URL to PDF" },
|
||||
{ id: "html-to-pdf", icon: <CodeIcon />, name: "HTML to PDF" },
|
||||
{ id: "markdown-to-pdf", icon: <IntegrationInstructionsIcon />, name: "Markdown to PDF" },
|
||||
{ id: "pdf-to-img", icon: <CollectionsIcon />, name: "PDF to Image" },
|
||||
{ id: "pdf-to-pdfa", icon: <PictureAsPdfIcon />, name: "PDF to PDF/A" },
|
||||
{ id: "pdf-to-word", icon: <InsertDriveFileIcon />, name: "PDF to Word" },
|
||||
{ id: "pdf-to-presentation", icon: <DashboardIcon />, name: "PDF to Presentation" },
|
||||
{ id: "pdf-to-text", icon: <AssignmentIcon />, name: "PDF to Text" },
|
||||
{ id: "pdf-to-html", icon: <CodeIcon />, name: "PDF to HTML" },
|
||||
{ id: "pdf-to-xml", icon: <CodeIcon />, name: "PDF to XML" },
|
||||
{ id: "pdf-to-csv", icon: <TableChartIcon />, name: "PDF to CSV" },
|
||||
{ id: "pdf-to-markdown", icon: <IntegrationInstructionsIcon />, name: "PDF to Markdown" },
|
||||
{ id: "add-password", icon: <LockIcon />, name: "Add Password" },
|
||||
{ id: "remove-password", icon: <LockOpenIcon />, name: "Remove Password" },
|
||||
{ id: "change-permissions", icon: <LockIcon />, name: "Change Permissions" },
|
||||
{ id: "sign", icon: <EditNoteIcon />, name: "Sign PDF" },
|
||||
{ id: "cert-sign", icon: <WorkspacePremiumIcon />, name: "Certify Signature" },
|
||||
{ id: "validate-signature", icon: <VerifiedIcon />, name: "Validate Signature" },
|
||||
{ id: "remove-cert-sign", icon: <RemoveModeratorIcon />, name: "Remove Cert Signature" },
|
||||
{ id: "sanitize-pdf", icon: <SanitizerIcon />, name: "Sanitize PDF" },
|
||||
{ id: "auto-redact", icon: <VisibilityOffIcon />, name: "Auto Redact" },
|
||||
{ id: "redact", icon: <DrawIcon />, name: "Manual Redact" },
|
||||
{ id: "stamp", icon: <ApprovalIcon />, name: "Add Stamp" },
|
||||
{ id: "add-watermark", icon: <WaterDropIcon />, name: "Add Watermark" },
|
||||
{ id: "view-pdf", icon: <MenuBookIcon />, name: "View PDF" },
|
||||
{ id: "add-page-numbers", icon: <LooksOneIcon />, name: "Add Page Numbers" },
|
||||
{ id: "add-image", icon: <AddPhotoAlternateIcon />, name: "Add Image" },
|
||||
{ id: "change-metadata", icon: <AssignmentIcon />, name: "Change Metadata" },
|
||||
{ id: "ocr-pdf", icon: <LayersIcon />, name: "OCR PDF" },
|
||||
{ id: "extract-images", icon: <CollectionsIcon />, name: "Extract Images" },
|
||||
{ id: "flatten", icon: <LayersClearIcon />, name: "Flatten PDF" },
|
||||
{ id: "remove-blanks", icon: <ScannerIcon />, name: "Remove Blank Pages" },
|
||||
{ id: "remove-annotations", icon: <NoteAltIcon />, name: "Remove Annotations" },
|
||||
{ id: "compare", icon: <CompareIcon />, name: "Compare PDFs" },
|
||||
{ id: "get-info-on-pdf", icon: <InfoIcon />, name: "PDF Info" },
|
||||
{ id: "remove-image-pdf", icon: <HighlightOffIcon />, name: "Remove Images from PDF" },
|
||||
{ id: "replace-and-invert-color-pdf", icon: <InvertColorsIcon />, name: "Invert Colors" },
|
||||
{ id: "unlock-pdf-forms", icon: <LayersIcon />, name: "Unlock PDF Forms" },
|
||||
{ id: "pipeline", icon: <AccountTreeIcon />, name: "Pipeline" },
|
||||
{ id: "adjust-contrast", icon: <PaletteIcon />, name: "Adjust Contrast" },
|
||||
{ id: "compress-pdf", icon: <ZoomInMapIcon />, name: "Compress PDF" },
|
||||
{ id: "extract-image-scans", icon: <ScannerIcon />, name: "Extract Image Scans" },
|
||||
{ id: "repair", icon: <BuildIcon />, name: "Repair PDF" },
|
||||
{ id: "auto-rename", icon: <DriveFileRenameOutlineIcon />, name: "Auto Rename" },
|
||||
{ id: "show-javascript", icon: <JavascriptIcon />, name: "Show JavaScript" },
|
||||
{ id: "overlay-pdf", icon: <LayersIcon />, name: "Overlay PDF" },
|
||||
|
||||
];
|
||||
|
||||
const [selectedTool, setSelectedTool] = useState(null);
|
||||
const [search, setSearch] = useState("");
|
||||
const [pdfFile, setPdfFile] = useState(null);
|
||||
const SelectedComponent = selectedTool ? toolRegistry[selectedTool.id]?.component : null;
|
||||
const [downloadUrl, setDownloadUrl] = useState(null);
|
||||
|
||||
const filteredTools = tools.filter(tool =>
|
||||
tool.name.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
|
||||
function handleFileUpload(e) {
|
||||
const file = e.target.files[0];
|
||||
if (file && file.type === "application/pdf") {
|
||||
const fileUrl = URL.createObjectURL(file);
|
||||
setPdfFile({ file, url: fileUrl });
|
||||
}
|
||||
}
|
||||
|
||||
return ( <div className="flex h-screen overflow-hidden">
|
||||
{/* Left Sidebar */}
|
||||
<div className="w-64 bg-gray-100 p-4 flex flex-col space-y-2 overflow-y-auto border-r">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search tools..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="mb-3 px-2 py-1 border rounded text-sm"
|
||||
/>
|
||||
{filteredTools.map(tool => (
|
||||
<button
|
||||
key={tool.id}
|
||||
title={tool.name}
|
||||
onClick={() => setSelectedTool(tool)}
|
||||
className="flex items-center space-x-3 p-2 hover:bg-gray-200 rounded text-left"
|
||||
>
|
||||
<div className="text-xl leading-none flex items-center justify-center h-6 w-6">
|
||||
{tool.icon}
|
||||
</div>
|
||||
<span className="text-sm font-medium">{tool.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Central PDF Viewer Area */}
|
||||
<div className="flex-1 bg-white flex items-center justify-center overflow-hidden">
|
||||
<div className="w-full h-full max-w-5xl max-h-[95vh] border rounded shadow-md bg-gray-50 flex items-center justify-center">
|
||||
{!pdfFile ? (
|
||||
<label className="cursor-pointer text-blue-600 underline">
|
||||
Click to upload a PDF
|
||||
<input
|
||||
type="file"
|
||||
accept="application/pdf"
|
||||
onChange={handleFileUpload}
|
||||
className="hidden"
|
||||
/>
|
||||
</label>
|
||||
) : (
|
||||
<iframe
|
||||
src={pdfFile.url}
|
||||
title="PDF Viewer"
|
||||
className="w-full h-full border-none"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Sidebar: Tool Interactions */}
|
||||
<div className="w-72 bg-gray-50 p-4 border-l overflow-y-auto">
|
||||
<h2 className="text-lg font-semibold mb-4">Tool Panel</h2>
|
||||
<div className="space-y-3">
|
||||
{SelectedComponent ? (
|
||||
<SelectedComponent file={pdfFile} downloadUrl setDownloadUrl />
|
||||
) : selectedTool ? (
|
||||
<div className="p-2 border rounded bg-white shadow-sm">
|
||||
<h3 className="font-semibold text-sm mb-2">{selectedTool.name}</h3>
|
||||
<p className="text-xs text-gray-600">This is the panel for {selectedTool.name}.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-2 border rounded bg-white shadow-sm">
|
||||
<p className="text-sm">Select a tool to begin interacting with the PDF.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
91
frontend/src/tools/Compress-pdf.js
Normal file
91
frontend/src/tools/Compress-pdf.js
Normal file
@ -0,0 +1,91 @@
|
||||
import React, { useState } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
export default function CompressPdfPanel({file}) {
|
||||
const [optimizeLevel, setOptimizeLevel] = useState("5");
|
||||
const [grayscale, setGrayscale] = useState(false);
|
||||
const [expectedOutputSize, setExpectedOutputSize] = useState("");
|
||||
const [status, setStatus] = useState("");
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!file) {
|
||||
setStatus("Please select a file.");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file.file);
|
||||
formData.append("optimizeLevel", optimizeLevel);
|
||||
formData.append("grayscale", grayscale);
|
||||
if (expectedOutputSize) {
|
||||
formData.append("expectedOutputSize", expectedOutputSize);
|
||||
}
|
||||
|
||||
setStatus("Compressing...");
|
||||
|
||||
try {
|
||||
const response = await axios.post("/api/v1/misc/compress-pdf", formData, {
|
||||
responseType: "blob",
|
||||
});
|
||||
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.setAttribute("download", "compressed.pdf");
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
setStatus("Download ready!");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setStatus("Failed to compress PDF.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-4 text-sm">
|
||||
|
||||
<div>
|
||||
<label className="block font-medium">Compression Level (1-9)</label>
|
||||
<select
|
||||
value={optimizeLevel}
|
||||
onChange={(e) => setOptimizeLevel(e.target.value)}
|
||||
className="w-full border px-2 py-1 rounded"
|
||||
>
|
||||
{[...Array(9)].map((_, i) => (
|
||||
<option key={i + 1} value={i + 1}>{i + 1}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="grayscale"
|
||||
checked={grayscale}
|
||||
onChange={(e) => setGrayscale(e.target.checked)}
|
||||
className="mr-2"
|
||||
/>
|
||||
<label htmlFor="grayscale">Convert images to grayscale</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block font-medium">Expected Output Size (e.g. 2MB)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={expectedOutputSize}
|
||||
onChange={(e) => setExpectedOutputSize(e.target.value)}
|
||||
className="w-full border px-2 py-1 rounded"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded">
|
||||
Compress PDF
|
||||
</button>
|
||||
|
||||
{status && <p className="text-xs text-gray-600 mt-2">{status}</p>}
|
||||
</form>
|
||||
);
|
||||
}
|
191
frontend/src/tools/Split.js
Normal file
191
frontend/src/tools/Split.js
Normal file
@ -0,0 +1,191 @@
|
||||
import React, { useState } from "react";
|
||||
import axios from "axios";
|
||||
import DownloadIcon from '@mui/icons-material/Download';
|
||||
|
||||
export default function SplitPdfPanel({ file, downloadUrl, setDownloadUrl }) {
|
||||
const [mode, setMode] = useState("byPages");
|
||||
const [status, setStatus] = useState("");
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!file) {
|
||||
setStatus("Please upload a PDF first.");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file.file);
|
||||
|
||||
let endpoint = "";
|
||||
if (mode === "byPages") {
|
||||
const pageNumbers = document.getElementById("pagesInput").value;
|
||||
formData.append("pageNumbers", pageNumbers);
|
||||
endpoint = "/api/v1/general/split-pages";
|
||||
} else if (mode === "bySections") {
|
||||
const horizontal = document.getElementById("horizontalDivisions").value;
|
||||
const vertical = document.getElementById("verticalDivisions").value;
|
||||
const merge = document.getElementById("merge").checked;
|
||||
formData.append("horizontalDivisions", horizontal);
|
||||
formData.append("verticalDivisions", vertical);
|
||||
formData.append("merge", merge);
|
||||
endpoint = "/api/v1/general/split-pdf-by-sections";
|
||||
} else if (mode === "bySizeOrCount") {
|
||||
const splitType = document.getElementById("splitType").value;
|
||||
const splitValue = document.getElementById("splitValue").value;
|
||||
formData.append("splitType", splitType === "size" ? 0 : splitType === "pages" ? 1 : 2);
|
||||
formData.append("splitValue", splitValue);
|
||||
endpoint = "/api/v1/general/split-by-size-or-count";
|
||||
} else if (mode === "byChapters") {
|
||||
const bookmarkLevel = document.getElementById("bookmarkLevel").value;
|
||||
const includeMetadata = document.getElementById("includeMetadata").checked;
|
||||
const allowDuplicates = document.getElementById("allowDuplicates").checked;
|
||||
formData.append("bookmarkLevel", bookmarkLevel);
|
||||
formData.append("includeMetadata", includeMetadata);
|
||||
formData.append("allowDuplicates", allowDuplicates);
|
||||
endpoint = "/api/v1/general/split-pdf-by-chapters";
|
||||
}
|
||||
|
||||
setStatus("Processing split...");
|
||||
|
||||
try {
|
||||
const response = await axios.post(endpoint, formData, { responseType: "blob" });
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.setAttribute("download", "split_output.zip");
|
||||
document.body.appendChild(link);
|
||||
const blob = new Blob([response.data], { type: "application/zip" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
setDownloadUrl(url);
|
||||
setStatus("Download ready.");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setStatus("Split failed.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="p-2 border rounded bg-white shadow-sm space-y-4 text-sm">
|
||||
<h3 className="font-semibold">Split PDF</h3>
|
||||
|
||||
<div>
|
||||
<label className="block mb-1 font-medium">Split Mode</label>
|
||||
<select
|
||||
value={mode}
|
||||
onChange={(e) => setMode(e.target.value)}
|
||||
className="w-full border px-2 py-1 rounded"
|
||||
>
|
||||
<option value="byPages">Split by Pages (e.g. 1,3,5-10)</option>
|
||||
<option value="bySections">Split by Grid Sections</option>
|
||||
<option value="bySizeOrCount">Split by Size or Count</option>
|
||||
<option value="byChapters">Split by Chapters</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{mode === "byPages" && (
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Pages</label>
|
||||
<input
|
||||
type="text"
|
||||
id="pagesInput"
|
||||
className="w-full border px-2 py-1 rounded"
|
||||
placeholder="e.g. 1,3,5-10"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode === "bySections" && (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Horizontal Divisions</label>
|
||||
<input
|
||||
type="number"
|
||||
id="horizontalDivisions"
|
||||
className="w-full border px-2 py-1 rounded"
|
||||
min="0"
|
||||
max="300"
|
||||
defaultValue="0"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Vertical Divisions</label>
|
||||
<input
|
||||
type="number"
|
||||
id="verticalDivisions"
|
||||
className="w-full border px-2 py-1 rounded"
|
||||
min="0"
|
||||
max="300"
|
||||
defaultValue="1"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input type="checkbox" id="merge" />
|
||||
<label htmlFor="merge">Merge sections into one PDF</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode === "bySizeOrCount" && (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Split Type</label>
|
||||
<select id="splitType" className="w-full border px-2 py-1 rounded">
|
||||
<option value="size">By Size</option>
|
||||
<option value="pages">By Page Count</option>
|
||||
<option value="docs">By Document Count</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Split Value</label>
|
||||
<input
|
||||
type="text"
|
||||
id="splitValue"
|
||||
className="w-full border px-2 py-1 rounded"
|
||||
placeholder="e.g. 10MB or 5 pages"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode === "byChapters" && (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Bookmark Level</label>
|
||||
<input
|
||||
type="number"
|
||||
id="bookmarkLevel"
|
||||
className="w-full border px-2 py-1 rounded"
|
||||
defaultValue="0"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input type="checkbox" id="includeMetadata" />
|
||||
<label htmlFor="includeMetadata">Include Metadata</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input type="checkbox" id="allowDuplicates" />
|
||||
<label htmlFor="allowDuplicates">Allow Duplicate Bookmarks</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded mt-2">
|
||||
Split PDF
|
||||
</button>
|
||||
|
||||
{status && <p className="text-xs text-gray-600">{status}</p>}
|
||||
|
||||
{status === "Download ready." && downloadUrl && (
|
||||
<a
|
||||
href={downloadUrl}
|
||||
download="split_output.zip"
|
||||
className="inline-flex items-center bg-green-600 text-white px-4 py-2 rounded shadow hover:bg-green-700 transition mt-2"
|
||||
>
|
||||
<DownloadIcon className="mr-2" />
|
||||
Download Split PDF
|
||||
</a>
|
||||
)}
|
||||
|
||||
</form>
|
||||
);
|
||||
}
|
9
frontend/tailwind.config.js
Normal file
9
frontend/tailwind.config.js
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,jsx,ts,tsx}"
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
264
package-lock.json
generated
Normal file
264
package-lock.json
generated
Normal file
@ -0,0 +1,264 @@
|
||||
{
|
||||
"name": "Stirling-PDF",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.21",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
||||
"integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.24.4",
|
||||
"caniuse-lite": "^1.0.30001702",
|
||||
"fraction.js": "^4.3.7",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^1.1.1",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"autoprefixer": "bin/autoprefixer"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.24.5",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz",
|
||||
"integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001716",
|
||||
"electron-to-chromium": "^1.5.149",
|
||||
"node-releases": "^2.0.19",
|
||||
"update-browserslist-db": "^1.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001718",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
|
||||
"integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.152",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.152.tgz",
|
||||
"integrity": "sha512-xBOfg/EBaIlVsHipHl2VdTPJRSvErNUaqW8ejTq5OlOlIYx1wOllCHsAvAIrr55jD1IYEfdR86miUEt8H5IeJg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"type": "patreon",
|
||||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/normalize-range": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
|
||||
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.8",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.6",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.6.tgz",
|
||||
"integrity": "sha512-j0cGLTreM6u4OWzBeLBpycK0WIh8w7kSwcUsQZoGLHZ7xDTdM69lN64AgoIEEwFi0tnhs4wSykUa5YWxAzgFYg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escalade": "^3.2.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"update-browserslist-db": "cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"browserslist": ">= 4.21.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
package.json
Normal file
7
package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.3",
|
||||
"tailwindcss": "^4.1.6"
|
||||
}
|
||||
}
|
@ -6,12 +6,13 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
@Controller
|
||||
public class ReactRoutingController {
|
||||
|
||||
@GetMapping(
|
||||
value = {
|
||||
"/{path:^(?!api|static|robots\\.txt|favicon\\.ico).*}",
|
||||
"/**/{path:^(?!.*\\.).*}"
|
||||
})
|
||||
public String forwardToIndex() {
|
||||
@GetMapping("/{path:^(?!api|static|robots\\.txt|favicon\\.ico)[^\\.]*$}")
|
||||
public String forwardRootPaths() {
|
||||
return "forward:/index.html";
|
||||
}
|
||||
|
||||
@GetMapping("/{path:^(?!api|static)[^\\.]*}/{subpath:^(?!.*\\.).*$}")
|
||||
public String forwardNestedPaths() {
|
||||
return "forward:/index.html";
|
||||
}
|
||||
}
|
||||
|
@ -1,72 +1,156 @@
|
||||
<!DOCTYPE html>
|
||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
|
||||
xmlns:th="https://www.thymeleaf.org">
|
||||
import React, { useState } from "react";
|
||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||
|
||||
<head>
|
||||
<th:block th:insert="~{fragments/common :: head(title=#{splitByChapters.title}, header=#{splitByChapters.header})}">
|
||||
</th:block>
|
||||
</head>
|
||||
const tools = [
|
||||
{ id: "split-pdf", icon: <PictureAsPdfIcon />, name: "Split PDF" }
|
||||
];
|
||||
|
||||
<body>
|
||||
<div id="page-container">
|
||||
<div id="content-wrap">
|
||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||
<br><br>
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 bg-card">
|
||||
<div class="tool-header">
|
||||
<svg class="material-symbols-rounded tool-header-icon advance">
|
||||
<use xlink:href="/images/split-chapters.svg#icon-split-chapters"></use>
|
||||
</svg>
|
||||
<span class="tool-header-text" th:text="#{splitByChapters.header}"></span>
|
||||
</div>
|
||||
<form th:action="@{'/api/v1/general/split-pdf-by-chapters'}" method="post" enctype="multipart/form-data">
|
||||
<div
|
||||
th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}">
|
||||
function SplitPdfPanel() {
|
||||
const [mode, setMode] = useState("byPages");
|
||||
return (
|
||||
<div className="p-2 border rounded bg-white shadow-sm space-y-4 text-sm">
|
||||
<h3 className="font-semibold">Split PDF</h3>
|
||||
|
||||
<div>
|
||||
<label className="block mb-1 font-medium">Split Mode</label>
|
||||
<select
|
||||
value={mode}
|
||||
onChange={(e) => setMode(e.target.value)}
|
||||
className="w-full border px-2 py-1 rounded"
|
||||
>
|
||||
<option value="byPages">Split by Pages (e.g. 1,3,5-10)</option>
|
||||
<option value="bySections">Split by Grid Sections</option>
|
||||
<option value="bySizeOrCount">Split by Size or Count</option>
|
||||
<option value="byChapters">Split by Chapters</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="bookmarkLevel" th:text="#{splitByChapters.bookmarkLevel}"></label>
|
||||
<input type="number" class="form-control" id="bookmarkLevel" name="bookmarkLevel" min="0" value="0"
|
||||
required>
|
||||
{mode === "byPages" && (
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Pages</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full border px-2 py-1 rounded"
|
||||
placeholder="e.g. 1,3,5-10"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode === "bySections" && (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Horizontal Divisions</label>
|
||||
<input type="number" className="w-full border px-2 py-1 rounded" min="0" max="300" defaultValue="0" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Vertical Divisions</label>
|
||||
<input type="number" className="w-full border px-2 py-1 rounded" min="0" max="300" defaultValue="1" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input type="checkbox" id="merge" />
|
||||
<label htmlFor="merge">Merge sections into one PDF</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode === "bySizeOrCount" && (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Split Type</label>
|
||||
<select className="w-full border px-2 py-1 rounded">
|
||||
<option value="size">By Size</option>
|
||||
<option value="pages">By Page Count</option>
|
||||
<option value="docs">By Document Count</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Split Value</label>
|
||||
<input type="text" className="w-full border px-2 py-1 rounded" placeholder="e.g. 10MB or 5 pages" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode === "byChapters" && (
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<label className="block font-medium mb-1">Bookmark Level</label>
|
||||
<input type="number" className="w-full border px-2 py-1 rounded" defaultValue="0" min="0" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input type="checkbox" id="includeMetadata" />
|
||||
<label htmlFor="includeMetadata">Include Metadata</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<input type="checkbox" id="allowDuplicates" />
|
||||
<label htmlFor="allowDuplicates">Allow Duplicate Bookmarks</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button className="bg-blue-600 text-white px-4 py-2 rounded mt-2">Split PDF</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
const [selectedTool, setSelectedTool] = useState(null);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const filteredTools = tools.filter(tool =>
|
||||
tool.name.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Left Sidebar */}
|
||||
<div className="w-64 bg-gray-100 p-4 flex flex-col space-y-2 overflow-y-auto border-r">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search tools..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="mb-3 px-2 py-1 border rounded text-sm"
|
||||
/>
|
||||
{filteredTools.map(tool => (
|
||||
<button
|
||||
key={tool.id}
|
||||
title={tool.name}
|
||||
onClick={() => setSelectedTool(tool)}
|
||||
className="flex items-center space-x-3 p-2 hover:bg-gray-200 rounded text-left"
|
||||
>
|
||||
<div className="text-xl leading-none flex items-center justify-center h-6 w-6">
|
||||
{tool.icon}
|
||||
</div>
|
||||
<span className="text-sm font-medium">{tool.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="includeMetadata" name="includeMetadata">
|
||||
<label class="form-check-label" for="includeMetadata"
|
||||
th:text="#{splitByChapters.includeMetadata}"></label>
|
||||
<input type="hidden" name="includeMetadata" value="false" />
|
||||
{/* Central PDF Viewer Area */}
|
||||
<div className="flex-1 bg-white flex items-center justify-center overflow-hidden">
|
||||
<div className="w-full h-full max-w-5xl max-h-[95vh] border rounded shadow-md bg-gray-50 flex items-center justify-center">
|
||||
<span className="text-gray-400 text-lg">PDF Viewer Placeholder</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="allowDuplicates" name="allowDuplicates">
|
||||
<label class="form-check-label" for="allowDuplicates"
|
||||
th:text="#{splitByChapters.allowDuplicates}"></label>
|
||||
<input type="hidden" name="allowDuplicates" value="false" />
|
||||
{/* Right Sidebar: Tool Interactions */}
|
||||
<div className="w-72 bg-gray-50 p-4 border-l overflow-y-auto">
|
||||
<h2 className="text-lg font-semibold mb-4">Tool Panel</h2>
|
||||
<div className="space-y-3">
|
||||
{selectedTool?.id === "split-pdf" ? (
|
||||
<SplitPdfPanel />
|
||||
) : selectedTool ? (
|
||||
<div className="p-2 border rounded bg-white shadow-sm">
|
||||
<h3 className="font-semibold text-sm mb-2">{selectedTool.name}</h3>
|
||||
<p className="text-xs text-gray-600">This is the panel for {selectedTool.name}.</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<a class="btn btn-outline-primary" data-bs-toggle="collapse" href="#info" role="button"
|
||||
aria-expanded="false" aria-controls="info" th:text="#{info}"></a>
|
||||
</p>
|
||||
<div class="collapse" id="info">
|
||||
<p th:text="#{splitByChapters.desc.1}"></p>
|
||||
<p th:text="#{splitByChapters.desc.2}"></p>
|
||||
<p th:text="#{splitByChapters.desc.3}"></p>
|
||||
<p th:text="#{splitByChapters.desc.4}"></p>
|
||||
) : (
|
||||
<div className="p-2 border rounded bg-white shadow-sm">
|
||||
<p className="text-sm">Select a tool to begin interacting with the PDF.</p>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{splitByChapters.submit}"></button>
|
||||
</form>
|
||||
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user