Skip to content

Commit b53da15

Browse files
committed
feat: Improved data sharing between different windows
- Improved useStorage hook - Reload data on chrome store change - Use settings provider to share settings - Moved IntlProvider
1 parent 52f245e commit b53da15

File tree

6 files changed

+104
-66
lines changed

6 files changed

+104
-66
lines changed

src/hooks/useSettings.ts

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,18 @@
11
import deepmerge from "deepmerge";
2-
import { loadRedmineConfig } from "../api/axios.config";
3-
import useStorage, { getChromeStorage } from "./useStorage";
4-
5-
export type Settings = {
6-
language: string;
7-
redmineURL: string;
8-
redmineApiKey: string;
9-
options: {
10-
autoPauseOnSwitch: boolean;
11-
extendedSearch: boolean;
12-
roundTimeNearestQuarterHour: boolean;
13-
};
14-
};
15-
16-
const defaultSettings = {
17-
language: "browser",
18-
redmineURL: "",
19-
redmineApiKey: "",
20-
options: {
21-
autoPauseOnSwitch: true,
22-
extendedSearch: true,
23-
roundTimeNearestQuarterHour: false,
24-
},
25-
};
2+
import { useContext } from "react";
3+
import { Settings, SettingsContext, defaultSettings } from "../provider/SettingsProvider";
4+
import { getChromeStorage } from "./useStorage";
265

6+
/**
7+
* Use settings context
8+
*/
279
const useSettings = () => {
28-
const { data, setData } = useStorage<Settings>("settings", defaultSettings);
29-
30-
return {
31-
settings: deepmerge<Settings>(defaultSettings, data),
32-
setSettings: (data: Settings) => {
33-
setData(data);
34-
loadRedmineConfig();
35-
},
36-
};
10+
return useContext(SettingsContext);
3711
};
3812

