diff --git a/package-lock.json b/package-lock.json index 6336462..5a47808 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,13 +13,12 @@ "@emotion/styled": "^11.10.5", "@mui/icons-material": "^5.11.0", "@mui/material": "^5.11.2", - "@reduxjs/toolkit": "^1.9.1", "async-mutex": "^0.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^8.0.5", "sjcl-all": "^1.0.1", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "zustand": "^5.0.1" }, "devDependencies": { "@types/chai": "^4.3.4", @@ -1004,29 +1003,6 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@reduxjs/toolkit": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.1.tgz", - "integrity": "sha512-HikrdY+IDgRfRYlCTGUQaiCxxDDgM1mQrRbZ6S1HFZX5ZYuJ4o8EstNmhTwHdPl2rTmLxzwSu0b3AyeyTlR+RA==", - "dependencies": { - "immer": "^9.0.16", - "redux": "^4.2.0", - "redux-thunk": "^2.4.2", - "reselect": "^4.1.7" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.0.2" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-redux": { - "optional": true - } - } - }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -1123,15 +1099,6 @@ "integrity": "sha512-IG8AE1m2pWtPqQ7wXhFhy6Q59bwwnLwO36v5Rit2FrbXCIp8Sk8E2PfUCreyrdo17STwFSKDAkitVuVYbpEHvQ==", "dev": true }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1174,7 +1141,7 @@ "version": "18.0.10", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", - "devOptional": true, + "dev": true, "dependencies": { "@types/react": "*" } @@ -1206,11 +1173,6 @@ "integrity": "sha512-gP9M7Oq4xAoDpuueWHeOTasccV4K6iFBEBPbR0tXejKT/e/Gkla0XcJ9REmgSCICt6y8/ubcvXFtiZ4ZLQ6BKA==", "dev": true }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" - }, "node_modules/@types/uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz", @@ -2602,6 +2564,8 @@ "version": "9.0.17", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.17.tgz", "integrity": "sha512-+hBruaLSQvkPfxRiTLK/mi4vLH+/VQS6z2KJahdoxlleFOI8ARqzOF17uy12eFDlqWmPoygwc5evgwcp+dlHhg==", + "optional": true, + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -3556,44 +3520,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, - "node_modules/react-redux": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", - "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -3633,22 +3559,6 @@ "node": ">= 0.10" } }, - "node_modules/redux": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", - "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", - "peerDependencies": { - "redux": "^4" - } - }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -3663,11 +3573,6 @@ "node": ">=0.10.0" } }, - "node_modules/reselect": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", - "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" - }, "node_modules/resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -4201,6 +4106,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "optional": true, + "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } @@ -4549,6 +4456,34 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz", + "integrity": "sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } }, "dependencies": { @@ -5211,17 +5146,6 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" }, - "@reduxjs/toolkit": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.1.tgz", - "integrity": "sha512-HikrdY+IDgRfRYlCTGUQaiCxxDDgM1mQrRbZ6S1HFZX5ZYuJ4o8EstNmhTwHdPl2rTmLxzwSu0b3AyeyTlR+RA==", - "requires": { - "immer": "^9.0.16", - "redux": "^4.2.0", - "redux-thunk": "^2.4.2", - "reselect": "^4.1.7" - } - }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -5318,15 +5242,6 @@ "integrity": "sha512-IG8AE1m2pWtPqQ7wXhFhy6Q59bwwnLwO36v5Rit2FrbXCIp8Sk8E2PfUCreyrdo17STwFSKDAkitVuVYbpEHvQ==", "dev": true }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, "@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -5369,7 +5284,7 @@ "version": "18.0.10", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz", "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==", - "devOptional": true, + "dev": true, "requires": { "@types/react": "*" } @@ -5401,11 +5316,6 @@ "integrity": "sha512-gP9M7Oq4xAoDpuueWHeOTasccV4K6iFBEBPbR0tXejKT/e/Gkla0XcJ9REmgSCICt6y8/ubcvXFtiZ4ZLQ6BKA==", "dev": true }, - "@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" - }, "@types/uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz", @@ -6468,7 +6378,9 @@ "immer": { "version": "9.0.17", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.17.tgz", - "integrity": "sha512-+hBruaLSQvkPfxRiTLK/mi4vLH+/VQS6z2KJahdoxlleFOI8ARqzOF17uy12eFDlqWmPoygwc5evgwcp+dlHhg==" + "integrity": "sha512-+hBruaLSQvkPfxRiTLK/mi4vLH+/VQS6z2KJahdoxlleFOI8ARqzOF17uy12eFDlqWmPoygwc5evgwcp+dlHhg==", + "optional": true, + "peer": true }, "import-fresh": { "version": "3.3.0", @@ -7176,19 +7088,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, - "react-redux": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", - "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", - "requires": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - } - }, "react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -7218,20 +7117,6 @@ "resolve": "^1.9.0" } }, - "redux": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz", - "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==", - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", - "requires": {} - }, "regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -7243,11 +7128,6 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, - "reselect": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", - "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" - }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -7621,6 +7501,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "optional": true, + "peer": true, "requires": {} }, "util-deprecate": { @@ -7866,6 +7748,12 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zustand": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz", + "integrity": "sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==", + "requires": {} } } } diff --git a/package.json b/package.json index 375a441..c10585a 100644 --- a/package.json +++ b/package.json @@ -20,13 +20,12 @@ "@emotion/styled": "^11.10.5", "@mui/icons-material": "^5.11.0", "@mui/material": "^5.11.2", - "@reduxjs/toolkit": "^1.9.1", "async-mutex": "^0.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-redux": "^8.0.5", "sjcl-all": "^1.0.1", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "zustand": "^5.0.1" }, "devDependencies": { "@types/chai": "^4.3.4", diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx index 429a744..eca1c2a 100644 --- a/src/components/Dropdown.tsx +++ b/src/components/Dropdown.tsx @@ -29,7 +29,7 @@ const DropdownContainer = styled(Popper)(({ theme })=>({ const Dropdown: React.FC = (props)=> { - const settings = useSettings(); + const settings = useSettings(state=>state.settings); // Keep track of the anchor element's width const [minWidth, setMinWidth] = React.useState(props.anchorEl.clientWidth); diff --git a/src/components/EmbeddedPicker/CredentialPicker.tsx b/src/components/EmbeddedPicker/CredentialPicker.tsx index 27e269e..af29e6a 100644 --- a/src/components/EmbeddedPicker/CredentialPicker.tsx +++ b/src/components/EmbeddedPicker/CredentialPicker.tsx @@ -25,7 +25,7 @@ export const CredentialPicker: React.FC = (props)=> const dropdown = React.useRef>(); const [credentails, setCredentials] = React.useState(props.credentails); const [errorMsg, setErrorMsg] = React.useState(); - const settings = useSettings(); + const settings = useSettings(state=>state.settings); // Close the dropdown when this element unmounts React.useEffect(()=>{ diff --git a/src/components/Hooks/Settings.ts b/src/components/Hooks/Settings.ts index db6eec7..af8f0ff 100644 --- a/src/components/Hooks/Settings.ts +++ b/src/components/Hooks/Settings.ts @@ -1,41 +1,52 @@ -import React from 'react'; -import { Mutex } from 'async-mutex'; +import { create } from 'zustand'; import { log } from '../../classes/Constants'; -import { ISettings, loadSettings } from '../../Settings'; +import { ISettings, loadSettings, saveSettings } from '../../Settings'; -const fetchingSettings = new Mutex(); -let fetchedSettings: ISettings|undefined; - -/** - * This hooks fetches settings only once, even if it's used in multiple components - * @returns settings - */ -export const useSettings = () => +interface SettingsState { - const [settings, setSettings] = React.useState(); + settings?: ISettings; + isLoading: boolean; + isSaving: boolean; + errorMsg?: string; + loadSettings: ()=>void; + saveSettings: (settings: Partial)=>void; +} - React.useEffect(()=>{ - (async ()=>{ - if(fetchingSettings.isLocked()) // Some other component is already fetching the settings? - { - await fetchingSettings.waitForUnlock(); - setSettings(fetchedSettings); - } - else if(!fetchedSettings) // We need to fetch the settings - { - await fetchingSettings.runExclusive(async ()=>{ - try { - fetchedSettings = await loadSettings(); - setSettings(fetchedSettings); - } catch(error) { - log('error', 'Failed to load settings.', error); - } - }); +export const useSettings = create()((set, get, store) => ({ + isLoading: false, + isSaving: false, + loadSettings: async ()=>{ + if(!get().isLoading) + { + log('debug', 'Load settings'); + try { + set({isLoading: true, errorMsg: undefined}); + set({settings: await loadSettings()}); + } catch(error) { + console.error('Failed to load settings.', error); + set({errorMsg: `Failed to load settings. ${String(error)}`}); + } finally { + set({isLoading: false}); } - else // We already have loaded the settings - setSettings(fetchedSettings); - })(); - }, []); + } + }, + saveSettings: async (settings)=>{ + log('debug', 'Saving settings', settings); + try { + set({isSaving: true, errorMsg: undefined}); + await saveSettings(settings); + + if(get().settings) // We already have settings loaded? + set(state => ({settings: {...state.settings!, ...settings}})); // Merge new settings with the original + else + get().loadSettings(); // Load all settings + } catch(error) { + console.error('Failed to save settings.', error); + set({errorMsg: `Failed to save settings. ${String(error)}`}); + } finally { + set({isSaving: false}); + } + }, +})); - return settings; -}; +export default useSettings; diff --git a/src/components/Options/Changelog.tsx b/src/components/Options/Changelog.tsx index b9a930a..2604bac 100644 --- a/src/components/Options/Changelog.tsx +++ b/src/components/Options/Changelog.tsx @@ -7,11 +7,10 @@ import Link from '@mui/material/Link'; import AlertTitle from '@mui/material/AlertTitle'; import ScienceIcon from '@mui/icons-material/Science'; import changelog from '../../changelog/changelog'; +import { ExtensionName, isBeta } from '../../classes/Constants'; +import useSettings from '../Hooks/Settings'; import { PaperGrid } from './Options'; import SwitchOption from './SwitchOption'; -import { ExtensionName, isBeta } from '../../classes/Constants'; -import { useAppDispatch, useAppSelector } from '../redux/Store'; -import { saveSettings } from '../redux/Settings'; export interface IProps { @@ -21,8 +20,8 @@ export interface IProps const Changelog: React.FC = (props)=> { - const dispatch = useAppDispatch(); - const hideTryBetaMsg = useAppSelector(state=>state.settings.settings?.hideTryBetaMsg); + const hideTryBetaMsg = useSettings(state=>state.settings?.hideTryBetaMsg); + const saveSettings = useSettings(state=>state.saveSettings); // Order by version number const versions = React.useMemo(()=>{ @@ -37,7 +36,7 @@ const Changelog: React.FC = (props)=> }, []); const disabledBetaMsg = React.useCallback(()=>{ - dispatch(saveSettings({hideTryBetaMsg: true})); + saveSettings({hideTryBetaMsg: true}); }, []); return ( diff --git a/src/components/Options/Connection.tsx b/src/components/Options/Connection.tsx index 9909e0b..66280fb 100644 --- a/src/components/Options/Connection.tsx +++ b/src/components/Options/Connection.tsx @@ -6,16 +6,15 @@ import Alert from '@mui/material/Alert'; import Typography from '@mui/material/Typography'; import { useAssociation } from '../Hooks/Association'; import { PaperGrid } from './Options'; -import { useAppDispatch, useAppSelector } from '../redux/Store'; import { defaultSettings } from '../../Settings'; import Button from '@mui/material/Button'; -import { saveSettings } from '../redux/Settings'; import { log } from '../../classes/Constants'; import CircularProgress from '@mui/material/CircularProgress'; +import useSettings from '../Hooks/Settings'; const AssociationStatus: React.FC = ()=> { - const settings = useAppSelector(state=>state.settings.settings); + const settings = useSettings(state=>state.settings); const [status, associationId, associationError, associate] = useAssociation([settings?.keePassHost, settings?.keePassPort]); if(status === 'checking') @@ -57,8 +56,9 @@ const Connection: React.FC = ()=> { const [ inputHost, setInputHost ] = React.useState(); const [ inputPort, setInputPort ] = React.useState(); - const [settings, isSaving] = useAppSelector(state=>[state.settings.settings, state.settings.isSaving]); - const dispatch = useAppDispatch(); + const settings = useSettings(state=>state.settings); + const isSaving = useSettings(state=>state.isSaving); + const saveSettings = useSettings(state=>state.saveSettings); /** Host or port changed? */ const canApply = React.useMemo(()=>{ @@ -71,10 +71,10 @@ const Connection: React.FC = ()=> /** Save host and port */ const onApply = React.useCallback(()=>{ log('debug', `Apply KeePassHttp settings (${inputHost || defaultSettings.keePassHost}:${inputPort || defaultSettings.keePassPort})`); - dispatch(saveSettings({ + saveSettings({ keePassHost: inputHost || defaultSettings.keePassHost, keePassPort: inputPort || defaultSettings.keePassPort, - })); + }); }, [inputHost, inputPort]); return ( diff --git a/src/components/Options/Options.tsx b/src/components/Options/Options.tsx index 67a8e71..f8a1481 100644 --- a/src/components/Options/Options.tsx +++ b/src/components/Options/Options.tsx @@ -1,8 +1,5 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; -import { Provider } from 'react-redux'; -import { store, useAppDispatch, useAppSelector } from '../redux/Store'; -import { loadSettings } from '../redux/Settings'; import Box from '@mui/material/Box'; import PageHeader from './PageHeader'; import Container from '@mui/material/Container'; @@ -18,6 +15,7 @@ import FindInPageIcon from '@mui/icons-material/FindInPage'; import FiberNewIcon from '@mui/icons-material/FiberNew'; import Connection from './Connection'; import Wrapper from '../Wrapper'; +import useSettings from '../Hooks/Settings'; import Behaviour from './Behaviour'; import Appearance from './Appearance'; import Changelog from './Changelog'; @@ -29,7 +27,7 @@ export interface IProps export const PaperGrid: React.FC> = (props)=> { - const isLoading = useAppSelector(state=>state.settings.isLoading); + const isLoading = useSettings(state=>state.isLoading); if(isLoading) return () @@ -47,15 +45,15 @@ const Options: React.FC = (props)=> { const showUpdate = React.useMemo(()=>new URLSearchParams(location.search).get('update'), []); const [selectedTab, setSelectedTab] = React.useState<'connection'|'behaviour'|'appearance'|'changelog'>(showUpdate?'changelog':'connection'); - const errorMessage = useAppSelector(state=>state.settings.errorMessage); - const dispatch = useAppDispatch(); + const errorMessage = useSettings(state=>state.errorMsg); + const loadSettings = useSettings(state=>state.loadSettings); React.useEffect(()=>{ // Update the page title document.title = EXTENSIONNAME; // Load settings - dispatch(loadSettings()); + loadSettings(); }, []); return ( @@ -105,9 +103,7 @@ const Options: React.FC = (props)=> export const mount = (container: HTMLElement, props: IProps): ()=>void => { const reactContainer = createRoot(container); - reactContainer.render( - - ); + reactContainer.render(); return reactContainer.unmount; }; diff --git a/src/components/Options/SwitchOption.tsx b/src/components/Options/SwitchOption.tsx index 8aea18a..53926a9 100644 --- a/src/components/Options/SwitchOption.tsx +++ b/src/components/Options/SwitchOption.tsx @@ -1,10 +1,9 @@ import React from 'react'; import Grid from '@mui/material/Grid'; import Switch from '@mui/material/Switch'; -import { ISettings } from '../../Settings'; -import { useAppDispatch, useAppSelector } from '../redux/Store'; -import { saveSettings } from '../redux/Settings'; import { SxProps, Theme } from '@mui/material/styles'; +import { ISettings } from '../../Settings'; +import useSettings from '../Hooks/Settings'; export interface IProps { @@ -15,30 +14,30 @@ export interface IProps const SwitchOption: React.FC = (props)=> { - const isSaving = useAppSelector(state=>state.settings.isSaving); - const optionEnabled = useAppSelector(state=>{ + const isSaving = useSettings(state=>state.isSaving); + const optionEnabled = useSettings(state=>{ const [ option, subOption ] = props.option.split('.') as [keyof ISettings, keyof ISettings['theme']]; if(subOption) - return (state.settings.settings as any)?.[option]?.[subOption] as boolean; + return (state.settings as any)?.[option]?.[subOption] as boolean; else - return state.settings.settings?.[option] as boolean; + return state.settings?.[option] as boolean; }); - const dispatch = useAppDispatch(); + const saveSettings = useSettings(state=>state.saveSettings); const setOption = React.useCallback((checked: boolean)=>{ const [ option, subOption ] = props.option.split('.') as [keyof ISettings, keyof ISettings['theme']]; if(subOption) { - dispatch(saveSettings({ + saveSettings({ [option]: {[subOption]: checked}, - })); + }); } else { - dispatch(saveSettings({ + saveSettings({ [option]: checked, - })); + }); } }, [props.option]) diff --git a/src/components/redux/Settings.ts b/src/components/redux/Settings.ts deleted file mode 100644 index 0262303..0000000 --- a/src/components/redux/Settings.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import { ISettings, loadSettings as settingsLoadSettings, saveSettings as settingsSaveSettings } from '../../Settings'; - -export interface SettingsState -{ - isLoading: boolean; - isSaving: boolean; - errorMessage?: string; - settings?: ISettings; -} - -const initialState: SettingsState = { - isLoading: false, - isSaving: false, -}; - -export const loadSettings = createAsyncThunk( - 'settings/loadSettings', - settingsLoadSettings, -); - -export const saveSettings = createAsyncThunk( - 'settings/saveSettings', - settingsSaveSettings, -); - -export const settingsSlice = createSlice({ - name: 'settings', - initialState, - reducers: { - - }, - extraReducers: (builder) => { - // Load settings - builder.addCase(loadSettings.pending, (state, action)=>{ - state.isLoading = true; - state.errorMessage = undefined; - }) - .addCase(loadSettings.rejected, (state, action)=>{ - state.errorMessage = String(action.error); - }) - .addCase(loadSettings.fulfilled, (state, action)=>{ - state.isLoading = false; - state.settings = action.payload; - }); - - // Save settings - builder.addCase(saveSettings.pending, (state, action)=>{ - state.isSaving = true; - state.errorMessage = undefined; - }) - .addCase(saveSettings.rejected, (state, action)=>{ - state.errorMessage = String(action.error); - }) - .addCase(saveSettings.fulfilled, (state, action)=>{ - state.isSaving = false; - state.settings = {...state.settings, ...action.meta.arg as ISettings}; - }); - }, -}); - -export const settingsActions = settingsSlice.actions; - -export default settingsSlice.reducer; diff --git a/src/components/redux/Store.ts b/src/components/redux/Store.ts deleted file mode 100644 index 1ed9b63..0000000 --- a/src/components/redux/Store.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { configureStore } from '@reduxjs/toolkit'; -import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import settings from './Settings'; - -export const store = configureStore({ - reducer: { - settings, - }, -}); - -type RootState = ReturnType; -type AppDispatch = typeof store.dispatch; - -export const useAppDispatch: () => AppDispatch = useDispatch -export const useAppSelector: TypedUseSelectorHook = useSelector;