initial
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user