84 lines
2.3 KiB
TypeScript
84 lines
2.3 KiB
TypeScript
// Local-only user preferences for push-to-talk. Stored in localStorage because
|
|
// the server is zero-knowledge and doesn't need to know input-device details.
|
|
|
|
const STORAGE_KEY = 'chatapp.ptt';
|
|
|
|
export interface PttSettings {
|
|
enabled: boolean;
|
|
// KeyboardEvent.code of the hold-to-talk key (e.g. 'Space', 'KeyV').
|
|
key: string;
|
|
// Human-readable label derived from the key — kept in settings so we don't
|
|
// re-derive it on every render. Updated together with `key`.
|
|
keyLabel: string;
|
|
}
|
|
|
|
const DEFAULTS: PttSettings = {
|
|
enabled: false,
|
|
key: 'Space',
|
|
keyLabel: 'Space',
|
|
};
|
|
|
|
type Listener = (s: PttSettings) => void;
|
|
const listeners = new Set<Listener>();
|
|
|
|
let cached: PttSettings | null = null;
|
|
|
|
function read(): PttSettings {
|
|
if (cached) return cached;
|
|
try {
|
|
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
if (!raw) {
|
|
cached = DEFAULTS;
|
|
return cached;
|
|
}
|
|
const parsed = JSON.parse(raw) as Partial<PttSettings>;
|
|
cached = {
|
|
enabled: typeof parsed.enabled === 'boolean' ? parsed.enabled : DEFAULTS.enabled,
|
|
key: typeof parsed.key === 'string' && parsed.key ? parsed.key : DEFAULTS.key,
|
|
keyLabel:
|
|
typeof parsed.keyLabel === 'string' && parsed.keyLabel
|
|
? parsed.keyLabel
|
|
: DEFAULTS.keyLabel,
|
|
};
|
|
return cached;
|
|
} catch {
|
|
cached = DEFAULTS;
|
|
return cached;
|
|
}
|
|
}
|
|
|
|
function write(s: PttSettings): void {
|
|
cached = s;
|
|
try {
|
|
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(s));
|
|
} catch {
|
|
/* quota / private mode */
|
|
}
|
|
for (const l of listeners) l(s);
|
|
}
|
|
|
|
export function getPttSettings(): PttSettings {
|
|
return read();
|
|
}
|
|
|
|
export function updatePttSettings(patch: Partial<PttSettings>): PttSettings {
|
|
const next = { ...read(), ...patch };
|
|
write(next);
|
|
return next;
|
|
}
|
|
|
|
export function subscribePttSettings(listener: Listener): () => void {
|
|
listeners.add(listener);
|
|
return () => listeners.delete(listener);
|
|
}
|
|
|
|
// Turns a KeyboardEvent.code into a short human label (best-effort).
|
|
export function keyCodeToLabel(code: string): string {
|
|
if (code === 'Space') return 'Space';
|
|
if (code.startsWith('Key')) return code.slice(3);
|
|
if (code.startsWith('Digit')) return code.slice(5);
|
|
if (code.startsWith('Numpad')) return 'Num' + code.slice(6);
|
|
if (code.startsWith('Arrow')) return code.slice(5) + ' Arrow';
|
|
return code;
|
|
}
|