settings json functions

This commit is contained in:
Vomitblood 2024-08-02 00:03:57 +08:00
parent 6e0c1d2b85
commit 50e8016b14
7 changed files with 88 additions and 166 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -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"

View file

@ -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,
}} }}
> >

View file

@ -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
View 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();
}
}

View file

@ -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. */}