mirror of
				https://github.com/Vomitblood/stort.git
				synced 2025-11-04 04:37:21 +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
 | 
					 | 
				
			||||||
  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 every time
 | 
					  // fetch user settings from local on first load every time
 | 
				
			||||||
  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