// gomuks - A Matrix client written in Go. // Copyright (C) 2024 Tulir Asokan // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { Suspense, lazy, use, useCallback, useRef, useState } from "react" import { ScaleLoader } from "react-spinners" import Client from "@/api/client.ts" import { getRoomAvatarThumbnailURL, getRoomAvatarURL } from "@/api/media.ts" import { RoomStateStore, usePreferences } from "@/api/statestore" import { Preference, PreferenceContext, PreferenceValueType, Preferences, preferenceContextToInt, preferences, } from "@/api/types/preferences" import { useEventAsState } from "@/util/eventdispatcher.ts" import useEvent from "@/util/useEvent.ts" import ClientContext from "../ClientContext.ts" import { LightboxContext, ModalCloseContext, ModalContext } from "../modal" import JSONView from "../util/JSONView.tsx" import Toggle from "../util/Toggle.tsx" import RoomStateExplorer from "./RoomStateExplorer.tsx" import CloseIcon from "@/icons/close.svg?react" import "./SettingsView.css" interface PreferenceCellProps { context: PreferenceContext name: keyof Preferences pref: Preference setPref: SetPrefFunc value: T | undefined inheritedValue: T } const makeRemover = ( context: PreferenceContext, setPref: SetPrefFunc, name: keyof Preferences, value: PreferenceValueType | undefined, ) => { if (value === undefined) { return null } return } const BooleanPreferenceCell = ({ context, name, setPref, value, inheritedValue }: PreferenceCellProps) => { return
setPref(context, name, evt.target.checked)}/> {makeRemover(context, setPref, name, value)}
} const TextPreferenceCell = ({ context, name, setPref, value, inheritedValue }: PreferenceCellProps) => { return
setPref(context, name, evt.target.value)}/> {makeRemover(context, setPref, name, value)}
} const SelectPreferenceCell = ({ context, name, pref, setPref, value, inheritedValue }: PreferenceCellProps) => { if (!pref.allowedValues) { return null } return
{makeRemover(context, setPref, name, value)}
} type SetPrefFunc = (context: PreferenceContext, key: keyof Preferences, value: PreferenceValueType | undefined) => void interface PreferenceRowProps { name: keyof Preferences pref: Preference setPref: SetPrefFunc globalServer?: PreferenceValueType globalLocal?: PreferenceValueType roomServer?: PreferenceValueType roomLocal?: PreferenceValueType } const customUIPrefs = new Set([ "custom_css", "custom_notification_sound", ] as (keyof Preferences)[]) const PreferenceRow = ({ name, pref, setPref, globalServer, globalLocal, roomServer, roomLocal, }: PreferenceRowProps) => { const prefType = typeof pref.defaultValue if (customUIPrefs.has(name)) { return null } const makeContentCell = ( context: PreferenceContext, val: PreferenceValueType | undefined, inheritedVal: PreferenceValueType, ) => { if (!pref.allowedContexts.includes(context)) { return null } if (prefType === "boolean") { return } value={val as boolean | undefined} inheritedValue={inheritedVal as boolean} /> } else if (pref.allowedValues) { return } value={val as string | undefined} inheritedValue={inheritedVal as string} /> } else if (prefType === "string") { return } value={val as string | undefined} inheritedValue={inheritedVal as string} /> } else { return null } } let inherit: PreferenceValueType return {pref.displayName} {makeContentCell(PreferenceContext.Account, globalServer, inherit = pref.defaultValue)} {makeContentCell(PreferenceContext.Device, globalLocal, inherit = globalServer ?? inherit)} {makeContentCell(PreferenceContext.RoomAccount, roomServer, inherit = globalLocal ?? inherit)} {makeContentCell(PreferenceContext.RoomDevice, roomLocal, inherit = roomServer ?? inherit)} } interface SettingsViewProps { room: RoomStateStore } function getActiveCSSContext(client: Client, room: RoomStateStore): PreferenceContext { if (room.localPreferenceCache.custom_css !== undefined) { return PreferenceContext.RoomDevice } else if (room.serverPreferenceCache.custom_css !== undefined) { return PreferenceContext.RoomAccount } else if (client.store.localPreferenceCache.custom_css !== undefined) { return PreferenceContext.Device } else { return PreferenceContext.Account } } const Monaco = lazy(() => import("../util/monaco.tsx")) const CustomCSSInput = ({ setPref, room }: { setPref: SetPrefFunc, room: RoomStateStore }) => { const client = use(ClientContext)! const appliedContext = getActiveCSSContext(client, room) const [context, setContext] = useState(appliedContext) const getContextText = (context: PreferenceContext) => { if (context === PreferenceContext.Account) { return client.store.serverPreferenceCache.custom_css } else if (context === PreferenceContext.Device) { return client.store.localPreferenceCache.custom_css } else if (context === PreferenceContext.RoomAccount) { return room.serverPreferenceCache.custom_css } else if (context === PreferenceContext.RoomDevice) { return room.localPreferenceCache.custom_css } } const origText = getContextText(context) const [text, setText] = useState(origText ?? "") const onChangeContext = (evt: React.ChangeEvent) => { const newContext = evt.target.value as PreferenceContext setContext(newContext) setText(getContextText(newContext) ?? "") } const onChangeText = (evt: React.ChangeEvent) => { setText(evt.target.value) } const onSave = useEvent(() => { if (vscodeOpen) { setText(vscodeContentRef.current) setPref(context, "custom_css", vscodeContentRef.current) } else { setPref(context, "custom_css", text) } }) const onDelete = () => { setPref(context, "custom_css", undefined) setText("") } const [vscodeOpen, setVSCodeOpen] = useState(false) const vscodeContentRef = useRef("") const vscodeInitialContentRef = useRef("") const onClickVSCode = () => { vscodeContentRef.current = text vscodeInitialContentRef.current = text setVSCodeOpen(true) } const closeVSCode = useCallback(() => { setVSCodeOpen(false) setText(vscodeContentRef.current) vscodeContentRef.current = "" }, []) return

Custom CSS

{preferenceContextToInt(context) < preferenceContextToInt(appliedContext) && ⚠️ This context will not be applied, {appliedContext} has content }
{vscodeOpen ?
}>
: