forked from Mirrors/gomuks
web/preferences: improve preference proxies
This commit is contained in:
parent
f4be132313
commit
41074937d3
3 changed files with 49 additions and 17 deletions
|
@ -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
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue