import { Component, ElementRef, afterNextRender, inject, input, signal, viewChild, } from '@angular/core'; import { NgIcon } from '@ng-icons/core'; import { DialogsService, type EditorRequest } from '../dialogs.service'; import { PanelsService } from '../panels.service'; import { RpcService } from '../rpc.service'; import { ToastService } from '../toast.service'; @Component({ selector: 'app-editor-dialog', imports: [NgIcon], template: `

{{ request().name }}{{ dirty() ? ' •' : '' }}

{{ request().sessionTitle }}
⌘S saves · Esc closes
`, }) export class EditorDialog { readonly request = input.required(); readonly text = signal(''); readonly dirty = signal(false); readonly saving = signal(false); private readonly box = viewChild.required>('box'); private readonly dialogs = inject(DialogsService); private readonly panels = inject(PanelsService); private readonly rpc = inject(RpcService); private readonly toast = inject(ToastService); constructor() { afterNextRender(() => { this.text.set(this.request().content); this.box().nativeElement.focus(); }); } onInput(value: string): void { this.text.set(value); this.dirty.set(value !== this.request().content); } onKeydown(event: KeyboardEvent): void { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 's') { event.preventDefault(); void this.save(); } else if (event.key === 'Escape') { event.preventDefault(); this.close(); } else if (event.key === 'Tab' && event.target === this.box().nativeElement) { // keep Tab as an input character inside the editor event.preventDefault(); const el = this.box().nativeElement; const { selectionStart, selectionEnd, value } = el; el.value = `${value.slice(0, selectionStart)}\t${value.slice(selectionEnd)}`; el.selectionStart = el.selectionEnd = selectionStart + 1; this.onInput(el.value); } } async save(): Promise { const req = this.request(); if (!this.dirty() || this.saving()) return; this.saving.set(true); try { const res = await this.rpc.request.writeFile({ sessionId: req.sessionId, path: req.path, content: this.text(), }); if (!res.ok) { this.toast.error(res.error); return; } this.toast.success(`Saved ${req.name}`); req.content = this.text(); this.dirty.set(false); void req.panel.refresh(); } finally { this.saving.set(false); } } close(): void { if (this.dirty()) { this.dialogs.confirm.set({ title: 'Discard changes?', body: `"${this.request().name}" has unsaved changes.`, confirmLabel: 'Discard', destructive: true, onConfirm: () => this.forceClose(), }); return; } this.forceClose(); } private forceClose(): void { this.dialogs.editor.set(null); this.panels.refocus(); } }