The keymap was scattered over ShortcutsService if-chains, the file panel's key switch, macOS-only menu accelerators and hand-typed hints in 16 tooltips x 7 locales, and large parts of the app had no keyboard path at all. src/shared/keymap.ts is now the one table: scopes, per-platform defaults, exact matching, formatting with localized key names, conflicts, reserved combos and native-menu accelerators, proven by keymap.spec.ts. - ShortcutsService resolves against the effective keymap: claim() takes the key (preventDefault, held keys repeat only movement, never mid-IME), and from a text field only chords and F-keys fire. The file panel, App, editor, manager and Settings windows all dispatch through it. - FileActionsService.run is the one file-command dispatcher for the list keys, the row menu, the macOS menu bar and the toolbar. - New keys: the row menu (Shift+F10 / Menu / Ctrl+Return), the source picker (Mod+K), the path bar (Mod+L), transfers (Mod+J), sorting (Alt+1-7), Shift+movement selection, close other tabs, move and disconnect tabs, refresh and edit on Windows/Linux, permissions, copy path, reveal, trash, open in default app, editor find next/previous. - Right-click menus: soft shortcut hints, icons on the everyday verbs (the toolbar's glyphs), section headers, scrolling, item keys while open, a highlighted first item when opened from the keyboard. The panel source picker now renders through the same menu. - Dialogs: role=dialog, a Tab trap via cycleFocus (WKWebView skips buttons on Tab without Keyboard navigation), initial focus, Esc that stops propagating; the chmod dialog takes focus and applies on Enter. - Fixed: Ctrl+Tab also flipped the active panel; Esc in a manager dialog closed the manager; Ctrl-F/T fired Cmd shortcuts on macOS; shortcuts fired under dialogs; keys died after any button click on macOS; the editor's save prompt and find box never got focus; WebView2 reload keys; a window could save default settings before its first load. - Settings closes on its own close-window key (new settingsClose request). Co-Authored-By: Claude <noreply@anthropic.com>
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
import { Injectable, inject, signal } from '@angular/core';
|
|
import { RpcService } from './rpc.service';
|
|
|
|
export type UpdaterState = 'idle' | 'available' | 'downloading' | 'ready' | 'error';
|
|
|
|
/**
|
|
* Drives the tab-bar update pill. The backend pushes updateStatus events
|
|
* (available / downloading progress / error); "ready" is derived locally when
|
|
* downloadUpdate resolves ok. Two-step UX: available -> download -> ready ->
|
|
* apply (relaunch).
|
|
*/
|
|
@Injectable({ providedIn: 'root' })
|
|
export class UpdaterService {
|
|
private rpc = inject(RpcService);
|
|
|
|
readonly state = signal<UpdaterState>('idle');
|
|
readonly version = signal('');
|
|
readonly progress = signal(0);
|
|
|
|
constructor() {
|
|
if (!this.rpc.available) return;
|
|
this.rpc.onUpdateStatus((s) => {
|
|
if (s.phase === 'available') {
|
|
this.version.set(s.version ?? '');
|
|
this.state.set('available');
|
|
} else if (s.phase === 'downloading') {
|
|
this.state.set('downloading');
|
|
this.progress.set(s.progress ?? 0);
|
|
} else if (s.phase === 'error') {
|
|
this.state.set('error');
|
|
}
|
|
});
|
|
}
|
|
|
|
async download(): Promise<void> {
|
|
if (this.state() !== 'available') return;
|
|
this.state.set('downloading');
|
|
this.progress.set(0);
|
|
const r = await this.rpc.request.downloadUpdate({});
|
|
this.state.set(r?.ok ? 'ready' : 'error');
|
|
}
|
|
|
|
apply(): void {
|
|
if (this.state() !== 'ready') return;
|
|
void this.rpc.request.applyUpdate({}); // quits + relaunches
|
|
}
|
|
|
|
/** The pill's one action, also the "Install update" key: download when
|
|
* available, apply (relaunch) once ready; nothing otherwise. */
|
|
advance(): void {
|
|
const state = this.state();
|
|
if (state === 'available') void this.download();
|
|
else if (state === 'ready') this.apply();
|
|
}
|
|
|
|
/** Manual re-check (menu item). */
|
|
async check(): Promise<void> {
|
|
if (!this.rpc.available) return;
|
|
const r = await this.rpc.request.checkForUpdate({});
|
|
if (r?.ok && r.updateAvailable) {
|
|
this.version.set(r.version);
|
|
this.state.set('available');
|
|
}
|
|
}
|
|
}
|