import { Component, computed, inject, signal } from '@angular/core'; import { NgIcon } from '@ng-icons/core'; import { BRANDING } from '@shared/branding'; import { LOCALE_LABELS, SUPPORTED } from '@shared/i18n'; import { COLUMN_KEYS } from '@core/context-menu.service'; import { I18nService } from '@core/i18n.service'; import { blockNativeContextMenu, IS_MAC_CHROME } from '@core/platform'; import { RpcService } from '@core/rpc.service'; import { SettingsService } from '@core/settings.service'; import { ShortcutsService } from '@core/shortcuts.service'; const SECTIONS = ['appearance', 'transfers', 'editor', 'fileList', 'about'] as const; type Section = (typeof SECTIONS)[number]; const THEMES = ['system', 'light', 'dark'] as const; const DARK_TONES = ['dark', 'darker'] as const; const SPEED_UNITS = [ { value: 'MBps', label: 'MB/s' }, { value: 'Mbps', label: 'Mbps' }, ] as const; const TRANSFER_MODES = ['single', 'multi'] as const; const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const; /** * Root of the Settings window (APP_MODE 'settings'). Every control is a plain * SettingsService setter: the save goes to bun, which rebroadcasts it, so the * other windows follow live while this one stays open. */ @Component({ selector: 'app-root', imports: [NgIcon], templateUrl: './settings-window.html', host: { '(window:keydown)': 'onWindowKeydown($event)', '(window:contextmenu)': 'blockContextMenu($event)', }, }) export class SettingsWindow { readonly settings = inject(SettingsService); readonly i18n = inject(I18nService); private readonly rpc = inject(RpcService); private readonly shortcuts = inject(ShortcutsService); readonly isMacChrome = IS_MAC_CHROME; readonly sections = SECTIONS; readonly section = signal
('appearance'); readonly localeOptions = SUPPORTED; readonly localeLabels = LOCALE_LABELS; readonly themes = THEMES; readonly darkTones = DARK_TONES; readonly speedUnits = SPEED_UNITS; readonly transferModes = TRANSFER_MODES; readonly concurrencyChoices = CONCURRENCY_CHOICES; readonly columnKeys = COLUMN_KEYS; readonly appName = BRANDING.appName; readonly buildLabel = BRANDING.buildLabel; /** "100%" for the stepper readout; the value itself lives in settings. */ readonly scalePercent = computed(() => `${Math.round(this.settings.uiScale() * 100)}%`); /** "Visual Studio Code.app" -> "Visual Studio Code"; empty -> system default. */ editorAppName(): string { const path = this.settings.editorApp(); if (!path) return this.i18n.t('options.systemDefault'); const base = path.split('/').filter(Boolean).pop() ?? path; return base.replace(/\.app$/i, ''); } async pickEditorApp(): Promise { const res = await this.rpc.request.pickEditorApp({}); if (res.ok && res.path) this.settings.setEditorApp(res.path); } showAbout(): void { void this.rpc.request.showAbout({}); } /** Any-window shortcuts (interface scale); ⌘W arrives as a menu action bun-side. */ onWindowKeydown(event: KeyboardEvent): void { const match = this.shortcuts.resolve(event, 'settings'); if (!match || this.shortcuts.isTypingTarget(event)) return; event.preventDefault(); if (match.id === 'scale-up') this.settings.setUiScale(this.settings.uiScale() + 0.1); else if (match.id === 'scale-down') this.settings.setUiScale(this.settings.uiScale() - 0.1); else if (match.id === 'scale-reset') this.settings.setUiScale(1); } blockContextMenu(event: Event): void { blockNativeContextMenu(event); } }