mirror of
				https://github.com/Frooodle/Stirling-PDF.git
				synced 2025-11-01 01:21:18 +01: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 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 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>
 | 
			
		||||
    <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')}">
 | 
			
		||||
              </div>
 | 
			
		||||
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 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>
 | 
			
		||||
              </div>
 | 
			
		||||
      <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 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" />
 | 
			
		||||
              </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" />
 | 
			
		||||
              </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>
 | 
			
		||||
 | 
			
		||||
              <br>
 | 
			
		||||
              <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{splitByChapters.submit}"></button>
 | 
			
		||||
            </form>
 | 
			
		||||
      {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>
 | 
			
		||||
 | 
			
		||||
      {/* 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>
 | 
			
		||||
 | 
			
		||||
      {/* 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>
 | 
			
		||||
          ) : (
 | 
			
		||||
            <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>
 | 
			
		||||
    <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
 | 
			
		||||
  </div>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user