13+
/**
14+
* Get the settings from storage
15+
*/
3916
export const getSettings = async () => {
4017
const data = await getChromeStorage<Settings>("settings", defaultSettings);
4118
return deepmerge<Settings>(defaultSettings, data);

src/hooks/useStorage.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,38 @@ const useStorage = <T>(name: string, defaultValue: T) => {
44
const [localData, setLocalData] = useState(defaultValue);
55

66
// load data from storage
7-
useEffect(() => {
8-
const loadData = async () => {
9-
try {
10-
const data = await getChromeStorage(name, defaultValue);
11-
setLocalData(data);
12-
// eslint-disable-next-line no-empty
13-
} catch (error) {}
14-
};
7+
const loadData = async () => {
8+
try {
9+
const data = await getChromeStorage(name, defaultValue);
10+
setLocalData(data);
11+
// eslint-disable-next-line no-empty
12+
} catch (error) {}
13+
};
14+
15+
// set data to storage
16+
const setData = async (data: T) => {
17+
setLocalData(data);
18+
await setChromeStorage(name, data);
19+
};
1520

16-
// init load
21+
/**
22+
* Init load
23+
*/
24+
useEffect(() => {
1725
loadData();
26+
}, []);
1827

19-
// load on custom event "local-storage"
20-
window.addEventListener("local-storage", loadData);
21-
return () => {
22-
window.removeEventListener("local-storage", loadData);
28+
/**
29+
* On chrome storage change => load data
30+
*/
31+
useEffect(() => {
32+
const onChange = () => {
33+
loadData();
2334
};
24-
// eslint-disable-next-line react-hooks/exhaustive-deps
35+
chrome.storage.local.onChanged.addListener(onChange);
36+
return () => chrome.storage.local.onChanged.removeListener(onChange);
2537
}, []);
2638

27-
// set data to storage
28-
const setData = (data: T) => {
29-
setLocalData(data);
30-
setChromeStorage(name, data);
31-
// fire custom event "local-storage"
32-
window.dispatchEvent(new Event("local-storage"));
33-
};
34-
3539
return { data: localData, setData };
3640
};
3741

@@ -47,8 +51,8 @@ export const getChromeStorage = <T>(name: string, defaultValue: T) => {
4751
});
4852
};
4953

50-
export const setChromeStorage = <T>(name: string, data: T) => {
51-
chrome.storage.local.set({ [name]: JSON.stringify(data) });
54+
export const setChromeStorage = async <T>(name: string, data: T) => {
55+
await chrome.storage.local.set({ [name]: JSON.stringify(data) });
5256
};
5357

5458
export default useStorage;

src/main.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import ReactDOM from "react-dom/client";
44
import { HashRouter } from "react-router-dom";
55
import "react-tooltip/dist/react-tooltip.css";
66
import App from "./App.tsx";
7-
import IntlProvider from "./IntlProvider.tsx";
87
import "./index.css";
8+
import IntlProvider from "./provider/IntlProvider.tsx";
9+
import SettingsProvider from "./provider/SettingsProvider.tsx";
910

1011
const queryClient = new QueryClient({
1112
defaultOptions: {
@@ -19,9 +20,11 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
1920
<React.StrictMode>
2021
<HashRouter>
2122
<QueryClientProvider client={queryClient}>
22-
<IntlProvider>
23-
<App />
24-
</IntlProvider>
23+
<SettingsProvider>
24+
<IntlProvider>
25+
<App />
26+
</IntlProvider>
27+
</SettingsProvider>
2528
</QueryClientProvider>
2629
</HashRouter>
2730
</React.StrictMode>

src/pages/SettingsPage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ import { Field, Form, Formik, FormikProps } from "formik";
55
import { useEffect, useRef, useState } from "react";
66
import { FormattedMessage, useIntl } from "react-intl";
77
import * as Yup from "yup";
8-
import { LANGUAGES } from "../IntlProvider";
98
import Button from "../components/general/Button";
109
import CheckBox from "../components/general/CheckBox";
1110
import Indicator from "../components/general/Indicator";
1211
import InputField from "../components/general/InputField";
1312
import SelectField from "../components/general/SelectField";
1413
import Toast from "../components/general/Toast";
1514
import useMyAccount from "../hooks/useMyAccount";
16-
import useSettings, { Settings } from "../hooks/useSettings";
15+
import useSettings from "../hooks/useSettings";
16+
import { LANGUAGES } from "../provider/IntlProvider";
17+
import { Settings } from "../provider/SettingsProvider";
1718

1819
const SettingsPage = () => {
1920
const queryClient = useQueryClient();
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { setDefaultOptions } from "date-fns";
22
import { de as dateFnsLocalDE, enUS as dateFnsLocalEN } from "date-fns/locale";
33
import React from "react";
44
import { IntlProvider as ReactIntlProvider } from "react-intl";
5-
import useSettings from "./hooks/useSettings";
6-
import messagesDE from "./lang/de.json";
7-
import messagesEN from "./lang/en.json";
5+
import useSettings from "../hooks/useSettings";
6+
import messagesDE from "../lang/de.json";
7+
import messagesEN from "../lang/en.json";
88

99
type Language = "de" | "en";
1010
// eslint-disable-next-line react-refresh/only-export-components

src/provider/SettingsProvider.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import deepmerge from "deepmerge";
2+
import { ReactNode, createContext } from "react";
3+
import { loadRedmineConfig } from "../api/axios.config";
4+
import useStorage from "../hooks/useStorage";
5+
6+
export type Settings = {
7+
language: string;
8+
redmineURL: string;
9+
redmineApiKey: string;
10+
options: {
11+
autoPauseOnSwitch: boolean;
12+
extendedSearch: boolean;
13+
roundTimeNearestQuarterHour: boolean;
14+
};
15+
};
16+
17+
export const defaultSettings = {
18+
language: "browser",
19+
redmineURL: "",
20+
redmineApiKey: "",
21+
options: {
22+
autoPauseOnSwitch: true,
23+
extendedSearch: true,
24+
roundTimeNearestQuarterHour: false,
25+
},
26+
};
27+
28+
const SettingsContext = createContext({
29+
settings: defaultSettings,
30+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
31+
setSettings: (data: Settings) => undefined,
32+
});
33+
34+
const SettingsProvider = ({ children }: { children: ReactNode }) => {
35+
const { data, setData } = useStorage<Settings>("settings", defaultSettings);
36+
37+
return (
38+
<SettingsContext.Provider
39+
value={{
40+
settings: deepmerge<Settings>(defaultSettings, data),
41+
setSettings: (data: Settings) => {
42+
setData(data);
43+
loadRedmineConfig();
44+
},
45+
}}
46+
>
47+
{children}
48+
</SettingsContext.Provider>
49+
);
50+
};
51+
52+
export { SettingsContext };
53+
export default SettingsProvider;

0 commit comments

Comments
 (0)