From 41074937d336202d2d8b77830c96417292fd199a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 17 Nov 2024 02:37:45 +0200 Subject: [PATCH] web/preferences: improve preference proxies --- web/src/api/types/preferences/localstorage.ts | 33 ++++++++++++------- web/src/api/types/preferences/preferences.ts | 13 +++++++- web/src/api/types/preferences/proxy.ts | 20 ++++++++--- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/web/src/api/types/preferences/localstorage.ts b/web/src/api/types/preferences/localstorage.ts index c74c984..c4992a0 100644 --- a/web/src/api/types/preferences/localstorage.ts +++ b/web/src/api/types/preferences/localstorage.ts @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { Preferences, existingPreferenceKeys, preferences } from "./preferences.ts" +import { Preferences, isValidPreferenceKey, preferences } from "./preferences.ts" import { PreferenceContext } from "./types.ts" function getObjectFromLocalStorage(key: string): Preferences { @@ -28,11 +28,18 @@ function getObjectFromLocalStorage(key: string): Preferences { return {} } +const globalPrefKeys = Object.entries(preferences) + .filter(([,pref]) => pref.allowedContexts.includes(PreferenceContext.Device)) + .map(([key]) => key) +const roomPrefKeys = Object.entries(preferences) + .filter(([,pref]) => pref.allowedContexts.includes(PreferenceContext.RoomDevice)) + .map(([key]) => key) + export function getLocalStoragePreferences(localStorageKey: string, onChange: () => void): Preferences { return new Proxy(getObjectFromLocalStorage(localStorageKey), { // eslint-disable-next-line @typescript-eslint/no-explicit-any - set(target: Preferences, key: keyof Preferences, newValue: any): boolean { - if (!existingPreferenceKeys.has(key)) { + set(target: Preferences, key: string | symbol, newValue: any): boolean { + if (!isValidPreferenceKey(key)) { return false } target[key] = newValue @@ -40,8 +47,8 @@ export function getLocalStoragePreferences(localStorageKey: string, onChange: () onChange() return true }, - deleteProperty(target: Preferences, key: keyof Preferences): boolean { - if (!existingPreferenceKeys.has(key)) { + deleteProperty(target: Preferences, key: string | symbol): boolean { + if (!isValidPreferenceKey(key)) { return false } delete target[key] @@ -52,13 +59,15 @@ export function getLocalStoragePreferences(localStorageKey: string, onChange: () return true }, ownKeys(): string[] { - console.warn("localStorage preference proxy ownKeys called") - // This is only for debugging, so the performance doesn't matter that much - return Object.entries(preferences) - .filter(([,pref]) => - pref.allowedContexts.includes(localStorageKey === "global_prefs" - ? PreferenceContext.Device : PreferenceContext.RoomDevice)) - .map(([key]) => key) + return localStorageKey === "global_prefs" ? globalPrefKeys : roomPrefKeys + }, + getOwnPropertyDescriptor(_target: never, key: string | symbol): PropertyDescriptor | undefined { + const keySet = localStorageKey === "global_prefs" ? globalPrefKeys : roomPrefKeys + return (typeof key === "string" && keySet.includes(key)) ? { + configurable: true, + enumerable: true, + writable: true, + } : undefined }, }) } diff --git a/web/src/api/types/preferences/preferences.ts b/web/src/api/types/preferences/preferences.ts index 6831a58..1404667 100644 --- a/web/src/api/types/preferences/preferences.ts +++ b/web/src/api/types/preferences/preferences.ts @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +import type { ContentURI } from "../../types" import { Preference, anyContext } from "./types.ts" export const codeBlockStyles = [ @@ -56,7 +57,7 @@ export const preferences = { allowedValues: codeBlockStyles, }), pointer_cursor: new Preference({ - displayName: "Pointer cursor", + displayName: "Use pointer cursor", description: "Whether to use a pointer cursor for clickable elements.", allowedContexts: anyContext, defaultValue: false, @@ -97,6 +98,12 @@ export const preferences = { allowedContexts: anyContext, defaultValue: true, }), + custom_notification_sound: new Preference({ + displayName: "Custom notification sound", + description: "The mxc:// URI to a custom notification sound.", + allowedContexts: anyContext, + defaultValue: "", + }), } as const export const existingPreferenceKeys = new Set(Object.keys(preferences)) @@ -104,3 +111,7 @@ export const existingPreferenceKeys = new Set(Object.keys(preferences)) export type Preferences = { -readonly [name in keyof typeof preferences]?: typeof preferences[name]["defaultValue"] } + +export function isValidPreferenceKey(key: unknown): key is keyof Preferences { + return typeof key === "string" && existingPreferenceKeys.has(key) +} diff --git a/web/src/api/types/preferences/proxy.ts b/web/src/api/types/preferences/proxy.ts index 97818bd..d9cd11a 100644 --- a/web/src/api/types/preferences/proxy.ts +++ b/web/src/api/types/preferences/proxy.ts @@ -14,18 +14,23 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import type { RoomStateStore, StateStore } from "@/api/statestore" -import { Preferences, existingPreferenceKeys, preferences } from "./preferences.ts" +import { Preferences, isValidPreferenceKey, preferences } from "./preferences.ts" import { PreferenceContext, PreferenceValueType } from "./types.ts" +const prefKeys = Object.keys(preferences) + export function getPreferenceProxy(store: StateStore, room?: RoomStateStore): Preferences { return new Proxy({}, { set(): boolean { throw new Error("The preference proxy is read-only") }, - get(_target: never, key: keyof Preferences): PreferenceValueType | undefined { + get(_target: never, key: keyof Preferences | symbol): PreferenceValueType | undefined { + if (typeof key !== "string") { + return + } const pref = preferences[key] if (!pref) { - return undefined + return } let val: typeof pref.defaultValue | undefined for (const ctx of pref.allowedContexts) { @@ -47,7 +52,14 @@ export function getPreferenceProxy(store: StateStore, room?: RoomStateStore): Pr return pref.defaultValue }, ownKeys(): string[] { - return Array.from(existingPreferenceKeys) + return prefKeys + }, + getOwnPropertyDescriptor(_target: never, key: string | symbol): PropertyDescriptor | undefined { + return isValidPreferenceKey(key) ? { + configurable: true, + enumerable: true, + writable: false, + } : undefined }, }) }