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();
}
}