From c8a23ccb83a28808517915d3b76a8b8159e6ed4d Mon Sep 17 00:00:00 2001 From: metamuffin Date: Sat, 10 Sep 2022 21:47:20 +0200 Subject: new prefs --- client-web/source/preferences/decl.ts | 4 +- client-web/source/preferences/mod.ts | 13 +++++- client-web/source/preferences/ui.ts | 79 ++++++++++++++++++++++++++++++----- 3 files changed, 84 insertions(+), 12 deletions(-) (limited to 'client-web/source/preferences') diff --git a/client-web/source/preferences/decl.ts b/client-web/source/preferences/decl.ts index 6590eea..0d82bd2 100644 --- a/client-web/source/preferences/decl.ts +++ b/client-web/source/preferences/decl.ts @@ -7,6 +7,8 @@ export function hex_id(len = 8): string { // TODO this could be simpler const string = "", bool = false, number = 0; // example types for ts +const optional = (a: T): T | undefined => a + export const PREF_DECLS = { username: { type: string, default: "guest-" + hex_id(), description: "Username" }, warn_redirect: { type: bool, default: false, description: "Interal option that is set by a server redirect." }, @@ -21,7 +23,7 @@ export const PREF_DECLS = { microphone_gain: { type: number, default: 1, description: "Amplify microphone volume" }, video_fps: { type: number, description: "Preferred framerate (in 1/s) for screencast and camera" }, video_resolution: { type: number, description: "Preferred width for screencast and camera" }, - camera_facing_mode: { type: string, possible_values: ["environment", "user"], description: "Prefer user-facing or env-facing camera" }, + camera_facing_mode: { type: optional(string), possible_values: ["environment", "user"], description: "Prefer user-facing or env-facing camera" }, auto_gain_control: { type: bool, description: "Automatically adjust mic gain" }, echo_cancellation: { type: bool, description: "Cancel echo" }, diff --git a/client-web/source/preferences/mod.ts b/client-web/source/preferences/mod.ts index 5b33924..5f238bb 100644 --- a/client-web/source/preferences/mod.ts +++ b/client-web/source/preferences/mod.ts @@ -1,3 +1,4 @@ +import { log } from "../logger.ts"; import { PREF_DECLS } from "./decl.ts"; @@ -15,7 +16,15 @@ type TypeMapper = { "string": string, "number": number, "boolean": boolean } type PrefMap = { [Key in keyof T]: T[Key]["type"] } type Optional = { [Key in keyof T]?: T[Key] } export const { prefs: PREFS, explicit: PREFS_EXPLICIT } = register_prefs(PREF_DECLS) -export const on_pref_change_handlers: ((key: keyof typeof PREFS) => void)[] = [] +const pref_change_handlers: Map unknown>> = new Map() +export const on_pref_changed = (key: keyof typeof PREFS, cb: () => unknown) => + (pref_change_handlers.get(key) + ?? (() => { + const n = new Set<() => unknown>(); + pref_change_handlers.set(key, n); + return n + })() + ).add(cb) export function register_prefs>>(ds: T): { prefs: PrefMap, explicit: Optional> } { const prefs: PrefMap = {} as PrefMap @@ -33,10 +42,12 @@ export function register_prefs>>(ds: } export function change_pref(key: T, value: typeof PREFS[T]) { + log("*", `pref changed: ${key}`) PREFS[key] = value if ((PREF_DECLS as Record>)[key].default != value) PREFS_EXPLICIT[key] = value else delete PREFS_EXPLICIT[key] + pref_change_handlers.get(key)?.forEach(h => h()) window.location.hash = "#" + generate_section() } export function generate_section(): string { diff --git a/client-web/source/preferences/ui.ts b/client-web/source/preferences/ui.ts index 3461da9..4b0832a 100644 --- a/client-web/source/preferences/ui.ts +++ b/client-web/source/preferences/ui.ts @@ -1,14 +1,30 @@ -import { ebr, ebutton, ediv, elabel, espan, OverlayUi } from "../helper.ts"; +import { ebr, ebutton, ediv, elabel, espan, etd, etr, OverlayUi } from "../helper.ts"; import { PREF_DECLS } from "./decl.ts"; -import { change_pref, PrefDecl, PREFS } from "./mod.ts"; +import { change_pref, on_pref_changed, PrefDecl, PREFS } from "./mod.ts"; export class PrefUi extends OverlayUi { constructor() { - const elements = Object.entries(PREF_DECLS as Record>).map(([key_, decl]) => { - const key = key_ as keyof typeof PREF_DECLS + console.log(PREFS); - if (typeof decl.type == "boolean") { - const id = `pref-check-${key}` + const rows = Object.entries(PREF_DECLS as Record>).map(([key_, decl]) => { + const key = key_ as keyof typeof PREF_DECLS + const id = `pref-${key}` + let prim_control: HTMLInputElement | HTMLSelectElement | undefined; + if (decl.possible_values) { + const sel = document.createElement("select") + sel.id = id + sel.value = JSON.stringify(PREFS[key]) + for (const v of decl.possible_values) { + const opt = document.createElement("option") + opt.value = opt.textContent = JSON.stringify(v ?? null) + sel.append(opt) + } + sel.onchange = () => { + change_pref(key, JSON.parse(sel.value) ?? undefined) + } + on_pref_changed(key, () => sel.value = JSON.stringify(PREFS[key] ?? null)) + prim_control = sel + } else if (typeof decl.type == "boolean") { const checkbox = document.createElement("input") checkbox.type = "checkbox" checkbox.id = id @@ -16,16 +32,59 @@ export class PrefUi extends OverlayUi { checkbox.onchange = () => { change_pref(key, checkbox.checked) } - const label = elabel(decl.description ?? `[${key}]`, { id }) - return ediv({ class: "pref" }, checkbox, label) + on_pref_changed(key, () => checkbox.checked = PREFS[key] as boolean) + prim_control = checkbox + } else if (typeof decl.type == "string") { + const textbox = document.createElement("input") + textbox.type = "text" + textbox.id = id + textbox.value = PREFS[key] as string + textbox.onchange = () => { + change_pref(key, textbox.value) + } + on_pref_changed(key, () => textbox.value = PREFS[key] as string) + prim_control = textbox + } else if (typeof decl.type == "number") { + const textbox = document.createElement("input") + textbox.type = "number" + textbox.id = id + textbox.value = PREFS[key] as string + textbox.onchange = () => { + change_pref(key, parseFloat(textbox.value)) + } + on_pref_changed(key, () => textbox.value = PREFS[key] as string) + prim_control = textbox } - return espan(`(not implemented)`) + + let use_opt_; + if (decl.default === undefined || decl.optional) { + const use_opt = document.createElement("input") + use_opt.type = "checkbox" + use_opt.id = id + use_opt.checked = PREFS[key] !== undefined + if (prim_control) prim_control.disabled = !use_opt.checked + use_opt.onchange = () => { + if (use_opt.checked) { if (prim_control?.onchange) prim_control.onchange(new Event("change")) } + else change_pref(key, undefined) + } + on_pref_changed(key, () => { + use_opt.checked = PREFS[key] !== undefined + if (prim_control) prim_control.disabled = !use_opt.checked + }) + use_opt_ = use_opt; + } + + const label = elabel(decl.description ?? `[${key}]`, { id }) + return etr({ class: "pref" }, etd({}, label), etd({}, use_opt_ ?? ""), etd({}, prim_control ?? "")) }) const notification_perm = Notification.permission == "granted" ? ediv() : ediv({}, espan("For keks-meet to send notifications, it needs you to grant permission: "), ebutton("Grant", { onclick: () => Notification.requestPermission() }), ) - super(ediv({ class: "prefs-overlay" }, notification_perm, ebr(), ...elements)) + const table = document.createElement("table") + table.append(...rows) + + super(ediv({ class: "prefs-overlay" }, notification_perm, ebr(), table)) } } -- cgit v1.2.3-70-g09d2