mirror of
https://github.com/Vomitblood/stort.git
synced 2024-11-26 13:55:27 +08:00
settings json functions
This commit is contained in:
parent
6e0c1d2b85
commit
50e8016b14
|
@ -19,6 +19,7 @@
|
||||||
"@tauri-apps/api": "^1.6.0",
|
"@tauri-apps/api": "^1.6.0",
|
||||||
"@types/lodash": "^4.17.7",
|
"@types/lodash": "^4.17.7",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"lowdb": "^7.0.1",
|
||||||
"next": "14.2.5",
|
"next": "14.2.5",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18"
|
"react-dom": "^18"
|
||||||
|
|
|
@ -1,24 +1,19 @@
|
||||||
import { merge } from "lodash";
|
|
||||||
import {
|
import {
|
||||||
|
createContext,
|
||||||
FC,
|
FC,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
createContext,
|
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { defaultSettings, SettingsType } from "../lib/defaultSettings";
|
import { defaultSettings, SettingsType } from "../lib/defaultSettings";
|
||||||
import { logcat } from "../lib/logcatService";
|
import { logcat } from "../lib/logcatService";
|
||||||
|
import { LowDB } from "../lib/lowDB";
|
||||||
|
|
||||||
// settings context
|
// settings context
|
||||||
type SettingsContextProps = {
|
type SettingsContextProps = {
|
||||||
settings: SettingsType;
|
settings: SettingsType;
|
||||||
fetchUserSettingsLocal: () => Promise<void>;
|
|
||||||
fetchUserSettingsCloud: () => Promise<void>;
|
|
||||||
updateSettingsLocal: (settings: SettingsType) => void;
|
|
||||||
updateSettingsCloud: (settings: SettingsType) => Promise<void>;
|
|
||||||
deleteSettingsLocal: () => void;
|
|
||||||
deleteSettingsCloud: () => Promise<void>;
|
|
||||||
settingsLoading: boolean;
|
settingsLoading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -33,163 +28,46 @@ export const SettingsProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||||
const [settings, setSettings] = useState<SettingsType>(defaultSettings);
|
const [settings, setSettings] = useState<SettingsType>(defaultSettings);
|
||||||
const [settingsLoading, setSettingsLoading] = useState<boolean>(true);
|
const [settingsLoading, setSettingsLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
// fetch user settings from localStorage
|
// initialize a new lowdb instance for settings outside of state
|
||||||
const fetchUserSettingsLocal = async () => {
|
const settingsDB = useMemo(() => {
|
||||||
|
return new LowDB<SettingsType>("settings.json", defaultSettings);
|
||||||
|
}, []); // empty dependency array ensures this is only created once
|
||||||
|
|
||||||
|
// function to update settings
|
||||||
|
const updateSettings = async (updates: Partial<SettingsType>) => {
|
||||||
try {
|
try {
|
||||||
logcat.log("Fetching user settings from localStorage...", "INFO");
|
await settingsDB.updateData((data) => {
|
||||||
const userSettings = localStorage.getItem("userSettings");
|
Object.assign(data, updates);
|
||||||
|
});
|
||||||
if (userSettings) {
|
const newSettings = await settingsDB.readData();
|
||||||
// deep merge user settings with default settings
|
setSettings(newSettings);
|
||||||
const mergedSettings = merge(
|
logcat.log("Settings updated successfully", "INFO");
|
||||||
{},
|
|
||||||
defaultSettings,
|
|
||||||
JSON.parse(userSettings),
|
|
||||||
);
|
|
||||||
|
|
||||||
setSettings(mergedSettings);
|
|
||||||
|
|
||||||
logcat.log("User settings fetched from localStorage", "INFO");
|
|
||||||
logcat.log("User settings: " + userSettings, "DEBUG");
|
|
||||||
} else {
|
|
||||||
logcat.log(
|
|
||||||
"User settings not found in localStorage, using default settings",
|
|
||||||
"WARN",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setSettingsLoading(false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logcat.log("Error fetching user settings from localStorage", "ERROR");
|
logcat.log(`Failed to update settings: ${error}`, "ERROR");
|
||||||
logcat.log(String(error), "ERROR");
|
|
||||||
setSettingsLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// fetch user settings from Firestore
|
// fetch user settings from local on first load every time
|
||||||
const fetchUserSettingsCloud = async () => {
|
|
||||||
try {
|
|
||||||
if (user) {
|
|
||||||
logcat.log("Fetching user settings from firestore...", "INFO");
|
|
||||||
const userSettings = await firestoreRetrieveDocumentField(
|
|
||||||
`users/${user.uid}`,
|
|
||||||
"settings",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (userSettings) {
|
|
||||||
// deep merge user settings with default settings
|
|
||||||
const mergedSettings = merge({}, defaultSettings, userSettings);
|
|
||||||
|
|
||||||
setSettings(mergedSettings);
|
|
||||||
|
|
||||||
logcat.log("User settings fetched from firestore", "INFO");
|
|
||||||
logcat.log("User settings: " + JSON.stringify(userSettings), "DEBUG");
|
|
||||||
|
|
||||||
// and then save to localStorage
|
|
||||||
updateSettingsLocal(mergedSettings);
|
|
||||||
} else {
|
|
||||||
logcat.log(
|
|
||||||
"User settings not found in firestore, using default settings",
|
|
||||||
"WARN",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setSettingsLoading(false);
|
|
||||||
} else {
|
|
||||||
logcat.log("User not logged in, using default settings", "WARN");
|
|
||||||
setSettingsLoading(false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logcat.log("No such field", "ERROR");
|
|
||||||
logcat.log(String(error), "ERROR");
|
|
||||||
setSettingsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// push new settings to localStorage
|
|
||||||
const updateSettingsLocal = (settings: SettingsType) => {
|
|
||||||
try {
|
|
||||||
// apply settings into state
|
|
||||||
logcat.log("Applying settings...", "INFO");
|
|
||||||
setSettings(settings);
|
|
||||||
logcat.log("Settings applied", "INFO");
|
|
||||||
|
|
||||||
// save settings to localStorage
|
|
||||||
logcat.log("Saving user settings to localStorage...", "INFO");
|
|
||||||
localStorage.setItem("userSettings", JSON.stringify(settings));
|
|
||||||
logcat.log("User settings saved to localStorage", "INFO");
|
|
||||||
} catch (error) {
|
|
||||||
logcat.log("Error saving user settings to localStorage", "ERROR");
|
|
||||||
logcat.log(String(error), "ERROR");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// push new settings to firestore
|
|
||||||
const updateSettingsCloud = async (settings: SettingsType) => {
|
|
||||||
try {
|
|
||||||
// apply settings into state
|
|
||||||
logcat.log("Applying settings...", "INFO");
|
|
||||||
setSettings(settings);
|
|
||||||
logcat.log("Settings applied", "INFO");
|
|
||||||
|
|
||||||
// save settings to firestore
|
|
||||||
logcat.log("Saving user settings to Firestore...", "INFO");
|
|
||||||
|
|
||||||
// push entire settings object to firestore
|
|
||||||
if (user) {
|
|
||||||
firestoreCommitUpdate(`users/${user.uid}`, "settings", settings);
|
|
||||||
await firestorePushUpdates();
|
|
||||||
|
|
||||||
logcat.log("User settings saved to firestore", "INFO");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logcat.log("Error saving user settings to firestore", "ERROR");
|
|
||||||
logcat.log(String(error), "ERROR");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// delete settings from localStorage
|
|
||||||
const deleteSettingsLocal = () => {
|
|
||||||
try {
|
|
||||||
logcat.log("Deleting user settings from localStorage...", "INFO");
|
|
||||||
localStorage.removeItem("userSettings");
|
|
||||||
logcat.log("User settings deleted from localStorage", "INFO");
|
|
||||||
} catch (error) {
|
|
||||||
logcat.log("Error deleting user settings from localStorage", "ERROR");
|
|
||||||
logcat.log(String(error), "ERROR");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// delete settings from cloud
|
|
||||||
const deleteSettingsCloud = async () => {
|
|
||||||
try {
|
|
||||||
logcat.log("Deleting user settings from Firestore...", "INFO");
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
firestoreCommitDelete(`users/${user.uid}`, "settings");
|
|
||||||
await firestorePushDeletes();
|
|
||||||
|
|
||||||
logcat.log("User settings deleted from Firestore", "INFO");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logcat.log(String(error), "ERROR");
|
|
||||||
throw new Error("Error deleting user settings from Firestore");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// fetch user settings from local on first load everytime
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUserSettingsLocal();
|
const fetchSettings = async () => {
|
||||||
}, []);
|
try {
|
||||||
|
await settingsDB.init();
|
||||||
|
const storedSettings = await settingsDB.readData();
|
||||||
|
setSettings(storedSettings);
|
||||||
|
} catch (error) {
|
||||||
|
logcat.log(`Failed to load settings: ${error}`, "ERROR");
|
||||||
|
} finally {
|
||||||
|
setSettingsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchSettings();
|
||||||
|
}, [settingsDB]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContext.Provider
|
<SettingsContext.Provider
|
||||||
value={{
|
value={{
|
||||||
settings,
|
settings,
|
||||||
fetchUserSettingsLocal,
|
|
||||||
fetchUserSettingsCloud,
|
|
||||||
updateSettingsLocal,
|
|
||||||
updateSettingsCloud,
|
|
||||||
deleteSettingsLocal,
|
|
||||||
deleteSettingsCloud,
|
|
||||||
settingsLoading,
|
settingsLoading,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -9,16 +9,6 @@ export const defaultSettings = {
|
||||||
font_family: "monospace" as string,
|
font_family: "monospace" as string,
|
||||||
font_scaling: 100,
|
font_scaling: 100,
|
||||||
},
|
},
|
||||||
timings: {
|
|
||||||
auto_refresh: false as boolean,
|
|
||||||
auto_refresh_interval: 30 as number,
|
|
||||||
eta_as_duration: true as boolean,
|
|
||||||
military_time_format: true as boolean,
|
|
||||||
timings_bus_expanded_items: 3 as number,
|
|
||||||
timings_train_expanded_items: 3 as number,
|
|
||||||
favourites_bus_expanded_items: 0 as number,
|
|
||||||
favourites_train_expanded_items: 0 as number,
|
|
||||||
},
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type SettingsType = typeof defaultSettings;
|
export type SettingsType = typeof defaultSettings;
|
||||||
|
|
53
src/lib/lowDB.ts
Normal file
53
src/lib/lowDB.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { merge } from "lodash";
|
||||||
|
import { Low } from "lowdb";
|
||||||
|
import { JSONFile } from "lowdb/node";
|
||||||
|
|
||||||
|
// define a generic interface for json structure
|
||||||
|
interface Database<T> {
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateCallback<T> = (data: T) => void;
|
||||||
|
|
||||||
|
export class LowDB<T> {
|
||||||
|
private db: Low<Database<T>>;
|
||||||
|
private defaultData: T;
|
||||||
|
|
||||||
|
constructor(filePath: string, defaultData: T) {
|
||||||
|
const adapter = new JSONFile<Database<T>>(filePath);
|
||||||
|
this.db = new Low(adapter, { data: defaultData });
|
||||||
|
this.defaultData = defaultData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the json file and merge with default data if needed
|
||||||
|
async init() {
|
||||||
|
await this.db.read();
|
||||||
|
if (!this.db.data || !this.db.data.data) {
|
||||||
|
// initialize with default data
|
||||||
|
this.db.data = { data: { ...this.defaultData } };
|
||||||
|
} else {
|
||||||
|
// merge existing data with default data to fill in missing properties
|
||||||
|
this.db.data.data = { ...this.defaultData, ...this.db.data.data };
|
||||||
|
}
|
||||||
|
await this.db.write();
|
||||||
|
}
|
||||||
|
|
||||||
|
async readData(): Promise<T> {
|
||||||
|
await this.db.read();
|
||||||
|
// ensure that the data is merged with default values every time it is read
|
||||||
|
this.db.data!.data = merge({}, this.defaultData, this.db.data!.data);
|
||||||
|
return this.db.data!.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeData(data: T): Promise<void> {
|
||||||
|
this.db.data!.data = data;
|
||||||
|
await this.db.write();
|
||||||
|
}
|
||||||
|
|
||||||
|
// update a specific part of the json file
|
||||||
|
async updateData(callback: UpdateCallback<T>): Promise<void> {
|
||||||
|
await this.db.read();
|
||||||
|
callback(this.db.data!.data);
|
||||||
|
await this.db.write();
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,8 @@ export default function MyApp(props: MyAppProps) {
|
||||||
return (
|
return (
|
||||||
<CacheProvider value={emotionCache}>
|
<CacheProvider value={emotionCache}>
|
||||||
<Head>
|
<Head>
|
||||||
<title>WhensApp</title>
|
<title>Stort</title>
|
||||||
<meta name='viewport' content='initial-scale=1, width=device-width' />
|
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
||||||
</Head>
|
</Head>
|
||||||
<UserThemeProvider>
|
<UserThemeProvider>
|
||||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||||
|
|
Loading…
Reference in a new issue