1
0
Fork 0
forked from Mirrors/gomuks

web/preferences: improve preference proxies

This commit is contained in:
Tulir Asokan 2024-11-17 02:37:45 +02:00
parent f4be132313
commit 41074937d3
3 changed files with 49 additions and 17 deletions

View file

@ -13,7 +13,7 @@
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import { Preferences, existingPreferenceKeys, preferences } from "./preferences.ts" import { Preferences, isValidPreferenceKey, preferences } from "./preferences.ts"
import { PreferenceContext } from "./types.ts" import { PreferenceContext } from "./types.ts"
function getObjectFromLocalStorage(key: string): Preferences { function getObjectFromLocalStorage(key: string): Preferences {
@ -28,11 +28,18 @@ function getObjectFromLocalStorage(key: string): Preferences {
return {} 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 { export function getLocalStoragePreferences(localStorageKey: string, onChange: () => void): Preferences {
return new Proxy(getObjectFromLocalStorage(localStorageKey), { return new Proxy(getObjectFromLocalStorage(localStorageKey), {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
set(target: Preferences, key: keyof Preferences, newValue: any): boolean { set(target: Preferences, key: string | symbol, newValue: any): boolean {
if (!existingPreferenceKeys.has(key)) { if (!isValidPreferenceKey(key)) {
return false return false
} }
target[key] = newValue target[key] = newValue
@ -40,8 +47,8 @@ export function getLocalStoragePreferences(localStorageKey: string, onChange: ()
onChange() onChange()
return true return true
}, },
deleteProperty(target: Preferences, key: keyof Preferences): boolean { deleteProperty(target: Preferences, key: string | symbol): boolean {
if (!existingPreferenceKeys.has(key)) { if (!isValidPreferenceKey(key)) {
return false return false
} }
delete target[key] delete target[key]
@ -52,13 +59,15 @@ export function getLocalStoragePreferences(localStorageKey: string, onChange: ()
return true return true
}, },
ownKeys(): string[] { ownKeys(): string[] {
console.warn("localStorage preference proxy ownKeys called") return localStorageKey === "global_prefs" ? globalPrefKeys : roomPrefKeys
// This is only for debugging, so the performance doesn't matter that much },
return Object.entries(preferences) getOwnPropertyDescriptor(_target: never, key: string | symbol): PropertyDescriptor | undefined {
.filter(([,pref]) => const keySet = localStorageKey === "global_prefs" ? globalPrefKeys : roomPrefKeys
pref.allowedContexts.includes(localStorageKey === "global_prefs" return (typeof key === "string" && keySet.includes(key)) ? {
? PreferenceContext.Device : PreferenceContext.RoomDevice)) configurable: true,
.map(([key]) => key) enumerable: true,
writable: true,
} : undefined
}, },
}) })
} }

View file

@ -13,6 +13,7 @@
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import type { ContentURI } from "../../types"
import { Preference, anyContext } from "./types.ts" import { Preference, anyContext } from "./types.ts"
export const codeBlockStyles = [ export const codeBlockStyles = [
@ -56,7 +57,7 @@ export const preferences = {
allowedValues: codeBlockStyles, allowedValues: codeBlockStyles,
}), }),
pointer_cursor: new Preference<boolean>({ pointer_cursor: new Preference<boolean>({
displayName: "Pointer cursor", displayName: "Use pointer cursor",
description: "Whether to use a pointer cursor for clickable elements.", description: "Whether to use a pointer cursor for clickable elements.",
allowedContexts: anyContext, allowedContexts: anyContext,
defaultValue: false, defaultValue: false,
@ -97,6 +98,12 @@ export const preferences = {
allowedContexts: anyContext, allowedContexts: anyContext,
defaultValue: true, defaultValue: true,
}), }),
custom_notification_sound: new Preference<ContentURI>({
displayName: "Custom notification sound",
description: "The mxc:// URI to a custom notification sound.",
allowedContexts: anyContext,
defaultValue: "",
}),
} as const } as const
export const existingPreferenceKeys = new Set(Object.keys(preferences)) export const existingPreferenceKeys = new Set(Object.keys(preferences))
@ -104,3 +111,7 @@ export const existingPreferenceKeys = new Set(Object.keys(preferences))
export type Preferences = { export type Preferences = {
-readonly [name in keyof typeof preferences]?: typeof preferences[name]["defaultValue"] -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)
}

View file

@ -14,18 +14,23 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import type { RoomStateStore, StateStore } from "@/api/statestore" 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" import { PreferenceContext, PreferenceValueType } from "./types.ts"
const prefKeys = Object.keys(preferences)
export function getPreferenceProxy(store: StateStore, room?: RoomStateStore): Preferences { export function getPreferenceProxy(store: StateStore, room?: RoomStateStore): Preferences {
return new Proxy({}, { return new Proxy({}, {
set(): boolean { set(): boolean {
throw new Error("The preference proxy is read-only") 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] const pref = preferences[key]
if (!pref) { if (!pref) {
return undefined return
} }
let val: typeof pref.defaultValue | undefined let val: typeof pref.defaultValue | undefined
for (const ctx of pref.allowedContexts) { for (const ctx of pref.allowedContexts) {
@ -47,7 +52,14 @@ export function getPreferenceProxy(store: StateStore, room?: RoomStateStore): Pr
return pref.defaultValue return pref.defaultValue
}, },
ownKeys(): string[] { 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
}, },
}) })
} }