add IPC
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
+19
-15
@@ -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"
|
||||
}
|
||||
"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"
|
||||
}
|
||||
}
|
||||
Generated
+904
-5
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-3
@@ -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",
|
||||
|
||||
@@ -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 <NewTab />;
|
||||
};
|
||||
@@ -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 (
|
||||
<Suspense fallback={<></>}>
|
||||
<GlobalSettingsPanel />
|
||||
<GridOverlay grid={grid} />
|
||||
|
||||
{widgetsToShow.map(widget => (
|
||||
<WidgetFactory
|
||||
key={widget.id}
|
||||
widget={widget}
|
||||
grid={grid}
|
||||
/>
|
||||
))}
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
@@ -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<WidgetData>) => void;
|
||||
removeWidget: (id: string) => void;
|
||||
disableRemove?: boolean;
|
||||
disableEdit?: boolean;
|
||||
}
|
||||
|
||||
export const Widget: React.FC<WidgetProps> = ({ widgetData, gridSize, children, settingsContent, updateWidget, removeWidget }) => {
|
||||
export const Widget: React.FC<WidgetProps> = ({ widgetData, gridSize, children, settingsContent, updateWidget, removeWidget, disableEdit, disableRemove }) => {
|
||||
const widgetRef = useRef<HTMLDivElement>(null);
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -61,11 +62,11 @@ export const Widget: React.FC<WidgetProps> = ({ 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<WidgetProps> = ({ 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<WidgetProps> = ({ widgetData, gridSize, children,
|
||||
<SettingsComponent
|
||||
widgetData={widgetData}
|
||||
updateWidget={updateWidget}
|
||||
settingsOpen={[isSettingsOpen, setSettingsOpen]}
|
||||
>
|
||||
{settingsContent}
|
||||
</SettingsComponent>
|
||||
</ModalOverlay>
|
||||
<div
|
||||
ref={widgetRef}
|
||||
className={`container ${widgetData.background ? 'aero' : ''}`}
|
||||
className={`container ${widgetData.type} ${widgetData.background ? 'aero' : ''}`}
|
||||
onMouseDown={handleMouseDown}
|
||||
style={{ cursor: isEditable && !isSettingsOpen ? 'grab' : 'default' }}
|
||||
>
|
||||
{isEditable && <>
|
||||
{(isEditable && !widgetData.permanent) && <>
|
||||
<CloseButton onClick={() => removeWidget(widgetData.id)} />
|
||||
</>}
|
||||
{(isEditable) &&
|
||||
<button
|
||||
className="settings-button"
|
||||
onClick={() => setSettingsOpen(true)}
|
||||
>
|
||||
<CogwheelIcon size={24} />
|
||||
</button>
|
||||
</>}
|
||||
}
|
||||
{children}
|
||||
</div>
|
||||
</>);
|
||||
@@ -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<WidgetFactoryProps> = ({ 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(
|
||||
<T extends WidgetData>(id: string, updates: Partial<T>) => {
|
||||
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 = <Wdgt.widget widget={widget} updateWidget={updateWidgets}></Wdgt.widget>
|
||||
const settings = Wdgt.settings({ widget, updateWidget: updateWidgets })
|
||||
|
||||
return (
|
||||
<Widget
|
||||
widgetData={widget}
|
||||
gridSize={gridSize}
|
||||
settingsContent={settings}
|
||||
updateWidget={updateWidgets}
|
||||
removeWidget={removeWidget}
|
||||
>
|
||||
{component}
|
||||
</Widget>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -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<ClockWidgetProps> = ({ widget, updat
|
||||
};
|
||||
|
||||
return <>
|
||||
<div className="clock-time">{formatTime()}</div>
|
||||
<div className="timezone-display">{widget.timezone}</div>
|
||||
<div className="time">{formatTime()}</div>
|
||||
<div className="timezone">{widget.timezone}</div>
|
||||
</>;
|
||||
};
|
||||
|
||||
+4
-2
@@ -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 (
|
||||
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<svg className={className} onClick={onClick} width={size} height={size} viewBox={`0 0 ${size} ${size}`} fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l-.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l-.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v-.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l-.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
||||
</svg>
|
||||
@@ -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<WidgetData>) => void
|
||||
children: ReactElement | ReactElement[]
|
||||
settingsOpen?: [boolean, Dispatch<SetStateAction<boolean>>]
|
||||
}
|
||||
|
||||
export function SettingsComponent({
|
||||
widgetData,
|
||||
updateWidget,
|
||||
children
|
||||
}: GenericSettingsProps) {
|
||||
const [isIncognitoAllowed] = useAtom(incognitoAllowedState)
|
||||
|
||||
return <>
|
||||
{children}
|
||||
<label>
|
||||
Width (grid cells):
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={widgetData.width}
|
||||
onChange={e => updateWidget(widgetData.id, { width: parseInt(e.target.value) })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Height (grid cells):
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={widgetData.height}
|
||||
onChange={e => updateWidget(widgetData.id, { height: parseInt(e.target.value) })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Background:
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={widgetData.background}
|
||||
onChange={_ => updateWidget(widgetData.id, { background: !widgetData.background })}
|
||||
/>
|
||||
</label>
|
||||
{isIncognitoAllowed &&
|
||||
<label>
|
||||
Show in standard or incognito tabs?
|
||||
<select
|
||||
value={widgetData.showIn}
|
||||
onChange={e => updateWidget(
|
||||
widgetData.id,
|
||||
{ showIn: e.target.value as WidgetData['showIn'] }
|
||||
)}>
|
||||
{(['standard', 'incognito', 'both'] as Array<WidgetData['showIn']>)
|
||||
.map(showInOption => (
|
||||
<option key={showInOption} value={showInOption}>
|
||||
{showInOption}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
}
|
||||
|
||||
</>
|
||||
}
|
||||
@@ -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 (
|
||||
<ModalOverlay visible={isOpen} setVisible={setOpen}>
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', height: 600 }}>
|
||||
<Tabs
|
||||
orientation="vertical"
|
||||
variant="scrollable"
|
||||
value={tab}
|
||||
onChange={handleChange}
|
||||
aria-label="Vertical tabs example"
|
||||
sx={{ borderRight: 1, borderColor: 'divider', textAlign: 'end' }}
|
||||
>
|
||||
{Object.keys(settingsComponents).map(key => <Tab label={key} />)}
|
||||
</Tabs>
|
||||
|
||||
{Object.values(settingsComponents).map(
|
||||
(Component, i) =>
|
||||
<TabPanel value={tab} index={i}>
|
||||
<Component></Component>
|
||||
</TabPanel>
|
||||
)}
|
||||
</Box>
|
||||
</ModalOverlay>
|
||||
)
|
||||
}
|
||||
|
||||
interface TabPanelProps {
|
||||
children?: React.ReactNode;
|
||||
index: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
function TabPanel(props: TabPanelProps) {
|
||||
const { children, value, index, ...other } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`vertical-tabpanel-${index}`}
|
||||
aria-labelledby={`vertical-tab-${index}`}
|
||||
{...other}
|
||||
>
|
||||
{value === index && (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography>{children}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<GlobalSettingsProps> = ({ addWidget }) => {
|
||||
const [isGlobalSettingsOpen, setGlobalSettingsOpen] = useAtom(globalSettingsOpenState)
|
||||
|
||||
return (
|
||||
<CogwheelIcon className="settings-toggle" size={24} onClick={() => setGlobalSettingsOpen(!isGlobalSettingsOpen)} />
|
||||
);
|
||||
};
|
||||
|
||||
export const GlobalSettingsWidget: WidgetExport = {
|
||||
settings: () => <></>,
|
||||
widget: GlobalSettingsComponent
|
||||
}
|
||||
@@ -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 (<>
|
||||
<TextField
|
||||
type="text"
|
||||
value={backgroundImage}
|
||||
onChange={(e) => setBackgroundImage(e.target.value)}
|
||||
placeholder="Enter image URL"
|
||||
/>
|
||||
</>)
|
||||
|
||||
}
|
||||
@@ -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 (
|
||||
<Box>
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell align="left" style={{ fontWeight: 'bold' }}>Alias</TableCell>
|
||||
<TableCell align="left" style={{ fontWeight: 'bold' }}>Searchstring</TableCell>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{Object.entries(availableSearchEngines).map(([name, searchString]) => (
|
||||
<TableRow
|
||||
key={name}
|
||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<TableCell align="left">{name}</TableCell>
|
||||
<TableCell align="left">{searchString}</TableCell>
|
||||
<TableCell>
|
||||
<Button onClick={_ => removeEngine(name)} variant="outlined" size="small" color="warning" startIcon={<DeleteIcon />}>
|
||||
Remove
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<TextField value={aliasInput} onChange={e => setAliasInput(e.target.value)} id="Alias-input" variant="standard" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TextField value={searchStringInput} onChange={e => setSearchStringInput(e.target.value)} id="Searchstring-input" variant="standard" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button onClick={addEngine} variant="outlined" size="small" startIcon={<AddIcon />}>
|
||||
Add
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
|
||||
)
|
||||
}
|
||||
@@ -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'}
|
||||
<label>
|
||||
Editmode:
|
||||
<Switch
|
||||
checked={editEnabled}
|
||||
onChange={() => setEditEnabled(!editEnabled)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Show Grid:
|
||||
<Switch
|
||||
checked={grid.show}
|
||||
onChange={() => setGrid({ ...grid, show: !grid.show})}
|
||||
/>
|
||||
</label>
|
||||
<button onClick={() => createWidget('clock')}>Add Clock</button>
|
||||
<button onClick={() => createWidget('search')}>Add Search</button>
|
||||
</>)
|
||||
}
|
||||
@@ -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 && (
|
||||
<div className="grid-overlay">
|
||||
{Array.from({ length: grid.cols + 1 }).map((_, i) => (
|
||||
<div key={`v${i}`}
|
||||
className="grid-line vertical"
|
||||
style={{
|
||||
left: `${(i / grid.cols) * 100}%`,
|
||||
...coloredGrindlines(i, grid.cols)
|
||||
}} />
|
||||
))}
|
||||
{Array.from({ length: grid.rows + 1 }).map((_, i) => (
|
||||
<div key={`h${i}`}
|
||||
className="grid-line horizontal"
|
||||
style={{
|
||||
top: `${(i / grid.rows) * 100}%`,
|
||||
...coloredGrindlines(i, grid.rows),
|
||||
}} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>)
|
||||
}
|
||||
+11
-8
@@ -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<SearchWidgetProps> = ({ widget, updateWidget }) => {
|
||||
|
||||
const [availableEngines] = useAtom(searchEngineState)
|
||||
const [availableEngines] = useAtom(availableEnginesState)
|
||||
|
||||
const handleSearch = useCallback(
|
||||
(e: React.FormEvent<HTMLFormElement>) => {
|
||||
@@ -21,9 +22,6 @@ const SearchWidgetComponent: React.FC<SearchWidgetProps> = ({ 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<SearchWidgetProps> = ({ widget, updateWidg
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSearch}>
|
||||
<form onSubmit={handleSearch} style={{ width: '100%', height: '100%', fontSize: '50cqmin' }}>
|
||||
<input type="text" name="query" placeholder={`Search ${widget.engines}`} autoComplete="off" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
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) => {
|
||||
@@ -0,0 +1,4 @@
|
||||
import { FrontendBindings } from '../../shared/RPC'
|
||||
|
||||
export const IpcService = FrontendBindings
|
||||
|
||||
@@ -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 <K extends keyof GlobalSettings>(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<GlobalSettings> => {
|
||||
return await chrome.storage.sync.get(null) as GlobalSettings
|
||||
}
|
||||
};
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
import { globalSettingsAtom } from "./globalSettings.atom";
|
||||
import { globalSettingsAtom } from "./util/globalSettings.atom";
|
||||
|
||||
export const backgroundImageState = globalSettingsAtom('backgroundUrl')
|
||||
@@ -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}'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { globalSettingsAtom } from "./util/globalSettings.atom";
|
||||
|
||||
export const editState = globalSettingsAtom("editEnabled")
|
||||
@@ -0,0 +1,3 @@
|
||||
import { atom } from "jotai";
|
||||
|
||||
export const globalSettingsOpenState = atom(false)
|
||||
@@ -0,0 +1,3 @@
|
||||
import { globalSettingsAtom } from "./util/globalSettings.atom";
|
||||
|
||||
export const gridState = globalSettingsAtom('grid')
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
import { globalSettingsAtom } from "./util/globalSettings.atom";
|
||||
|
||||
export const availableEnginesState = globalSettingsAtom('searchEngines')
|
||||
@@ -0,0 +1,24 @@
|
||||
import { atom } from "jotai";
|
||||
|
||||
export const asyncStorageProxyAtom = <T>(
|
||||
getter: () => Promise<T>,
|
||||
setter: (value: T) => Promise<void> = async () => { throw new Error('AsyncProxyAtom setter not defined') }
|
||||
) => {
|
||||
const storage = atom<T | undefined>(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)
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { settingsService } from "../../services/settingsService";
|
||||
import { GlobalSettings } from "../../types";
|
||||
import { asyncStorageProxyAtom } from "./asyncStorageProxy.atom";
|
||||
|
||||
export const globalSettingsAtom = <T extends keyof GlobalSettings>(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)}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { globalSettingsAtom } from "./util/globalSettings.atom";
|
||||
|
||||
export const widgetState = globalSettingsAtom('widgets')
|
||||
@@ -0,0 +1,58 @@
|
||||
export type WidgetData<T extends WidgetTypeValues = WidgetTypeValues> = {
|
||||
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<string>;
|
||||
}
|
||||
|
||||
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<WidgetType extends WidgetTypeValues = WidgetTypeValues> = {
|
||||
[Key in WidgetType]: WidgetData<Key>
|
||||
} & {
|
||||
'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<W = React.FC<any>, S = (...a:any[]) =>JSX.Element > = {
|
||||
widget: W,
|
||||
settings: S
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import React from 'react';
|
||||
import { NewTab } from './components/NewTab';
|
||||
|
||||
export const App: React.FC = () => {
|
||||
return <NewTab />;
|
||||
};
|
||||
@@ -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<SettingsPanelProps> = ({
|
||||
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 (
|
||||
<>
|
||||
<button className="settings-toggle" onClick={() => setModalShowing(true)}>
|
||||
<CogwheelIcon size={24} />
|
||||
</button>
|
||||
<ModalOverlay
|
||||
setVisible={setModalShowing}
|
||||
visible={isModalShowing}
|
||||
>
|
||||
<h2>Settings</h2>
|
||||
<label>
|
||||
Background URL:
|
||||
<input
|
||||
type="text"
|
||||
value={backgroundImage}
|
||||
onChange={(e) => setBackgroundImage(e.target.value)}
|
||||
placeholder="Enter image URL"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Drag Enabled:
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={editEnabled}
|
||||
onChange={() => setEditEnabled(!editEnabled)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Show Grid:
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showGrid}
|
||||
onChange={() => setShowGrid(!showGrid)}
|
||||
/>
|
||||
</label>
|
||||
<div className="widget-buttons">
|
||||
<button onClick={() => createWidget('clock')}>Add Clock</button>
|
||||
<button onClick={() => createWidget('search')}>Add Search</button>
|
||||
</div>
|
||||
</ModalOverlay >
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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(
|
||||
<T extends WidgetData>(id: string, updates: Partial<T>) => {
|
||||
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 (
|
||||
<Suspense fallback={<></>}>
|
||||
<GlobalSettingsPanel
|
||||
addWidget={addWidget}
|
||||
/>
|
||||
{showGrid && (
|
||||
<div className="grid-overlay">
|
||||
{Array.from({ length: GRID_COLS + 1 }).map((_, i) => (
|
||||
<div key={`v${i}`}
|
||||
className="grid-line vertical"
|
||||
style={{
|
||||
left: `${(i / GRID_COLS) * 100}%`,
|
||||
...coloredGrindlines(i, GRID_COLS)
|
||||
}} />
|
||||
))}
|
||||
{Array.from({ length: GRID_ROWS + 1 }).map((_, i) => (
|
||||
<div key={`h${i}`}
|
||||
className="grid-line horizontal"
|
||||
style={{
|
||||
top: `${(i / GRID_ROWS) * 100}%`,
|
||||
...coloredGrindlines(i, GRID_ROWS),
|
||||
}} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{widgets.map(widget => (
|
||||
<WidgetFactory
|
||||
key={widget.id}
|
||||
widget={widget}
|
||||
gridSize={gridSize}
|
||||
updateWidget={updateWidget}
|
||||
removeWidget={removeWidget}
|
||||
/>
|
||||
))}
|
||||
</ Suspense>
|
||||
);
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
import { ReactElement } from "react"
|
||||
import { WidgetData } from "../types"
|
||||
|
||||
type GenericSettingsProps = {
|
||||
widgetData: WidgetData
|
||||
updateWidget: (id: string, data: Partial<WidgetData>) => void
|
||||
children: ReactElement | ReactElement[]
|
||||
}
|
||||
|
||||
export function SettingsComponent({
|
||||
widgetData,
|
||||
updateWidget,
|
||||
children
|
||||
}: GenericSettingsProps) {
|
||||
return <>
|
||||
{children}
|
||||
<label>
|
||||
Width (grid cells):
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={widgetData.gridWidth}
|
||||
onChange={e => updateWidget(widgetData.id, { gridWidth: parseInt(e.target.value) })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Height (grid cells):
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
value={widgetData.gridHeight}
|
||||
onChange={e => updateWidget(widgetData.id, { gridHeight: parseInt(e.target.value) })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Background:
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={widgetData.background}
|
||||
onChange={_ => updateWidget(widgetData.id, { background: !widgetData.background })}
|
||||
/>
|
||||
</label>
|
||||
</>
|
||||
}
|
||||
@@ -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<WidgetData>) => void;
|
||||
removeWidget: (id:string) => void;
|
||||
}
|
||||
|
||||
const WidgetMap: { [key in WidgetTypes]: WidgetExport } = {
|
||||
'clock': ClockWidget,
|
||||
'search': SearchWidget
|
||||
}
|
||||
|
||||
export const WidgetFactory: React.FC<WidgetFactoryProps> = ({ widget, gridSize, updateWidget, removeWidget }) => {
|
||||
const Wdgt = WidgetMap[widget.type]
|
||||
const component = <Wdgt.widget widget={widget} updateWidget={updateWidget}></Wdgt.widget>
|
||||
const settings = Wdgt.settings({ widget: widget, updateWidget })
|
||||
|
||||
return (
|
||||
<Widget
|
||||
widgetData={widget}
|
||||
gridSize={gridSize}
|
||||
settingsContent={settings}
|
||||
updateWidget={updateWidget}
|
||||
removeWidget={removeWidget}
|
||||
>
|
||||
{component}
|
||||
</Widget>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
import { globalSettingsAtom } from "./globalSettings.atom";
|
||||
|
||||
export const editState = globalSettingsAtom("editEnabled")
|
||||
@@ -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 = <T extends keyof GlobalSettings>(key: T) => {
|
||||
const storage = atom<GlobalSettings[T] | undefined>(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)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { globalSettingsAtom } from "./globalSettings.atom";
|
||||
|
||||
export const searchEngineState = globalSettingsAtom('searchEngines')
|
||||
@@ -1,3 +0,0 @@
|
||||
import { globalSettingsAtom } from "./globalSettings.atom";
|
||||
|
||||
export const showGridState = globalSettingsAtom('showGrid')
|
||||
@@ -1,3 +0,0 @@
|
||||
import { globalSettingsAtom } from "./globalSettings.atom";
|
||||
|
||||
export const widgetState = globalSettingsAtom('widgets')
|
||||
@@ -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<string>;
|
||||
}
|
||||
|
||||
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<W = React.FC<any>, S = (...a:any[]) =>JSX.Element > = {
|
||||
widget: W,
|
||||
settings: S
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ExposedEndpoints } from "../worker/Endpoints"
|
||||
|
||||
type NamedFunction<ArgT, RetT, NameT extends string> = { name: NameT } & ((a: ArgT) => RetT)
|
||||
|
||||
type Promised<T> = T extends Promise<any> ? T : Promise<T>
|
||||
|
||||
const wrapIpc = <A, R, N extends string = any>(fn: NamedFunction<A, R, N>): NamedFunction<A, R extends Promise<any> ? R : Promise<R>, 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<A, R extends Promise<any> ? R : Promise<R>, N>
|
||||
}
|
||||
|
||||
export type RPCs = typeof ExposedEndpoints
|
||||
|
||||
export type FrontendIpcService = {
|
||||
[endpoint in keyof RPCs]: NamedFunction<Parameters<RPCs[endpoint]>[0], Promised<ReturnType<RPCs[endpoint]>>, string>
|
||||
}
|
||||
|
||||
export const FrontendBindings: FrontendIpcService = Object.entries(ExposedEndpoints)
|
||||
.map(([endpoint, fn]) => ({ [endpoint]: wrapIpc(fn) }))
|
||||
.reduce((prev, curr) => Object.assign(prev, curr), {}) as FrontendIpcService
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>New Tab</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="widget-styles.css">
|
||||
</head>
|
||||
<body style="height: 100%; margin: 0; padding: 0;">
|
||||
<div id="root"></div>
|
||||
@@ -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;
|
||||
}
|
||||
+3
-4
@@ -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"]
|
||||
}
|
||||
Reference in New Issue
Block a user