From 6e0c1d2b85a21d08a0e8118487e1a5bffc8cc370 Mon Sep 17 00:00:00 2001 From: Vomitblood Date: Thu, 1 Aug 2024 01:47:08 +0800 Subject: [PATCH] settings --- bun.lockb | Bin 167827 -> 168530 bytes package.json | 2 + src-tauri/Cargo.toml | 2 +- src-tauri/tauri.conf.json | 3 + src/components/Generic/FloatingDialog.tsx | 99 +++++++++++ src/components/HeaderBar/HeaderBar.tsx | 9 +- src/components/HeaderBar/Settings.tsx | 0 src/contexts/SettingsContext.tsx | 207 ++++++++++++++++++++++ src/lib/defaultSettings.ts | 24 +++ src/lib/logcatService.ts | 66 +++++++ src/pages/api/hello.ts | 13 -- src/pages/testing.tsx | 24 +++ 12 files changed, 432 insertions(+), 17 deletions(-) create mode 100644 src/components/Generic/FloatingDialog.tsx create mode 100644 src/components/HeaderBar/Settings.tsx create mode 100644 src/contexts/SettingsContext.tsx create mode 100644 src/lib/defaultSettings.ts create mode 100644 src/lib/logcatService.ts delete mode 100644 src/pages/api/hello.ts create mode 100644 src/pages/testing.tsx diff --git a/bun.lockb b/bun.lockb index 06bf92ce4af2783c56d037ddcc3ed81f0ce0f0fd..a89fb6bd356ffe4acccaa4f04544ee028ca0b0b5 100755 GIT binary patch delta 31883 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 W*<0YB6#^#rNpG!n<0HJ$5B`4~r6jlj delta 31353 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 QX2Vn7;;WOwy^$0C4^<0X-~a#s diff --git a/package.json b/package.json index 30be59f..2f64333 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "@mui/icons-material": "^5.16.5", "@mui/material": "^5.16.5", "@tauri-apps/api": "^1.6.0", + "@types/lodash": "^4.17.7", + "lodash": "^4.17.21", "next": "14.2.5", "react": "^18", "react-dom": "^18" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7e361e4..2645023 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -17,7 +17,7 @@ tauri-build = { version = "1.5.3", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.7.0", features = [ "window-all", "process-all", "notification-all", "dialog-all"] } +tauri = { version = "1.7.0", features = [ "path-all", "window-all", "process-all", "notification-all", "dialog-all"] } [features] # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4eb2ccf..f9dddaf 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -23,6 +23,9 @@ "notification": { "all": true }, + "path": { + "all": true + }, "process": { "all": true }, diff --git a/src/components/Generic/FloatingDialog.tsx b/src/components/Generic/FloatingDialog.tsx new file mode 100644 index 0000000..4144c16 --- /dev/null +++ b/src/components/Generic/FloatingDialog.tsx @@ -0,0 +1,99 @@ +import { Close, UnfoldLess, UnfoldMore } from "@mui/icons-material"; +import { Box, IconButton, Modal, Tooltip, Typography } from "@mui/material"; +import { FC, ReactNode } from "react"; + +interface FloatingDialog { + sx?: any; + openState: boolean; + maximisedState: boolean; + setMaximisedState: (state: boolean) => void; + toggleOpen: () => void; + close: () => void; + actionButtons?: ReactNode; + body: ReactNode; + bottomBar?: ReactNode; + openButton: ReactNode; + title: string; +} + +export const FloatingDialog: FC = ({ + sx, + openState, + maximisedState, + setMaximisedState, + toggleOpen, + close, + actionButtons, + body, + bottomBar, + openButton, + title, +}) => { + const { settings } = useSettings(); + + return ( + <> + {openButton} + + + + + {title} + + + {actionButtons} + + + { + setMaximisedState(!maximisedState); + }} + sx={{ + mr: 1, + }} + > + {maximisedState ? : } + + + + + + + + + {body} + {bottomBar} + + + + ); +}; diff --git a/src/components/HeaderBar/HeaderBar.tsx b/src/components/HeaderBar/HeaderBar.tsx index e80282f..88f0f86 100644 --- a/src/components/HeaderBar/HeaderBar.tsx +++ b/src/components/HeaderBar/HeaderBar.tsx @@ -1,5 +1,6 @@ -import { Box, Typography } from "@mui/material"; +import { Box, Button, IconButton, Typography } from "@mui/material"; import { WindowButtons } from "./WindowButtons"; +import { BugReport } from "@mui/icons-material"; export const HeaderBar = () => { return ( @@ -12,7 +13,7 @@ export const HeaderBar = () => { borderBottom: "1px solid red", display: "flex", flexDirection: "row", - height: "64px", + height: "48px", justifyContent: "space-between", p: 1, }} @@ -37,7 +38,9 @@ export const HeaderBar = () => { flexDirection: "row", }} > - hello this is the right side | + + + diff --git a/src/components/HeaderBar/Settings.tsx b/src/components/HeaderBar/Settings.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/contexts/SettingsContext.tsx b/src/contexts/SettingsContext.tsx new file mode 100644 index 0000000..abfa944 --- /dev/null +++ b/src/contexts/SettingsContext.tsx @@ -0,0 +1,207 @@ +import { merge } from "lodash"; +import { + FC, + ReactNode, + createContext, + useContext, + useEffect, + useState, +} from "react"; +import { defaultSettings, SettingsType } from "../lib/defaultSettings"; +import { logcat } from "../lib/logcatService"; + +// settings context +type SettingsContextProps = { + settings: SettingsType; + fetchUserSettingsLocal: () => Promise; + fetchUserSettingsCloud: () => Promise; + updateSettingsLocal: (settings: SettingsType) => void; + updateSettingsCloud: (settings: SettingsType) => Promise; + deleteSettingsLocal: () => void; + deleteSettingsCloud: () => Promise; + settingsLoading: boolean; +}; + +const SettingsContext = createContext( + undefined, +); + +export const SettingsProvider: FC<{ children: ReactNode }> = ({ children }) => { + logcat.log("Initializing settings...", "INFO"); + + // useStates + const [settings, setSettings] = useState(defaultSettings); + const [settingsLoading, setSettingsLoading] = useState(true); + + // fetch user settings from localStorage + const fetchUserSettingsLocal = async () => { + try { + logcat.log("Fetching user settings from localStorage...", "INFO"); + const userSettings = localStorage.getItem("userSettings"); + + if (userSettings) { + // deep merge user settings with default settings + const mergedSettings = merge( + {}, + 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) { + logcat.log("Error fetching user settings from localStorage", "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 everytime + useEffect(() => { + fetchUserSettingsLocal(); + }, []); + + return ( + + {children} + + ); +}; + +export const useSettings = () => { + const context = useContext(SettingsContext); + if (context === undefined) { + throw new Error("Please use useSettings only within a SettingsProvider"); + } + return context; +}; diff --git a/src/lib/defaultSettings.ts b/src/lib/defaultSettings.ts new file mode 100644 index 0000000..a8094e6 --- /dev/null +++ b/src/lib/defaultSettings.ts @@ -0,0 +1,24 @@ +export const defaultSettings = { + display: { + dark_mode: true as boolean, + accent_color: "#8ab4f8" as string, + transition_duration: 200 as number, + radius: 8 as number, + window_height: 60 as number, + window_width: 400 as number, + font_family: "monospace" as string, + 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; + +export type SettingsType = typeof defaultSettings; diff --git a/src/lib/logcatService.ts b/src/lib/logcatService.ts new file mode 100644 index 0000000..f836c4f --- /dev/null +++ b/src/lib/logcatService.ts @@ -0,0 +1,66 @@ +type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR" | "CRITICAL"; + +export interface LogEntry { + level: LogLevel; + message: string; + timestamp: number; +} + +export class LogcatServiceClass { + private logs: LogEntry[] = []; + private lastLogTimestamp: number | null = null; + private listeners: (() => void)[] = []; + + constructor() { + this.log("LogcatService initialised", "INFO"); + } + + addListener(callback: () => void) { + this.listeners.push(callback); + } + + removeListener(callback: () => void) { + this.listeners = this.listeners.filter((listener) => listener !== callback); + } + + private notifyListeners() { + this.listeners.forEach((listener) => listener()); + } + + log(message: string, level: LogLevel = "INFO"): void { + const currentTimestamp = new Date().getTime(); + const timeSinceLastLog = this.lastLogTimestamp + ? currentTimestamp - this.lastLogTimestamp + : 0; + + const logEntry: LogEntry = { message, level, timestamp: timeSinceLastLog }; + this.logs.push(logEntry); + + switch (level) { + case "DEBUG": + console.debug(`> [${level}] ${message} (+${timeSinceLastLog}ms)`); + break; + case "INFO": + console.info(`> [${level}] ${message} (+${timeSinceLastLog}ms)`); + break; + case "WARN": + console.warn(`> [${level}] ${message} (+${timeSinceLastLog}ms)`); + break; + case "ERROR": + console.error(`> [${level}] ${message} (+${timeSinceLastLog}ms)`); + break; + case "CRITICAL": + console.error(`> [${level}] ${message} (+${timeSinceLastLog}ms)`); + break; + } + + this.lastLogTimestamp = currentTimestamp; + this.notifyListeners(); + } + + getLogs(): LogEntry[] { + return this.logs; + } +} + +export const logcat = new LogcatServiceClass(); diff --git a/src/pages/api/hello.ts b/src/pages/api/hello.ts deleted file mode 100644 index ea77e8f..0000000 --- a/src/pages/api/hello.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from "next"; - -type Data = { - name: string; -}; - -export default function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - res.status(200).json({ name: "John Doe" }); -} diff --git a/src/pages/testing.tsx b/src/pages/testing.tsx new file mode 100644 index 0000000..5dfe6c7 --- /dev/null +++ b/src/pages/testing.tsx @@ -0,0 +1,24 @@ +import { BugReport } from "@mui/icons-material"; +import { Box, Button, IconButton } from "@mui/material"; +import { configDir } from "@tauri-apps/api/path"; + +export default function Testing() { + const testing = async () => { + console.log(await configDir()); + }; + + return ( + + + + + + + ); +}