From 066bb11814836be57b738effbdcbb0181e6eb2ba Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 18 Apr 2025 09:52:27 -0400 Subject: [PATCH] add IPC --- .gitignore | 1 + public/icon.png => icon.png | Bin manifest.json | 34 +- package-lock.json | 909 +++++++++++++++++- package.json | 13 +- src/frontend/App.tsx | 14 + src/frontend/components/NewTab.tsx | 36 + .../components/Widget.tsx | 38 +- src/frontend/components/WidgetFactory.tsx | 72 ++ .../components/clock-widget}/ClockWidget.tsx | 6 +- .../components}/components/Closebutton.tsx | 0 .../components}/components/CogwheelIcon.tsx | 6 +- .../components}/components/ModalOverlay.tsx | 0 .../components/SettingsComponent.tsx | 69 ++ .../global-settings/GlobalSettingsPanel.tsx | 73 ++ .../global-settings/GlobalSettingsWidget.tsx | 22 + .../tab-contents/BackgroundSettings.tsx | 17 + .../tab-contents/SearchEngineSettings.tsx | 83 ++ .../tab-contents/UtabSettings.tsx | 46 + .../components/grid-overlay/GridOverlay.tsx | 50 + .../search-widget}/SearchWidget.tsx | 19 +- src/{renderer => frontend}/index.tsx | 0 src/frontend/services/ipcService.ts | 4 + .../services/settingsService.ts | 7 +- .../state/backgroundImage.state.ts | 2 +- src/{renderer => frontend/state}/defaults.ts | 54 +- src/frontend/state/edit.state.ts | 3 + .../state/globalSettingsOpen.state.ts | 3 + src/frontend/state/grid.state.ts | 3 + src/frontend/state/incogntio.state.ts | 8 + src/frontend/state/searchEngines.state.ts | 3 + .../state/util/asyncStorageProxy.atom.ts | 24 + .../state/util/globalSettings.atom.ts | 12 + src/frontend/state/widget.state.ts | 3 + src/frontend/types.ts | 58 ++ src/renderer/App.tsx | 6 - .../components/GlobalSettingsPanel.tsx | 68 -- src/renderer/components/NewTab.tsx | 106 -- src/renderer/components/SettingsComponent.tsx | 44 - src/renderer/components/WidgetFactory.tsx | 35 - src/renderer/state/edit.state.ts | 3 - src/renderer/state/globalSettings.atom.ts | 28 - src/renderer/state/searchEngines.state.ts | 3 - src/renderer/state/showGrid.state.ts | 3 - src/renderer/state/widget.state.ts | 3 - src/renderer/types.ts | 37 - src/shared/RPC.ts | 31 + src/worker/Endpoints.ts | 6 + src/worker/main.ts | 9 + .../fonts/IBMPlexMono-Regular.ttf | Bin {public => static}/styles.css | 64 +- public/newtab.html => static/utab.html | 1 + static/widget-styles.css | 37 + tsconfig.json | 7 +- 54 files changed, 1714 insertions(+), 469 deletions(-) rename public/icon.png => icon.png (100%) create mode 100644 src/frontend/App.tsx create mode 100644 src/frontend/components/NewTab.tsx rename src/{renderer => frontend}/components/Widget.tsx (77%) create mode 100644 src/frontend/components/WidgetFactory.tsx rename src/{renderer/components => frontend/components/clock-widget}/ClockWidget.tsx (92%) rename src/{renderer => frontend/components}/components/Closebutton.tsx (100%) rename src/{renderer => frontend/components}/components/CogwheelIcon.tsx (73%) rename src/{renderer => frontend/components}/components/ModalOverlay.tsx (100%) create mode 100644 src/frontend/components/components/SettingsComponent.tsx create mode 100644 src/frontend/components/global-settings/GlobalSettingsPanel.tsx create mode 100644 src/frontend/components/global-settings/GlobalSettingsWidget.tsx create mode 100644 src/frontend/components/global-settings/tab-contents/BackgroundSettings.tsx create mode 100644 src/frontend/components/global-settings/tab-contents/SearchEngineSettings.tsx create mode 100644 src/frontend/components/global-settings/tab-contents/UtabSettings.tsx create mode 100644 src/frontend/components/grid-overlay/GridOverlay.tsx rename src/{renderer/components => frontend/components/search-widget}/SearchWidget.tsx (79%) rename src/{renderer => frontend}/index.tsx (100%) create mode 100644 src/frontend/services/ipcService.ts rename src/{renderer => frontend}/services/settingsService.ts (67%) rename src/{renderer => frontend}/state/backgroundImage.state.ts (52%) rename src/{renderer => frontend/state}/defaults.ts (51%) create mode 100644 src/frontend/state/edit.state.ts create mode 100644 src/frontend/state/globalSettingsOpen.state.ts create mode 100644 src/frontend/state/grid.state.ts create mode 100644 src/frontend/state/incogntio.state.ts create mode 100644 src/frontend/state/searchEngines.state.ts create mode 100644 src/frontend/state/util/asyncStorageProxy.atom.ts create mode 100644 src/frontend/state/util/globalSettings.atom.ts create mode 100644 src/frontend/state/widget.state.ts create mode 100644 src/frontend/types.ts delete mode 100644 src/renderer/App.tsx delete mode 100644 src/renderer/components/GlobalSettingsPanel.tsx delete mode 100644 src/renderer/components/NewTab.tsx delete mode 100644 src/renderer/components/SettingsComponent.tsx delete mode 100644 src/renderer/components/WidgetFactory.tsx delete mode 100644 src/renderer/state/edit.state.ts delete mode 100644 src/renderer/state/globalSettings.atom.ts delete mode 100644 src/renderer/state/searchEngines.state.ts delete mode 100644 src/renderer/state/showGrid.state.ts delete mode 100644 src/renderer/state/widget.state.ts delete mode 100644 src/renderer/types.ts create mode 100644 src/shared/RPC.ts create mode 100644 src/worker/Endpoints.ts create mode 100644 src/worker/main.ts rename {public => static}/fonts/IBMPlexMono-Regular.ttf (100%) rename {public => static}/styles.css (75%) rename public/newtab.html => static/utab.html (85%) create mode 100644 static/widget-styles.css diff --git a/.gitignore b/.gitignore index 0b69a71..1dbe478 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ package-lock.json # Optional: include if you use yarn or want to lock versions # Build output dist/ build/ +worker/build *.bundle.js *.bundle.js.map diff --git a/public/icon.png b/icon.png similarity index 100% rename from public/icon.png rename to icon.png diff --git a/manifest.json b/manifest.json index 8760cf0..f8f2e5a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,16 +1,20 @@ { - "manifest_version": 3, - "name": "Custom New Tab", - "version": "1.0.0", - "description": "Customizable new tab page with background and search", - "chrome_url_overrides": { - "newtab": "newtab.html" - }, - "permissions": [ - "storage" - ], - "icons": { - "128": "icon.png" - }, - "incognito": "spanning" - } \ No newline at end of file + "manifest_version": 3, + "name": "Custom New Tab", + "version": "0.0.1", + "description": "Customizable new tab page with background and search", + "chrome_url_overrides": { + "newtab": "frontend/utab.html" + }, + "permissions": [ + "storage" + ], + "icons": { + "128": "icon.png" + }, + "incognito": "split", + "background": { + "service_worker": "worker/main.js", + "type": "module" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2d7d7a2..05e87fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,10 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^7.0.2", + "@mui/material": "^7.0.2", "jotai": "^2.12.2", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -24,6 +28,262 @@ "typescript": "^5.6.2" } }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "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/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==", + "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==" + }, + "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==", + "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==" + }, + "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==", + "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==", + "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==" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "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==" + }, + "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==", + "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==" + }, + "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==" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", @@ -408,6 +668,283 @@ "node": ">=18" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.0.2.tgz", + "integrity": "sha512-TfeFU9TgN1N06hyb/pV/63FfO34nijZRMqgHk0TJ3gkl4Fbd+wZ73+ZtOd7jag6hMmzO9HSrBc6Vdn591nhkAg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.0.2.tgz", + "integrity": "sha512-Bo57PFLOqXOqPNrXjd8AhzH5s6TCsNUQbvnQ0VKZ8D+lIlteqKnrk/O1luMJUc/BXONK7BfIdTdc7qOnXYbMdw==", + "dependencies": { + "@babel/runtime": "^7.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.0.2", + "@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.0.2", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.0.2.tgz", + "integrity": "sha512-rjJlJ13+3LdLfobRplkXbjIFEIkn6LgpetgU/Cs3Xd8qINCCQK9qXQIjjQ6P0FXFTPFzEVMj0VgBR1mN+FhOcA==", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/core-downloads-tracker": "^7.0.2", + "@mui/system": "^7.0.2", + "@mui/types": "^7.4.1", + "@mui/utils": "^7.0.2", + "@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.0.2", + "@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/private-theming": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.0.2.tgz", + "integrity": "sha512-6lt8heDC9wN8YaRqEdhqnm0cFCv08AMf4IlttFvOVn7ZdKd81PNpD/rEtPGLLwQAFyyKSxBG4/2XCgpbcdNKiA==", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/utils": "^7.0.2", + "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.0.2", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.0.2.tgz", + "integrity": "sha512-11Bt4YdHGlh7sB8P75S9mRCUxTlgv7HGbr0UKz6m6Z9KLeiw1Bm9y/t3iqLLVMvSHYB6zL8X8X+LmfTE++gyBw==", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@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.0.2", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.0.2.tgz", + "integrity": "sha512-yFUraAWYWuKIISPPEVPSQ1NLeqmTT4qiQ+ktmyS8LO/KwHxB+NNVOacEZaIofh5x1NxY8rzphvU5X2heRZ/RDA==", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/private-theming": "^7.0.2", + "@mui/styled-engine": "^7.0.2", + "@mui/types": "^7.4.1", + "@mui/utils": "^7.0.2", + "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.1", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.1.tgz", + "integrity": "sha512-gUL8IIAI52CRXP/MixT1tJKt3SI6tVv4U/9soFsTtAsHzaJQptZ42ffdHZV3niX1ei0aUgMvOxBBN0KYqdG39g==", + "dependencies": { + "@babel/runtime": "^7.27.0" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.0.2.tgz", + "integrity": "sha512-72gcuQjPzhj/MLmPHLCgZjy2VjOH4KniR/4qRtXTTXIEwbkgcN+Y5W/rC90rWtMmZbjt9svZev/z+QHUI4j74w==", + "dependencies": { + "@babel/runtime": "^7.27.0", + "@mui/types": "^7.4.1", + "@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/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@types/chrome": { "version": "0.0.269", "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.269.tgz", @@ -439,17 +976,20 @@ "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", "dev": true }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, "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==", - "devOptional": true + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" }, "node_modules/@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", - "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -464,17 +1004,107 @@ "@types/react": "^18.0.0" } }, + "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==", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", "dev": true }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "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==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } }, "node_modules/esbuild": { "version": "0.23.1", @@ -515,6 +1145,96 @@ "@esbuild/win32-x64": "0.23.1" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "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==" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jotai": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.12.2.tgz", @@ -540,6 +1260,27 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -551,6 +1292,80 @@ "loose-envify": "cli.js" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/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==" + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -574,6 +1389,58 @@ "react": "^18.3.1" } }, + "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==" + }, + "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==", + "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/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -590,6 +1457,30 @@ "loose-envify": "^1.1.0" } }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -619,6 +1510,14 @@ "bin": { "uuid": "dist/bin/uuid" } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } } } } diff --git a/package.json b/package.json index 6180bd2..a11e269 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,12 @@ "description": "A customizable new tab page Chrome extension", "main": "index.js", "scripts": { - "start": "esbuild src/renderer/index.tsx --bundle --outfile=dist/index.js --sourcemap --watch", - "build": "rm -rf dist && mkdir dist && cp -r public/* dist/ && cp manifest.json dist/ && esbuild src/renderer/index.tsx --bundle --outfile=dist/index.js --minify --sourcemap", - "test": "echo \"Error: no test specified\" && exit 1" + "build": "npm run clean && npm run build-extension && npm run copy-static && npm run build-frontend && npm run build-worker", + "build-extension": "mkdir dist && cp manifest.json dist/ && cp icon.png dist/", + "copy-static": "mkdir dist/frontend && cp -r static/* dist/frontend/", + "build-frontend": "esbuild src/frontend/index.tsx --bundle --outfile=dist/frontend/index.js --minify --sourcemap", + "build-worker": "esbuild src/worker/main.ts --bundle --outfile=dist/worker/main.js --minify --sourcemap", + "clean": "rm -rf ./dist" }, "author": "", "license": "ISC", @@ -19,6 +22,10 @@ "typescript": "^5.6.2" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^7.0.2", + "@mui/material": "^7.0.2", "jotai": "^2.12.2", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/frontend/App.tsx b/src/frontend/App.tsx new file mode 100644 index 0000000..f7c530a --- /dev/null +++ b/src/frontend/App.tsx @@ -0,0 +1,14 @@ +import React, { useEffect } from 'react'; +import { NewTab } from './components/NewTab'; +import { useAtom } from 'jotai'; +import { backgroundImageState } from './state/backgroundImage.state'; +import { IpcService } from './services/ipcService'; + +export const App: React.FC = () => { + const [backgroundImage] = useAtom(backgroundImageState) + useEffect(() => { document.body.style.backgroundImage = `url(${backgroundImage})` }, [backgroundImage]); + + IpcService.writeFile({ path: 'testpath', fileContent: 'testcontent' }).then(console.log) + + return ; +}; \ No newline at end of file diff --git a/src/frontend/components/NewTab.tsx b/src/frontend/components/NewTab.tsx new file mode 100644 index 0000000..97b0366 --- /dev/null +++ b/src/frontend/components/NewTab.tsx @@ -0,0 +1,36 @@ +import React, { Suspense } from 'react'; +import { useAtom } from 'jotai' +import { WidgetFactory } from './WidgetFactory'; +import { gridState } from '../state/grid.state'; +import { widgetState } from '../state/widget.state'; +import { GlobalSettingsPanel } from './global-settings/GlobalSettingsPanel'; +import { isIncognitoState } from '../state/incogntio.state'; +import { GridOverlay } from './grid-overlay/GridOverlay'; + +export const NewTab: React.FC = () => { + + const [widgets] = useAtom(widgetState) + const [grid] = useAtom(gridState) + const [isIncognitoOn] = useAtom(isIncognitoState) + + const widgetsToShow = widgets.filter(widget => + widget.showIn === 'both' || + (!isIncognitoOn && widget.showIn === 'standard') || + (isIncognitoOn && widget.showIn === 'incognito') + ) + + return ( + }> + + + + {widgetsToShow.map(widget => ( + + ))} + + ); +}; \ No newline at end of file diff --git a/src/renderer/components/Widget.tsx b/src/frontend/components/Widget.tsx similarity index 77% rename from src/renderer/components/Widget.tsx rename to src/frontend/components/Widget.tsx index 9d6a24c..8a26140 100644 --- a/src/renderer/components/Widget.tsx +++ b/src/frontend/components/Widget.tsx @@ -1,12 +1,11 @@ import React, { useRef, useState, useEffect, ReactElement, useCallback, startTransition } from 'react'; import { WidgetData } from '../types'; -import { CogwheelIcon } from './CogwheelIcon'; +import { CogwheelIcon } from './components/CogwheelIcon'; import { useAtomValue } from 'jotai'; import { editState } from '../state/edit.state'; -import { CloseButton } from './Closebutton'; -import { ModalOverlay } from './ModalOverlay'; -import { SettingsComponent } from './SettingsComponent'; -import { debounce, debounceTime } from 'rxjs'; +import { CloseButton } from './components/Closebutton'; +import { ModalOverlay } from './components/ModalOverlay'; +import { SettingsComponent } from './components/SettingsComponent'; interface WidgetProps { widgetData: WidgetData; @@ -15,9 +14,11 @@ interface WidgetProps { settingsContent: ReactElement; updateWidget: (id: string, data: Partial) => void; removeWidget: (id: string) => void; + disableRemove?: boolean; + disableEdit?: boolean; } -export const Widget: React.FC = ({ widgetData, gridSize, children, settingsContent, updateWidget, removeWidget }) => { +export const Widget: React.FC = ({ widgetData, gridSize, children, settingsContent, updateWidget, removeWidget, disableEdit, disableRemove }) => { const widgetRef = useRef(null); const modalRef = useRef(null); @@ -61,11 +62,11 @@ export const Widget: React.FC = ({ widgetData, gridSize, children, const rect = widgetRef.current.getBoundingClientRect(); const gridX = snapToGrid(rect.left, gridSize.width); const gridY = snapToGrid(rect.top, gridSize.height); - if(gridX !== widgetData.gridX || gridY !== widgetData.gridY){ + if (gridX !== widgetData.y || gridY !== widgetData.x) { // overwrite the values so prevent a visual "bounce" - widgetData.gridX = gridX; - widgetData.gridY = gridY; - updateWidget(widgetData.id, { gridX, gridY }); + widgetData.y = gridX; + widgetData.x = gridY; + updateWidget(widgetData.id, { y: gridX, x: gridY }); } } }; @@ -100,10 +101,10 @@ export const Widget: React.FC = ({ widgetData, gridSize, children, useEffect(() => { if (widgetRef.current && !isDragging) { - widgetRef.current.style.left = `${widgetData.gridX * gridSize.width}px`; - widgetRef.current.style.top = `${widgetData.gridY * gridSize.height}px`; - widgetRef.current.style.width = `${(widgetData.gridWidth) * gridSize.width}px`; - widgetRef.current.style.height = `${(widgetData.gridHeight) * gridSize.height}px`; + widgetRef.current.style.left = `${widgetData.y * gridSize.width}px`; + widgetRef.current.style.top = `${widgetData.x * gridSize.height}px`; + widgetRef.current.style.width = `${(widgetData.width) * gridSize.width}px`; + widgetRef.current.style.height = `${(widgetData.height) * gridSize.height}px`; } }, [widgetData, gridSize, isDragging]); @@ -115,25 +116,28 @@ export const Widget: React.FC = ({ widgetData, gridSize, children, {settingsContent}
- {isEditable && <> + {(isEditable && !widgetData.permanent) && <> removeWidget(widgetData.id)} /> + } + {(isEditable) && - } + } {children}
); diff --git a/src/frontend/components/WidgetFactory.tsx b/src/frontend/components/WidgetFactory.tsx new file mode 100644 index 0000000..fda6a7d --- /dev/null +++ b/src/frontend/components/WidgetFactory.tsx @@ -0,0 +1,72 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { GlobalSettings, WidgetData, WidgetExport, WidgetTypeValues } from '../types'; +import { Widget } from './Widget'; +import { SearchWidget } from './search-widget/SearchWidget'; +import { ClockWidget } from './clock-widget/ClockWidget'; +import { GlobalSettingsWidget } from './global-settings/GlobalSettingsWidget'; +import { useAtom } from 'jotai'; +import { widgetState } from '../state/widget.state'; +import { settingsService } from '../services/settingsService'; + +export interface WidgetFactoryProps { + widget: WidgetData; + grid: GlobalSettings['grid']; +} + +const WidgetMap: { [key in WidgetTypeValues]: WidgetExport } = { + 'clock': ClockWidget, + 'search': SearchWidget, + 'globalsettings': GlobalSettingsWidget +} + +export const WidgetFactory: React.FC = ({ widget, grid }) => { + const [widgets, setWidgets] = useAtom(widgetState) + const [gridSize, setGridSize] = useState({ width: window.innerWidth / grid.cols, height: window.innerHeight / grid.rows }); + + useEffect(() => { + const handleResize = () => { + setGridSize({ + width: window.innerWidth / grid.cols, + height: window.innerHeight / grid.rows + }); + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [window.innerWidth, window.innerHeight, grid]) + + const updateWidgets = useCallback( + (id: string, updates: Partial) => { + console.log(id, updates) + + const updatedWidgets = widgets.map(w => + w.id === id ? { ...w, ...updates } : w + ); + setWidgets(updatedWidgets) + }, + [widgets, setWidgets, settingsService] + ); + + const removeWidget = useCallback( + (widgetId: string) => { + const updatedWidgets = widgets.filter(widget => widget.id !== widgetId) + setWidgets(updatedWidgets) + }, + [widgets, setWidgets, settingsService] + ); + + const Wdgt = WidgetMap[widget.type] + const component = + const settings = Wdgt.settings({ widget, updateWidget: updateWidgets }) + + return ( + + {component} + + ); +}; \ No newline at end of file diff --git a/src/renderer/components/ClockWidget.tsx b/src/frontend/components/clock-widget/ClockWidget.tsx similarity index 92% rename from src/renderer/components/ClockWidget.tsx rename to src/frontend/components/clock-widget/ClockWidget.tsx index fc5b85b..32f4f2c 100644 --- a/src/renderer/components/ClockWidget.tsx +++ b/src/frontend/components/clock-widget/ClockWidget.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { ClockWidgetData, WidgetExport } from '../types'; +import { ClockWidgetData, WidgetExport } from '../../types'; interface ClockWidgetProps { widget: ClockWidgetData; @@ -28,8 +28,8 @@ export const ClockWidgetComponent: React.FC = ({ widget, updat }; return <> -
{formatTime()}
-
{widget.timezone}
+
{formatTime()}
+
{widget.timezone}
; }; diff --git a/src/renderer/components/Closebutton.tsx b/src/frontend/components/components/Closebutton.tsx similarity index 100% rename from src/renderer/components/Closebutton.tsx rename to src/frontend/components/components/Closebutton.tsx diff --git a/src/renderer/components/CogwheelIcon.tsx b/src/frontend/components/components/CogwheelIcon.tsx similarity index 73% rename from src/renderer/components/CogwheelIcon.tsx rename to src/frontend/components/components/CogwheelIcon.tsx index 35e4a04..cd2705a 100644 --- a/src/renderer/components/CogwheelIcon.tsx +++ b/src/frontend/components/components/CogwheelIcon.tsx @@ -1,10 +1,12 @@ type CogwheelIconProps = { size: number; + onClick?: () => void; + className?: any } -export function CogwheelIcon({size}: CogwheelIconProps) { +export function CogwheelIcon({size, className, onClick}: CogwheelIconProps) { return ( - + diff --git a/src/renderer/components/ModalOverlay.tsx b/src/frontend/components/components/ModalOverlay.tsx similarity index 100% rename from src/renderer/components/ModalOverlay.tsx rename to src/frontend/components/components/ModalOverlay.tsx diff --git a/src/frontend/components/components/SettingsComponent.tsx b/src/frontend/components/components/SettingsComponent.tsx new file mode 100644 index 0000000..d166d11 --- /dev/null +++ b/src/frontend/components/components/SettingsComponent.tsx @@ -0,0 +1,69 @@ +import { Dispatch, ReactElement } from "react" +import { WidgetData } from "../../types" +import { SetStateAction, useAtom } from "jotai" +import { incognitoAllowedState, isIncognitoState } from "../../state/incogntio.state" + +type GenericSettingsProps = { + widgetData: WidgetData + updateWidget: (id: string, data: Partial) => void + children: ReactElement | ReactElement[] + settingsOpen?: [boolean, Dispatch>] +} + +export function SettingsComponent({ + widgetData, + updateWidget, + children +}: GenericSettingsProps) { + const [isIncognitoAllowed] = useAtom(incognitoAllowedState) + + return <> + {children} + + + + {isIncognitoAllowed && + + } + + +} \ No newline at end of file diff --git a/src/frontend/components/global-settings/GlobalSettingsPanel.tsx b/src/frontend/components/global-settings/GlobalSettingsPanel.tsx new file mode 100644 index 0000000..96fd71e --- /dev/null +++ b/src/frontend/components/global-settings/GlobalSettingsPanel.tsx @@ -0,0 +1,73 @@ +import { useAtom } from "jotai"; +import { ModalOverlay } from "../components/ModalOverlay"; +import { useState } from "react"; +import { globalSettingsOpenState } from "../../state/globalSettingsOpen.state"; +import { Box, Tab, Tabs, Typography } from "@mui/material"; +import { UtabSettings } from "./tab-contents/UtabSettings"; +import { BackgroundSettings } from "./tab-contents/BackgroundSettings"; +import { SearchEngineSettings } from "./tab-contents/SearchEngineSettings"; + +const settingsComponents = { + 'uTab': UtabSettings, + 'Background': BackgroundSettings, + 'Search': SearchEngineSettings +} + +export function GlobalSettingsPanel() { + const [isOpen, setOpen] = useAtom(globalSettingsOpenState) + const [tab, setTab] = useState(0); + + const handleChange = (event: React.SyntheticEvent, newValue: number) => { + setTab(newValue); + }; + + return ( + + + + {Object.keys(settingsComponents).map(key => )} + + + {Object.values(settingsComponents).map( + (Component, i) => + + + + )} + + + ) +} + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} \ No newline at end of file diff --git a/src/frontend/components/global-settings/GlobalSettingsWidget.tsx b/src/frontend/components/global-settings/GlobalSettingsWidget.tsx new file mode 100644 index 0000000..e2b7f43 --- /dev/null +++ b/src/frontend/components/global-settings/GlobalSettingsWidget.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { CogwheelIcon } from '../components/CogwheelIcon'; +import { useAtom } from 'jotai'; +import { WidgetData, WidgetExport } from '../../types'; +import { globalSettingsOpenState } from '../../state/globalSettingsOpen.state'; + +interface GlobalSettingsProps { + addWidget: (widget: WidgetData) => void, +} + +const GlobalSettingsComponent: React.FC = ({ addWidget }) => { + const [isGlobalSettingsOpen, setGlobalSettingsOpen] = useAtom(globalSettingsOpenState) + + return ( + setGlobalSettingsOpen(!isGlobalSettingsOpen)} /> + ); +}; + +export const GlobalSettingsWidget: WidgetExport = { + settings: () => <>, + widget: GlobalSettingsComponent +} \ No newline at end of file diff --git a/src/frontend/components/global-settings/tab-contents/BackgroundSettings.tsx b/src/frontend/components/global-settings/tab-contents/BackgroundSettings.tsx new file mode 100644 index 0000000..b2dc787 --- /dev/null +++ b/src/frontend/components/global-settings/tab-contents/BackgroundSettings.tsx @@ -0,0 +1,17 @@ +import { TextField } from "@mui/material"; +import { useAtom } from "jotai"; +import { backgroundImageState } from "../../../state/backgroundImage.state"; + +export function BackgroundSettings() { + const [backgroundImage, setBackgroundImage] = useAtom(backgroundImageState) + + return (<> + setBackgroundImage(e.target.value)} + placeholder="Enter image URL" + /> + ) + +} \ No newline at end of file diff --git a/src/frontend/components/global-settings/tab-contents/SearchEngineSettings.tsx b/src/frontend/components/global-settings/tab-contents/SearchEngineSettings.tsx new file mode 100644 index 0000000..2afbe78 --- /dev/null +++ b/src/frontend/components/global-settings/tab-contents/SearchEngineSettings.tsx @@ -0,0 +1,83 @@ +import { Box, Button, IconButton, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material"; +import { useAtom } from "jotai"; +import { availableEnginesState } from "../../../state/searchEngines.state"; +import DeleteIcon from '@mui/icons-material/Delete'; +import AddIcon from '@mui/icons-material/Add'; +import TextField from '@mui/material/TextField'; +import { useCallback, useState } from "react"; + + +export function SearchEngineSettings() { + const [availableSearchEngines, setAvailableSearchEngines] = useAtom(availableEnginesState) + + const [aliasInput, setAliasInput] = useState('') + const [searchStringInput, setSearchStringInput] = useState('') + + const addEngine = useCallback(() => { + const newEngines = { + ...availableSearchEngines, + [aliasInput]: searchStringInput + } + + console.log(newEngines) + + setAvailableSearchEngines(newEngines) + setAliasInput('') + setSearchStringInput('') + }, [availableSearchEngines, aliasInput, searchStringInput, setAvailableSearchEngines, setAliasInput, setSearchStringInput]) + + const removeEngine = useCallback((alias:string) => { + const engines = { ...availableSearchEngines } + delete engines[alias] + setAvailableSearchEngines(engines) + setAliasInput('') + setSearchStringInput('') + }, [availableSearchEngines, setAvailableSearchEngines]) + + + return ( + + + + + + Alias + Searchstring + + + + + {Object.entries(availableSearchEngines).map(([name, searchString]) => ( + + {name} + {searchString} + + + + + ))} + + + setAliasInput(e.target.value)} id="Alias-input" variant="standard" /> + + + setSearchStringInput(e.target.value)} id="Searchstring-input" variant="standard" /> + + + + + + +
+
+
+ + ) +} \ No newline at end of file diff --git a/src/frontend/components/global-settings/tab-contents/UtabSettings.tsx b/src/frontend/components/global-settings/tab-contents/UtabSettings.tsx new file mode 100644 index 0000000..b1d5000 --- /dev/null +++ b/src/frontend/components/global-settings/tab-contents/UtabSettings.tsx @@ -0,0 +1,46 @@ +import { Switch } from "@mui/material"; +import { useAtom } from "jotai"; +import { editState } from "../../../state/edit.state"; +import { gridState } from "../../../state/grid.state"; +import { widgetState } from "../../../state/widget.state"; +import { WidgetTypeValues } from "../../../types"; +import { intitialWidgetData } from "../../../state/defaults"; +import { useCallback } from "react"; +import { incognitoAllowedState } from "../../../state/incogntio.state"; + +export function UtabSettings() { + const [editEnabled, setEditEnabled] = useAtom(editState) + const [grid, setGrid] = useAtom(gridState) + const [widgets, setWidgets] = useAtom(widgetState) + + const createWidget = useCallback( + (type: WidgetTypeValues) => { + const data = intitialWidgetData(type) + const updatedWidgets = [...widgets, data] + setWidgets(updatedWidgets) + }, + [widgets, setWidgets] + ); + + const [isIncognitoAllowed] = useAtom(incognitoAllowedState) + + return (<> + Incognito enabled: {isIncognitoAllowed?'yes':'no'} + + + + + ) +} \ No newline at end of file diff --git a/src/frontend/components/grid-overlay/GridOverlay.tsx b/src/frontend/components/grid-overlay/GridOverlay.tsx new file mode 100644 index 0000000..980bfd2 --- /dev/null +++ b/src/frontend/components/grid-overlay/GridOverlay.tsx @@ -0,0 +1,50 @@ +import { GlobalSettings } from "../../types"; + +const Colors = { + 'red': '#FF3333', + 'blue': '#3333FF' +} as const + +const coloredGrindlines = (i:number, max: number) => { + let color; + + if(max%4 === 0 && i === max/4){ + color = Colors.blue + } + if(max%2 === 0 && i === max/2){ + color = Colors.red + } + if((max*3)%4 === 0 && i === max*3/4){ + color = Colors.blue + } + return { background: color } +} + +type GridOverlayProps = { + grid: GlobalSettings['grid'] +} + +export function GridOverlay({grid}:GridOverlayProps){ + return (<> + {grid.show && ( +
+ {Array.from({ length: grid.cols + 1 }).map((_, i) => ( +
+ ))} + {Array.from({ length: grid.rows + 1 }).map((_, i) => ( +
+ ))} +
+ )} + ) +} \ No newline at end of file diff --git a/src/renderer/components/SearchWidget.tsx b/src/frontend/components/search-widget/SearchWidget.tsx similarity index 79% rename from src/renderer/components/SearchWidget.tsx rename to src/frontend/components/search-widget/SearchWidget.tsx index 1758129..ca06861 100644 --- a/src/renderer/components/SearchWidget.tsx +++ b/src/frontend/components/search-widget/SearchWidget.tsx @@ -1,7 +1,8 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { GlobalSettings, SearchWidgetData, WidgetExport } from '../types'; -import { searchEngineState } from '../state/searchEngines.state'; +import { GlobalSettings, SearchWidgetData, WidgetExport } from '../../types'; +import { availableEnginesState } from '../../state/searchEngines.state'; import { useAtom, useAtomValue } from 'jotai'; +import { TextField } from '@mui/material'; interface SearchWidgetProps { widget: SearchWidgetData; @@ -12,7 +13,7 @@ const makeSearchString = (searchStringTemplate: string, query: string) => search const SearchWidgetComponent: React.FC = ({ widget, updateWidget }) => { - const [availableEngines] = useAtom(searchEngineState) + const [availableEngines] = useAtom(availableEnginesState) const handleSearch = useCallback( (e: React.FormEvent) => { @@ -21,9 +22,6 @@ const SearchWidgetComponent: React.FC = ({ widget, updateWidg if (query) { widget.engines.forEach(engine => { - if (!(engine in availableEngines)) { - return - } setTimeout(() => { window.open(makeSearchString(availableEngines[engine], query), '_blank') }, 200) @@ -34,14 +32,19 @@ const SearchWidgetComponent: React.FC = ({ widget, updateWidg ); return ( -
+
); }; const SearchWidgetSettings = ({ widget, updateWidget }: SearchWidgetProps) => { - const [availableEngines] = useAtom(searchEngineState) + const [availableEngines] = useAtom(availableEnginesState) + + useEffect(() => { + const engines = widget.engines.filter(engine => Object.keys(availableEngines).includes(engine)) + updateWidget(widget.id, { engines }) + }, [availableEngines]) const handleEngineChange = useCallback( (e: any) => { diff --git a/src/renderer/index.tsx b/src/frontend/index.tsx similarity index 100% rename from src/renderer/index.tsx rename to src/frontend/index.tsx diff --git a/src/frontend/services/ipcService.ts b/src/frontend/services/ipcService.ts new file mode 100644 index 0000000..d76f089 --- /dev/null +++ b/src/frontend/services/ipcService.ts @@ -0,0 +1,4 @@ +import { FrontendBindings } from '../../shared/RPC' + +export const IpcService = FrontendBindings + diff --git a/src/renderer/services/settingsService.ts b/src/frontend/services/settingsService.ts similarity index 67% rename from src/renderer/services/settingsService.ts rename to src/frontend/services/settingsService.ts index fc1d93e..9bc398a 100644 --- a/src/renderer/services/settingsService.ts +++ b/src/frontend/services/settingsService.ts @@ -1,5 +1,5 @@ -import { GlobalSettings, WidgetData } from '../types'; -import { DEFAULT_SETTINGS } from '../defaults'; +import { GlobalSettings } from '../types'; +import { DEFAULT_SETTINGS } from '../state/defaults'; export const settingsService = { saveSetting: async (key: K, value: GlobalSettings[K]) => { @@ -13,4 +13,7 @@ export const settingsService = { return loaded[key] !== undefined ? loaded[key] as GlobalSettings[K] : DEFAULT_SETTINGS[key] }, + exportSettings: async (): Promise => { + return await chrome.storage.sync.get(null) as GlobalSettings + } }; \ No newline at end of file diff --git a/src/renderer/state/backgroundImage.state.ts b/src/frontend/state/backgroundImage.state.ts similarity index 52% rename from src/renderer/state/backgroundImage.state.ts rename to src/frontend/state/backgroundImage.state.ts index ebe36ae..e81ef15 100644 --- a/src/renderer/state/backgroundImage.state.ts +++ b/src/frontend/state/backgroundImage.state.ts @@ -1,3 +1,3 @@ -import { globalSettingsAtom } from "./globalSettings.atom"; +import { globalSettingsAtom } from "./util/globalSettings.atom"; export const backgroundImageState = globalSettingsAtom('backgroundUrl') \ No newline at end of file diff --git a/src/renderer/defaults.ts b/src/frontend/state/defaults.ts similarity index 51% rename from src/renderer/defaults.ts rename to src/frontend/state/defaults.ts index 8a2aa81..8a4517e 100644 --- a/src/renderer/defaults.ts +++ b/src/frontend/state/defaults.ts @@ -1,48 +1,66 @@ -import { GlobalSettings, SearchWidgetData, ClockWidgetData, WidgetTypes, WidgetData } from './types'; +import { GlobalSettings, WidgetTypeValues, WidgetTypeMap } from '../types'; - - -export const intitialWidgetData = (type: WidgetTypes): WidgetData => { +export const intitialWidgetData = (type: WidgetTypeValues): WidgetTypeMap[typeof type] => { switch (type) { case 'clock': return { - gridHeight: 3, - gridWidth: 6, - gridX: 15, - gridY: 6, + height: 6, + width: 12, + y: 30, + x: 12, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, showSeconds: false, use24Hour: true, type: 'clock', background: true, - id: `${type}-${Date.now()}` - } as ClockWidgetData + showIn: 'both', + id: `${type}-${Date.now()}`, + } case 'search': return { - gridHeight: 1, - gridWidth: 10, - gridX: 13, - gridY: 6, + height: 2, + width: 20, + y: 26, + x: 12, id: `${type}-${Date.now()}`, type: 'search', engines: ['Google'], background: true, - } as SearchWidgetData + showIn: 'both', + } + case 'globalsettings': + return { + background: false, + height: 2, + width: 2, + y: 2, + x: 2, + id: 'globalsettings', + type: 'globalsettings', + permanent: true, + showIn: 'both' + } } } export const DEFAULT_SETTINGS: GlobalSettings = { + grid: { + show: true, + cols: 72, + rows: 36 + }, backgroundUrl: 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Cat_August_2010-4.jpg/2880px-Cat_August_2010-4.jpg', widgets: [ intitialWidgetData('search'), - intitialWidgetData('clock') + intitialWidgetData('clock'), + intitialWidgetData('globalsettings') ], editEnabled: true, - showGrid: true, searchEngines: { Google: 'https://www.google.com/search?q={s}', Bing: 'https://www.bing.com/search?q={s}', DuckDuckGo: 'https://duckduckgo.com/?q={s}', Yandex: 'https://yandex.com/search/?text={s}' } -}; \ No newline at end of file +}; + diff --git a/src/frontend/state/edit.state.ts b/src/frontend/state/edit.state.ts new file mode 100644 index 0000000..d205c9b --- /dev/null +++ b/src/frontend/state/edit.state.ts @@ -0,0 +1,3 @@ +import { globalSettingsAtom } from "./util/globalSettings.atom"; + +export const editState = globalSettingsAtom("editEnabled") \ No newline at end of file diff --git a/src/frontend/state/globalSettingsOpen.state.ts b/src/frontend/state/globalSettingsOpen.state.ts new file mode 100644 index 0000000..958f5aa --- /dev/null +++ b/src/frontend/state/globalSettingsOpen.state.ts @@ -0,0 +1,3 @@ +import { atom } from "jotai"; + +export const globalSettingsOpenState = atom(false) \ No newline at end of file diff --git a/src/frontend/state/grid.state.ts b/src/frontend/state/grid.state.ts new file mode 100644 index 0000000..2c141dc --- /dev/null +++ b/src/frontend/state/grid.state.ts @@ -0,0 +1,3 @@ +import { globalSettingsAtom } from "./util/globalSettings.atom"; + +export const gridState = globalSettingsAtom('grid') \ No newline at end of file diff --git a/src/frontend/state/incogntio.state.ts b/src/frontend/state/incogntio.state.ts new file mode 100644 index 0000000..fa00c38 --- /dev/null +++ b/src/frontend/state/incogntio.state.ts @@ -0,0 +1,8 @@ +import { atom } from "jotai"; +import { asyncStorageProxyAtom } from "./util/asyncStorageProxy.atom"; + +export const incognitoAllowedState = asyncStorageProxyAtom( + () => chrome.extension.isAllowedIncognitoAccess() +) + +export const isIncognitoState = atom(chrome.extension.inIncognitoContext) \ No newline at end of file diff --git a/src/frontend/state/searchEngines.state.ts b/src/frontend/state/searchEngines.state.ts new file mode 100644 index 0000000..8766ecb --- /dev/null +++ b/src/frontend/state/searchEngines.state.ts @@ -0,0 +1,3 @@ +import { globalSettingsAtom } from "./util/globalSettings.atom"; + +export const availableEnginesState = globalSettingsAtom('searchEngines') \ No newline at end of file diff --git a/src/frontend/state/util/asyncStorageProxy.atom.ts b/src/frontend/state/util/asyncStorageProxy.atom.ts new file mode 100644 index 0000000..5793eef --- /dev/null +++ b/src/frontend/state/util/asyncStorageProxy.atom.ts @@ -0,0 +1,24 @@ +import { atom } from "jotai"; + +export const asyncStorageProxyAtom = ( + getter: () => Promise, + setter: (value: T) => Promise = async () => { throw new Error('AsyncProxyAtom setter not defined') } +) => { + const storage = atom(undefined) + + return atom( + async (get) => { + const existing = get(storage) + + if (existing === undefined) { + const remoteValue = await getter() + return remoteValue + } + return existing + }, + async (get, set, update: T) => { + await setter(update) + set(storage, update) + } + ); +} diff --git a/src/frontend/state/util/globalSettings.atom.ts b/src/frontend/state/util/globalSettings.atom.ts new file mode 100644 index 0000000..6ab8707 --- /dev/null +++ b/src/frontend/state/util/globalSettings.atom.ts @@ -0,0 +1,12 @@ +import { settingsService } from "../../services/settingsService"; +import { GlobalSettings } from "../../types"; +import { asyncStorageProxyAtom } from "./asyncStorageProxy.atom"; + +export const globalSettingsAtom = (key:T) => + asyncStorageProxyAtom( + async () => {const value = await settingsService.getSetting(key); console.log('reading', key, value); return value;}, + (update) => {console.log('saving', key, update); return settingsService.saveSetting(key, update)} + ) + + + diff --git a/src/frontend/state/widget.state.ts b/src/frontend/state/widget.state.ts new file mode 100644 index 0000000..8b3df43 --- /dev/null +++ b/src/frontend/state/widget.state.ts @@ -0,0 +1,3 @@ +import { globalSettingsAtom } from "./util/globalSettings.atom"; + +export const widgetState = globalSettingsAtom('widgets') \ No newline at end of file diff --git a/src/frontend/types.ts b/src/frontend/types.ts new file mode 100644 index 0000000..0e229dc --- /dev/null +++ b/src/frontend/types.ts @@ -0,0 +1,58 @@ +export type WidgetData = { + id: string; + type: T; + y: number; + x: number; + width: number; + height: number; + background: boolean; + permanent?: true; + showIn: 'standard' | 'incognito' | 'both' + [key: string]: any; +} + +export type SearchWidgetData = WidgetData<'search'> & { + engines: Array; +} + +export type ClockWidgetData = WidgetData<'clock'> & { + showSeconds: boolean; + use24Hour: boolean; + timezone: string; +} + +export type GlobalSettingsData = WidgetData<'globalsettings'> & { + permanent: true; + showIn: 'both' +} + +export type WidgetTypeValues = ( + SearchWidgetData | + ClockWidgetData | + GlobalSettingsData +)['type'] + +export type WidgetTypeMap = { + [Key in WidgetType]: WidgetData +} & { + 'clock': ClockWidgetData, + 'search': SearchWidgetData, + 'globalsettings': GlobalSettingsData, +} + +export interface GlobalSettings { + grid: { + show: boolean; + rows: number; + cols: number; + } + backgroundUrl: string; + widgets: WidgetData[]; + editEnabled: boolean; + searchEngines: { [key in string]: string} +} + +export type WidgetExport, S = (...a:any[]) =>JSX.Element > = { + widget: W, + settings: S +} \ No newline at end of file diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx deleted file mode 100644 index 42ccad6..0000000 --- a/src/renderer/App.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import { NewTab } from './components/NewTab'; - -export const App: React.FC = () => { - return ; -}; \ No newline at end of file diff --git a/src/renderer/components/GlobalSettingsPanel.tsx b/src/renderer/components/GlobalSettingsPanel.tsx deleted file mode 100644 index 978aa89..0000000 --- a/src/renderer/components/GlobalSettingsPanel.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { CogwheelIcon } from './CogwheelIcon'; -import { useAtom } from 'jotai'; -import { editState } from '../state/edit.state'; -import { showGridState } from '../state/showGrid.state'; -import { intitialWidgetData } from '../defaults'; -import { WidgetData, WidgetTypes } from '../types'; -import { ModalOverlay } from './ModalOverlay'; -import { backgroundImageState } from '../state/backgroundImage.state'; - -interface SettingsPanelProps { - addWidget: (widget: WidgetData) => void, -} - -export const GlobalSettingsPanel: React.FC = ({ - addWidget -}) => { - const [isModalShowing, setModalShowing] = useState(false); - - const [editEnabled, setEditEnabled] = useAtom(editState); - const [showGrid, setShowGrid] = useAtom(showGridState); - const [backgroundImage, setBackgroundImage] = useAtom(backgroundImageState) - - const createWidget = (type: WidgetTypes) => addWidget(intitialWidgetData(type)); - - return ( - <> - - -

Settings

- - - -
- - -
-
- - ); -}; \ No newline at end of file diff --git a/src/renderer/components/NewTab.tsx b/src/renderer/components/NewTab.tsx deleted file mode 100644 index dc393a9..0000000 --- a/src/renderer/components/NewTab.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import React, { useState, useEffect, useCallback, Suspense, startTransition } from 'react'; -import { useAtom, useAtomValue } from 'jotai' -import { WidgetFactory } from './WidgetFactory'; -import { GlobalSettingsPanel } from './GlobalSettingsPanel'; -import { showGridState } from '../state/showGrid.state'; -import { WidgetData } from '../types'; -import { settingsService } from '../services/settingsService'; -import { backgroundImageState } from '../state/backgroundImage.state'; -import { widgetState } from '../state/widget.state'; - -const GRID_COLS = 36; -const GRID_ROWS = 18; - -const coloredGrindlines = (i:number, max: number) => { - let color; - if(i === max/2){ - color = '#FF3333' - } - return { background: color } -} - - -export const NewTab: React.FC = () => { - const [gridSize, setGridSize] = useState({ width: window.innerWidth / GRID_COLS, height: window.innerHeight / GRID_ROWS }); - const [showGrid] = useAtom(showGridState) - const [widgets, setWidgets] = useAtom(widgetState) - const [backgroundImage] = useAtom(backgroundImageState) - - useEffect(() => { - const handleResize = () => { - setGridSize({ - width: window.innerWidth / GRID_COLS, - height: window.innerHeight / GRID_ROWS - }); - }; - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); - }, [window.innerWidth, window.innerHeight]) - - useEffect(() => { document.body.style.backgroundImage = `url(${backgroundImage})` }, [backgroundImage]); - - const updateWidget = useCallback( - (id: string, updates: Partial) => { - console.log(id, updates) - - const updatedWidgets = widgets.map(w => - w.id === id ? { ...w, ...updates } : w - ); - setWidgets(updatedWidgets) - }, - [widgets, setWidgets, settingsService] - ); - - const addWidget = useCallback( - (data: WidgetData) => { - const updatedWidgets = [...widgets, data] - setWidgets(updatedWidgets) - }, - [widgets, setWidgets, settingsService] - ); - - const removeWidget = useCallback( - (widgetId: string) => { - const updatedWidgets = widgets.filter(widget => widget.id !== widgetId) - setWidgets(updatedWidgets) - }, - [widgets, setWidgets, settingsService] - ); - - return ( - }> - - {showGrid && ( -
- {Array.from({ length: GRID_COLS + 1 }).map((_, i) => ( -
- ))} - {Array.from({ length: GRID_ROWS + 1 }).map((_, i) => ( -
- ))} -
- )} - {widgets.map(widget => ( - - ))} - - ); -}; \ No newline at end of file diff --git a/src/renderer/components/SettingsComponent.tsx b/src/renderer/components/SettingsComponent.tsx deleted file mode 100644 index d7c5976..0000000 --- a/src/renderer/components/SettingsComponent.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { ReactElement } from "react" -import { WidgetData } from "../types" - -type GenericSettingsProps = { - widgetData: WidgetData - updateWidget: (id: string, data: Partial) => void - children: ReactElement | ReactElement[] -} - -export function SettingsComponent({ - widgetData, - updateWidget, - children -}: GenericSettingsProps) { - return <> - {children} - - - - -} \ No newline at end of file diff --git a/src/renderer/components/WidgetFactory.tsx b/src/renderer/components/WidgetFactory.tsx deleted file mode 100644 index 7e6748e..0000000 --- a/src/renderer/components/WidgetFactory.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import { WidgetData, WidgetExport, WidgetTypes } from '../types'; -import { Widget } from './Widget'; -import { SearchWidget } from './SearchWidget'; -import { ClockWidget } from './ClockWidget'; - -interface WidgetFactoryProps { - widget: WidgetData; - gridSize: { width: number; height: number }; - updateWidget: (id: string, data: Partial) => void; - removeWidget: (id:string) => void; -} - -const WidgetMap: { [key in WidgetTypes]: WidgetExport } = { - 'clock': ClockWidget, - 'search': SearchWidget -} - -export const WidgetFactory: React.FC = ({ widget, gridSize, updateWidget, removeWidget }) => { - const Wdgt = WidgetMap[widget.type] - const component = - const settings = Wdgt.settings({ widget: widget, updateWidget }) - - return ( - - {component} - - ); -}; \ No newline at end of file diff --git a/src/renderer/state/edit.state.ts b/src/renderer/state/edit.state.ts deleted file mode 100644 index 165c961..0000000 --- a/src/renderer/state/edit.state.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { globalSettingsAtom } from "./globalSettings.atom"; - -export const editState = globalSettingsAtom("editEnabled") \ No newline at end of file diff --git a/src/renderer/state/globalSettings.atom.ts b/src/renderer/state/globalSettings.atom.ts deleted file mode 100644 index b4541ce..0000000 --- a/src/renderer/state/globalSettings.atom.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { settingsService } from "../services/settingsService"; -import { atom } from "jotai"; -import { GlobalSettings } from "../types"; -import { DEFAULT_SETTINGS } from "../defaults"; - -export const globalSettingsAtom = (key: T) => { - const storage = atom(undefined) - - return atom( - async (get) => { - const existing = get(storage) - - if (existing === undefined) { - const remoteValue = await settingsService.getSetting(key) - console.log("load", key, remoteValue) - return remoteValue - } - return existing - }, - async (get, set, update: GlobalSettings[T]) => { - console.log("save", key, update) - await settingsService.saveSetting(key, update) - set(storage, update) - } - ); -} - - diff --git a/src/renderer/state/searchEngines.state.ts b/src/renderer/state/searchEngines.state.ts deleted file mode 100644 index 6df1be3..0000000 --- a/src/renderer/state/searchEngines.state.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { globalSettingsAtom } from "./globalSettings.atom"; - -export const searchEngineState = globalSettingsAtom('searchEngines') \ No newline at end of file diff --git a/src/renderer/state/showGrid.state.ts b/src/renderer/state/showGrid.state.ts deleted file mode 100644 index c91c696..0000000 --- a/src/renderer/state/showGrid.state.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { globalSettingsAtom } from "./globalSettings.atom"; - -export const showGridState = globalSettingsAtom('showGrid') \ No newline at end of file diff --git a/src/renderer/state/widget.state.ts b/src/renderer/state/widget.state.ts deleted file mode 100644 index b0ee4d6..0000000 --- a/src/renderer/state/widget.state.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { globalSettingsAtom } from "./globalSettings.atom"; - -export const widgetState = globalSettingsAtom('widgets') \ No newline at end of file diff --git a/src/renderer/types.ts b/src/renderer/types.ts deleted file mode 100644 index 5abfac9..0000000 --- a/src/renderer/types.ts +++ /dev/null @@ -1,37 +0,0 @@ -export interface WidgetData { - id: string; - type: WidgetTypes; - gridX: number; - gridY: number; - gridWidth: number; - gridHeight: number; - background: boolean; - [key: string]: any; -} - -export interface SearchWidgetData extends WidgetData { - type: 'search'; - engines: Array; -} - -export interface ClockWidgetData extends WidgetData { - type: 'clock'; - showSeconds: boolean; - use24Hour: boolean; - timezone: string; -} - -export type WidgetTypes = (SearchWidgetData | ClockWidgetData)['type'] - -export interface GlobalSettings { - backgroundUrl: string; - widgets: WidgetData[]; - editEnabled: boolean; - showGrid: boolean; - searchEngines: { [key in string]: string} -} - -export type WidgetExport, S = (...a:any[]) =>JSX.Element > = { - widget: W, - settings: S -} \ No newline at end of file diff --git a/src/shared/RPC.ts b/src/shared/RPC.ts new file mode 100644 index 0000000..f9eb467 --- /dev/null +++ b/src/shared/RPC.ts @@ -0,0 +1,31 @@ +import { ExposedEndpoints } from "../worker/Endpoints" + +type NamedFunction = { name: NameT } & ((a: ArgT) => RetT) + +type Promised = T extends Promise ? T : Promise + +const wrapIpc = (fn: NamedFunction): NamedFunction ? R : Promise, N> => { + const func = function (message: A) { + return new Promise((res, rej) => { + chrome.runtime.sendMessage( + { + endpoint: fn.name, + args: message + }, + (response) => { res(response) } + ) + }) + } + Object.defineProperty(fn, "name", { value: fn.name }); + return func as NamedFunction ? R : Promise, N> +} + +export type RPCs = typeof ExposedEndpoints + +export type FrontendIpcService = { + [endpoint in keyof RPCs]: NamedFunction[0], Promised>, string> +} + +export const FrontendBindings: FrontendIpcService = Object.entries(ExposedEndpoints) + .map(([endpoint, fn]) => ({ [endpoint]: wrapIpc(fn) })) + .reduce((prev, curr) => Object.assign(prev, curr), {}) as FrontendIpcService diff --git a/src/worker/Endpoints.ts b/src/worker/Endpoints.ts new file mode 100644 index 0000000..e1f404d --- /dev/null +++ b/src/worker/Endpoints.ts @@ -0,0 +1,6 @@ +export const ExposedEndpoints = { + writeFile: (message: { path: string, fileContent: string }) => { + console.log("writing", message.fileContent, "to", message.path) + return "OK" + } +} as const; \ No newline at end of file diff --git a/src/worker/main.ts b/src/worker/main.ts new file mode 100644 index 0000000..e4c8ce9 --- /dev/null +++ b/src/worker/main.ts @@ -0,0 +1,9 @@ +import { ExposedEndpoints } from "./Endpoints"; + +chrome.runtime.onMessage.addListener((param: { endpoint: keyof typeof ExposedEndpoints, args: any }, sender, sendReponse) => { + const returnValue = ExposedEndpoints[param.endpoint].apply({}, param.args) + if(returnValue !== undefined){ + sendReponse(returnValue) + return true; + } +}); \ No newline at end of file diff --git a/public/fonts/IBMPlexMono-Regular.ttf b/static/fonts/IBMPlexMono-Regular.ttf similarity index 100% rename from public/fonts/IBMPlexMono-Regular.ttf rename to static/fonts/IBMPlexMono-Regular.ttf diff --git a/public/styles.css b/static/styles.css similarity index 75% rename from public/styles.css rename to static/styles.css index 8c82000..0652843 100644 --- a/public/styles.css +++ b/static/styles.css @@ -1,7 +1,7 @@ /* Define the IBM Plex Mono font */ @font-face { font-family: 'IBM Plex Mono'; - src: url('/fonts/IBMPlexMono-Regular.ttf') format('truetype'); + src: url('./fonts/IBMPlexMono-Regular.ttf') format('truetype'); font-weight: 400; font-style: normal; } @@ -22,7 +22,6 @@ body { z-index: 0; container-type: size; overflow: hidden; - border: 1px solid rgba(255, 255, 255, 0); padding: 10px; border-radius: 5px; } @@ -31,7 +30,7 @@ body { background: rgba(255, 255, 255, 0.2); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); - border-color: rgba(255, 255, 255, 0.3); + border-color: 1px solid rgba(255, 255, 255, 0.3); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); } @@ -79,36 +78,6 @@ body { background: #333; } -/* Styles for ClockWidget time display */ -.clock-time { - color: #ffffff; - font-size: 48px; - font-family: 'IBM Plex Mono', monospace; - text-align: center; - font-size: 50cqmin; -} - -.timezone-display{ - font-size: small; - text-align: center; - color: #ffffff; - font-size: 15cqmin; -} - -/* SearchWidget form */ -.container form { - display: flex; - justify-content: center; -} - -.container form input[type="text"] { - width: 100%; - padding: 5px; - border: 1px solid rgba(255, 255, 255, 0.5); - border-radius: 4px; - background: rgba(255, 255, 255, 1); - color: #000; -} .accept-button { margin-top: 10px; @@ -137,20 +106,18 @@ body { /* Settings toggle (cogwheel for main settings) */ .settings-toggle { - position: fixed; - top: 10px; - left: 10px; background: rgba(255, 255, 255, 0.2); - border: none; - border-radius: 50%; - width: 40px; - height: 40px; cursor: pointer; display: flex; align-items: center; justify-content: center; + left:0; + top:0; + width:100%; + height:100%; } + .settings-toggle:hover { background: rgba(255, 255, 255, 0.4); } @@ -171,8 +138,8 @@ body { padding: 20px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); - max-width: 400px; - width: 90%; + min-width: 400px; + max-width: 60vw; text-align: center; } @@ -187,24 +154,11 @@ body { color: #333; } -.modal-content input[type="text"] { - width: 100%; - padding: 5px; - margin-top: 5px; - border: 1px solid #ccc; - border-radius: 4px; -} - .modal-content .slider { display: block; margin: 10px auto; } -.modal-content input[type="checkbox"] { - margin-left: 10px; - vertical-align: middle; -} - .widget-buttons { margin: 15px 0; } diff --git a/public/newtab.html b/static/utab.html similarity index 85% rename from public/newtab.html rename to static/utab.html index 8b1c355..0e76103 100644 --- a/public/newtab.html +++ b/static/utab.html @@ -4,6 +4,7 @@ New Tab +
diff --git a/static/widget-styles.css b/static/widget-styles.css new file mode 100644 index 0000000..ee17467 --- /dev/null +++ b/static/widget-styles.css @@ -0,0 +1,37 @@ +@font-face { + font-family: 'IBM Plex Mono'; + src: url('./fonts/IBMPlexMono-Regular.ttf') format('truetype'); + font-weight: 400; + font-style: normal; +} + +/* Styles for ClockWidget time display */ +.clock>.time { + color: #ffffff; + font-size: 48px; + font-family: 'IBM Plex Mono', monospace; + text-align: center; + font-size: 50cqmin; +} + +.clock>.timezone { + font-size: small; + text-align: center; + color: #ffffff; + font-size: 15cqmin; +} + +/* Styles for SearchWidget */ +.search form { + display: flex; + justify-content: center; +} + +.search form input[type="text"] { + width: 100%; + padding: 5px; + border: 1px solid rgba(255, 255, 255, 0.5); + border-radius: 4px; + background: rgba(255, 255, 255, 1); + color: #000; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index eaac3c6..7630ce5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,14 +6,13 @@ "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "outDir": "./dist", - "rootDir": "./src", + "rootDir": ".", "sourceMap": true, "jsx": "react-jsx", "moduleResolution": "node", "noImplicitAny": true, "allowSyntheticDefaultImports": true }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] + "include": ["./src/**/*"], + "exclude": ["dist", "node_modules"] } \ No newline at end of file