diff options
author | metamuffin <metamuffin@disroot.org> | 2022-09-10 00:56:25 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2022-09-10 00:56:25 +0200 |
commit | 429dc2d5375abf8ca9c3861bdc4bdff52a31b0e4 (patch) | |
tree | 2fa8201ebdf45237a9ec90429bd18b5d6cdd9944 /client-web/source/preferences | |
parent | 95041256f86745832df42423e889d50d2cff35e7 (diff) | |
download | keks-meet-429dc2d5375abf8ca9c3861bdc4bdff52a31b0e4.tar keks-meet-429dc2d5375abf8ca9c3861bdc4bdff52a31b0e4.tar.bz2 keks-meet-429dc2d5375abf8ca9c3861bdc4bdff52a31b0e4.tar.zst |
overlay rework + settings
Diffstat (limited to 'client-web/source/preferences')
-rw-r--r-- | client-web/source/preferences/decl.ts | 26 | ||||
-rw-r--r-- | client-web/source/preferences/mod.ts | 87 | ||||
-rw-r--r-- | client-web/source/preferences/ui.ts | 27 |
3 files changed, 140 insertions, 0 deletions
diff --git a/client-web/source/preferences/decl.ts b/client-web/source/preferences/decl.ts new file mode 100644 index 0000000..5718a44 --- /dev/null +++ b/client-web/source/preferences/decl.ts @@ -0,0 +1,26 @@ +// there should be no deps to dom APIs in this file for the tablegen to work + +export function hex_id(len = 8): string { + if (len > 8) return hex_id() + hex_id(len - 8) + return Math.floor(Math.random() * 16 ** len).toString(16).padStart(len, "0") +} + +// TODO this could be simpler +const string = "", bool = false, number = 0; // example types for ts +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." }, + + /* MEDIA */ + microphone_enabled: { type: bool, default: false, description: "Add one microphone track on startup" }, + screencast_enabled: { type: bool, default: false, description: "Add one screencast track on startup" }, + camera_enabled: { type: bool, default: false, description: "Add one camera track on startup" }, + rnnoise: { type: bool, default: true, description: "Use RNNoise for noise suppression" }, + native_noise_suppression: { type: bool, default: false, description: "Suggest the browser to do noise suppression" }, + 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" }, + 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 new file mode 100644 index 0000000..5b33924 --- /dev/null +++ b/client-web/source/preferences/mod.ts @@ -0,0 +1,87 @@ +import { PREF_DECLS } from "./decl.ts"; + + +export interface PrefDecl<T> { + default?: T, + type: T, + description?: string, + possible_values?: T[] + optional?: boolean, +} + +type Type = "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"; +type TypeMapper = { "string": string, "number": number, "boolean": boolean } + +type PrefMap<T extends { [key: string]: { type: unknown } }> = { [Key in keyof T]: T[Key]["type"] } +type Optional<T extends { [key: string]: unknown }> = { [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)[] = [] + +export function register_prefs<T extends Record<string, PrefDecl<unknown>>>(ds: T): { prefs: PrefMap<T>, explicit: Optional<PrefMap<T>> } { + const prefs: PrefMap<T> = {} as PrefMap<T> + const explicit: Optional<PrefMap<T>> = {} + for (const key in ds) { + const d = ds[key]; + const type = typeof d.type; + let value = get_param(type, key) + if (value !== undefined) explicit[key] = value + value ??= d.default; + if (d.possible_values) if (!d.possible_values.includes(value)) value = d.default + prefs[key] = value + } + return { prefs, explicit } +} + +export function change_pref<T extends keyof typeof PREFS>(key: T, value: typeof PREFS[T]) { + PREFS[key] = value + if ((PREF_DECLS as Record<string, PrefDecl<unknown>>)[key].default != value) + PREFS_EXPLICIT[key] = value + else delete PREFS_EXPLICIT[key] + window.location.hash = "#" + generate_section() +} +export function generate_section(): string { + const section = [] + for (const key in PREFS_EXPLICIT) { + section.push(encodeURIComponent(key) + "=" + encodeURIComponent(param_to_string( + PREFS_EXPLICIT[key as unknown as keyof typeof PREFS_EXPLICIT] + ))) + } + return load_params().rname + "?" + section.join("&") +} + +export function load_params(): { raw_params: { [key: string]: string }, rname: string } { + const raw_params: Record<string, string> = {} + const [rname, param_str] = window.location.hash.substring(1).split("?") + if (!param_str) return { rname, raw_params: {} } + for (const kv of param_str.split("&")) { + const [key, value] = kv.split("=") + if (key == "prototype") continue + raw_params[decodeURIComponent(key)] = decodeURIComponent(value) + } + return { raw_params, rname } +} + +function param_to_string<T>(p: T): string { + if (typeof p == "string") return p + else if (typeof p == "boolean") return JSON.stringify(p) + else if (typeof p == "number") return JSON.stringify(p) + throw new Error("impossible"); +} + +function get_param<T>(ty: string, key: string): T | undefined { + const v = load_params().raw_params[key] + if (v == undefined) return undefined + if (ty == "string") return v as unknown as T + else if (ty == "number") { + const n = parseInt(v) + if (!Number.isNaN(n)) return n as unknown as T + console.warn("invalid number parameter"); + } else if (ty == "boolean") { + if (v == "0" || v == "false" || v == "no") return false as unknown as T + if (v == "1" || v == "true" || v == "yes") return true as unknown as T + console.warn("invalid boolean parameter"); + } else { + throw new Error("invalid param type"); + } + return undefined +} diff --git a/client-web/source/preferences/ui.ts b/client-web/source/preferences/ui.ts new file mode 100644 index 0000000..1aaaca0 --- /dev/null +++ b/client-web/source/preferences/ui.ts @@ -0,0 +1,27 @@ +import { ediv, elabel, espan, OverlayUi } from "../helper.ts"; +import { PREF_DECLS } from "./decl.ts"; +import { change_pref, PrefDecl, PREFS } from "./mod.ts"; + +export class PrefUi extends OverlayUi { + constructor() { + const elements = Object.entries(PREF_DECLS as Record<string, PrefDecl<unknown>>).map(([key_, decl]) => { + const key = key_ as keyof typeof PREF_DECLS + + if (typeof decl.type == "boolean") { + const id = `pref-check-${key}` + const checkbox = document.createElement("input") + checkbox.type = "checkbox" + checkbox.id = id + checkbox.checked = PREFS[key] as boolean + checkbox.onchange = () => { + change_pref(key, checkbox.checked) + } + const label = elabel(decl.description ?? `[${key}]`, { id }) + return ediv({ class: "pref" }, checkbox, label) + } + return espan(`(not implemented)`) + }) + super(ediv({ class: "prefs-overlay" }, ...elements)) + } + +} |