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 (
+
+ );
+}
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",