From e62238459c056e5d74d212aff2b51b9a79e53823 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Fri, 24 Nov 2023 18:39:40 +0100 Subject: [PATCH] Add more settings saving logic + add connections section to settings + fix broken modals --- .eslintrc.js | 2 +- package.json | 2 + pnpm-lock.yaml | 16 ++ src/components/overlays/OverlayDisplay.tsx | 2 +- src/hooks/useChromecastAvailable.ts | 4 +- src/hooks/useSettingsState.ts | 7 +- src/pages/Settings.tsx | 61 +++++-- src/pages/parts/settings/AccountEditPart.tsx | 5 +- src/pages/parts/settings/ConnectionsPart.tsx | 149 ++++++++++++++++++ src/pages/parts/settings/ProfileEditModal.tsx | 38 ++++- src/pages/parts/settings/SidebarPart.tsx | 3 +- themes/default.ts | 1 + 12 files changed, 264 insertions(+), 26 deletions(-) create mode 100644 src/pages/parts/settings/ConnectionsPart.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 7f458a53..8b0c8e15 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -44,7 +44,7 @@ module.exports = { "react/destructuring-assignment": "off", "no-underscore-dangle": "off", "@typescript-eslint/no-explicit-any": "off", - "no-console": ["error", { allow: ["warn", "error"] }], + "no-console": ["warn", { allow: ["warn", "error", "debug", "info"] }], "@typescript-eslint/no-this-alias": "off", "import/prefer-default-export": "off", "@typescript-eslint/no-empty-function": "off", diff --git a/package.json b/package.json index 410bc8f4..b87be4d4 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "hls.js": "^1.0.7", "i18next": "^22.4.5", "immer": "^10.0.2", + "lodash.isequal": "^4.5.0", "node-forge": "^1.3.1", "ofetch": "^1.0.0", "react": "^17.0.2", @@ -67,6 +68,7 @@ "@types/crypto-js": "^4.1.1", "@types/dompurify": "^2.4.0", "@types/fscreen": "^1.0.1", + "@types/lodash.isequal": "^4.5.8", "@types/lodash.throttle": "^4.1.7", "@types/node": "^17.0.15", "@types/pako": "^2.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5e5fb1e..b1bb42bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ dependencies: immer: specifier: ^10.0.2 version: 10.0.2 + lodash.isequal: + specifier: ^4.5.0 + version: 4.5.0 node-forge: specifier: ^1.3.1 version: 1.3.1 @@ -130,6 +133,9 @@ devDependencies: '@types/fscreen': specifier: ^1.0.1 version: 1.0.1 + '@types/lodash.isequal': + specifier: ^4.5.8 + version: 4.5.8 '@types/lodash.throttle': specifier: ^4.1.7 version: 4.1.7 @@ -2132,6 +2138,12 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/lodash.isequal@4.5.8: + resolution: {integrity: sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==} + dependencies: + '@types/lodash': 4.14.197 + dev: true + /@types/lodash.throttle@4.1.7: resolution: {integrity: sha512-znwGDpjCHQ4FpLLx19w4OXDqq8+OvREa05H89obtSyXyOFKL3dDjCslsmfBz0T2FU8dmf5Wx1QvogbINiGIu9g==} dependencies: @@ -4561,6 +4573,10 @@ packages: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: true + /lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + dev: false + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true diff --git a/src/components/overlays/OverlayDisplay.tsx b/src/components/overlays/OverlayDisplay.tsx index b737b1aa..359ab106 100644 --- a/src/components/overlays/OverlayDisplay.tsx +++ b/src/components/overlays/OverlayDisplay.tsx @@ -55,7 +55,7 @@ export function OverlayPortal(props: { onDeactivate: close, }} > -
+
( useEffect(() => { setOverwrite(undefined); }, [initial]); - - const changed = overwrite !== initial && overwrite !== undefined; + const changed = useMemo( + () => !isEqual(overwrite, initial) && overwrite !== undefined, + [overwrite, initial] + ); const data = overwrite === undefined ? initial : overwrite; const reset = useCallback(() => setOverwrite(undefined), [setOverwrite]); diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index e046a168..8341715e 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,8 +1,10 @@ import classNames from "classnames"; -import { useEffect } from "react"; +import { useCallback, useEffect, useMemo } from "react"; import { useAsyncFn } from "react-use"; +import { base64ToBuffer, decryptData } from "@/backend/accounts/crypto"; import { getSessions } from "@/backend/accounts/sessions"; +import { updateSettings } from "@/backend/accounts/settings"; import { Button } from "@/components/Button"; import { WideContainer } from "@/components/layout/WideContainer"; import { Heading1 } from "@/components/utils/Text"; @@ -12,6 +14,7 @@ import { useSettingsState } from "@/hooks/useSettingsState"; import { AccountActionsPart } from "@/pages/parts/settings/AccountActionsPart"; import { AccountEditPart } from "@/pages/parts/settings/AccountEditPart"; import { CaptionsPart } from "@/pages/parts/settings/CaptionsPart"; +import { ConnectionsPart } from "@/pages/parts/settings/ConnectionsPart"; import { DeviceListPart } from "@/pages/parts/settings/DeviceListPart"; import { RegisterCalloutPart } from "@/pages/parts/settings/RegisterCalloutPart"; import { SidebarPart } from "@/pages/parts/settings/SidebarPart"; @@ -71,10 +74,18 @@ export function SettingsPage() { const setTheme = useThemeStore((s) => s.setTheme); const appLanguage = useLanguageStore((s) => s.language); + const setAppLanguage = useLanguageStore((s) => s.setLanguage); const subStyling = useSubtitleStore((s) => s.styling); + const setSubStyling = useSubtitleStore((s) => s.updateStyling); - const deviceName = useAuthStore((s) => s.account?.deviceName); + const account = useAuthStore((s) => s.account); + const decryptedName = useMemo(() => { + if (!account) return ""; + return decryptData(account.deviceName, base64ToBuffer(account.seed)); + }, [account]); + + const backendUrl = useBackendUrl(); const user = useAuthStore(); @@ -82,9 +93,23 @@ export function SettingsPage() { activeTheme, appLanguage, subStyling, - deviceName + decryptedName ); + const saveChanges = useCallback(async () => { + console.log(state); + + if (account) { + await updateSettings(backendUrl, account, { + applicationLanguage: state.appLanguage.state, + applicationTheme: state.theme.state ?? undefined, + }); + } + + setAppLanguage(state.appLanguage.state); + setTheme(state.theme.state); + setSubStyling(state.subtitleStyling.state); + }, [state, account, backendUrl, setAppLanguage, setTheme, setSubStyling]); return ( @@ -110,20 +135,28 @@ export function SettingsPage() {
s} + setStyling={state.subtitleStyling.set} />
-
- - {state.changed ? ( -
-

You have unsaved changes

-
- - -
+
+
- ) : null} + +
+

You have unsaved changes

+
+ + +
+
); } diff --git a/src/pages/parts/settings/AccountEditPart.tsx b/src/pages/parts/settings/AccountEditPart.tsx index 754821de..03c3e1da 100644 --- a/src/pages/parts/settings/AccountEditPart.tsx +++ b/src/pages/parts/settings/AccountEditPart.tsx @@ -13,7 +13,10 @@ export function AccountEditPart() { return ( - +
(null); + + const add = useCallback(() => { + idNum += 1; + setCustomWorkers((s) => [ + ...(s ?? []), + { + id: idNum, + url: "", + }, + ]); + }, [setCustomWorkers]); + + const changeItem = useCallback( + (id: number, val: string) => { + setCustomWorkers((s) => [ + ...(s ?? []).map((v) => { + if (v.id !== id) return v; + v.url = val; + return v; + }), + ]); + }, + [setCustomWorkers] + ); + + const removeItem = useCallback( + (id: number) => { + setCustomWorkers((s) => [...(s ?? []).filter((v) => v.id !== id)]); + }, + [setCustomWorkers] + ); + + return ( + +
+
+

Use custom proxy workers

+

+ To make the application function, all traffic is routed through + proxies. Enable this if you want to bring your own workers. +

+
+
+ setCustomWorkers((s) => (s === null ? [] : null))} + enabled={customWorkers !== null} + /> +
+
+ {customWorkers !== null ? ( + <> + +

Worker URLs

+ +
+ {(customWorkers?.length ?? 0) === 0 ? ( +

No workers yet, add one below

+ ) : null} + {(customWorkers ?? []).map((v) => ( +
+ changeItem(v.id, val)} + placeholder="https://" + /> + +
+ ))} +
+ + + + ) : null} +
+ ); +} + +function BackendEdit() { + const [customBackendUrl, setCustomBackendUrl] = useState(null); + + return ( + +
+
+

Custom server

+

+ To make the application function, all traffic is routed through + proxies. Enable this if you want to bring your own workers. +

+
+
+ setCustomBackendUrl((s) => (s === null ? "" : null))} + enabled={customBackendUrl !== null} + /> +
+
+ {customBackendUrl !== null ? ( + <> + +

Custom server URL

+ + + ) : null} +
+ ); +} + +export function ConnectionsPart() { + return ( +
+ Connections +
+ + +
+
+ ); +} diff --git a/src/pages/parts/settings/ProfileEditModal.tsx b/src/pages/parts/settings/ProfileEditModal.tsx index b292a259..0170ead7 100644 --- a/src/pages/parts/settings/ProfileEditModal.tsx +++ b/src/pages/parts/settings/ProfileEditModal.tsx @@ -1,14 +1,44 @@ +import { useState } from "react"; + import { Button } from "@/components/Button"; +import { ColorPicker } from "@/components/form/ColorPicker"; +import { IconPicker } from "@/components/form/IconPicker"; import { Modal, ModalCard } from "@/components/overlays/Modal"; +import { UserIcons } from "@/components/UserIcon"; import { Heading2 } from "@/components/utils/Text"; -export function ProfileEditModal(props: { id: string }) { +export interface ProfileEditModalProps { + id: string; + close?: () => void; +} + +export function ProfileEditModal(props: ProfileEditModalProps) { + const [colorA, setColorA] = useState("#2E65CF"); + const [colorB, setColorB] = useState("#2E65CF"); + const [userIcon, setUserIcon] = useState(UserIcons.USER); + return ( - Edit profile? -

I am existing

- + Edit profile picture +
+ + + +
+
+ +
); diff --git a/src/pages/parts/settings/SidebarPart.tsx b/src/pages/parts/settings/SidebarPart.tsx index d955cc78..fc96bde2 100644 --- a/src/pages/parts/settings/SidebarPart.tsx +++ b/src/pages/parts/settings/SidebarPart.tsx @@ -18,9 +18,10 @@ export function SidebarPart() { const settingLinks = [ { text: "Account", id: "settings-account", icon: Icons.USER }, - { text: "Locale", id: "settings-locale", icon: Icons.LINK }, + { text: "Locale", id: "settings-locale", icon: Icons.BOOKMARK }, { text: "Appearance", id: "settings-appearance", icon: Icons.GITHUB }, { text: "Captions", id: "settings-captions", icon: Icons.CAPTIONS }, + { text: "Connections", id: "settings-connection", icon: Icons.LINK }, ]; useEffect(() => { diff --git a/themes/default.ts b/themes/default.ts index b682c971..dd3d27b4 100644 --- a/themes/default.ts +++ b/themes/default.ts @@ -109,6 +109,7 @@ export const defaultTheme = { authentication: { border: "#393954", inputBg: "#171728", + inputBgHover: "#171726", wordBackground: "#171728", copyText: "#58587A", copyTextHover: "#8888AA",