Decouple dialog refocus from PanelsService via FocusService
Dialogs only needed refocus(); injecting PanelsService boots a whole workspace, which secondary windows must not do. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { Component, computed, inject, input, signal } from '@angular/core';
|
import { Component, computed, inject, input, signal } from '@angular/core';
|
||||||
import { DialogsService, type ChmodRequest } from '../dialogs.service';
|
import { DialogsService, type ChmodRequest } from '../dialogs.service';
|
||||||
import { PanelsService } from '../panels.service';
|
import { FocusService } from '../focus.service';
|
||||||
|
|
||||||
const ROLES = ['Owner', 'Group', 'Others'] as const;
|
const ROLES = ['Owner', 'Group', 'Others'] as const;
|
||||||
const PERMS = ['r', 'w', 'x'] as const;
|
const PERMS = ['r', 'w', 'x'] as const;
|
||||||
@@ -81,7 +81,7 @@ export class ChmodDialog {
|
|||||||
readonly perms = PERMS;
|
readonly perms = PERMS;
|
||||||
|
|
||||||
private readonly dialogs = inject(DialogsService);
|
private readonly dialogs = inject(DialogsService);
|
||||||
private readonly panels = inject(PanelsService);
|
private readonly focus = inject(FocusService);
|
||||||
|
|
||||||
// mode is initialized lazily from the request on first read
|
// mode is initialized lazily from the request on first read
|
||||||
private readonly modeOverride = signal<number | null>(null);
|
private readonly modeOverride = signal<number | null>(null);
|
||||||
@@ -120,6 +120,6 @@ export class ChmodDialog {
|
|||||||
|
|
||||||
private close(): void {
|
private close(): void {
|
||||||
this.dialogs.chmod.set(null);
|
this.dialogs.chmod.set(null);
|
||||||
this.panels.refocus();
|
this.focus.bump();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component, ElementRef, afterNextRender, inject, input, viewChild } from '@angular/core';
|
import { Component, ElementRef, afterNextRender, inject, input, viewChild } from '@angular/core';
|
||||||
import { DialogsService, type ConfirmRequest } from '../dialogs.service';
|
import { DialogsService, type ConfirmRequest } from '../dialogs.service';
|
||||||
import { PanelsService } from '../panels.service';
|
import { FocusService } from '../focus.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-confirm-dialog',
|
selector: 'app-confirm-dialog',
|
||||||
@@ -41,7 +41,7 @@ export class ConfirmDialog {
|
|||||||
|
|
||||||
private readonly confirmBtn = viewChild.required<ElementRef<HTMLButtonElement>>('confirmBtn');
|
private readonly confirmBtn = viewChild.required<ElementRef<HTMLButtonElement>>('confirmBtn');
|
||||||
private readonly dialogs = inject(DialogsService);
|
private readonly dialogs = inject(DialogsService);
|
||||||
private readonly panels = inject(PanelsService);
|
private readonly focus = inject(FocusService);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
afterNextRender(() => this.confirmBtn().nativeElement.focus());
|
afterNextRender(() => this.confirmBtn().nativeElement.focus());
|
||||||
@@ -59,6 +59,6 @@ export class ConfirmDialog {
|
|||||||
|
|
||||||
private close(): void {
|
private close(): void {
|
||||||
this.dialogs.confirm.set(null);
|
this.dialogs.confirm.set(null);
|
||||||
this.panels.refocus();
|
this.focus.bump();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import { NgIcon } from '@ng-icons/core';
|
import { NgIcon } from '@ng-icons/core';
|
||||||
import { DialogsService, type OverwriteChoice, type OverwriteRequest } from '../dialogs.service';
|
import { DialogsService, type OverwriteChoice, type OverwriteRequest } from '../dialogs.service';
|
||||||
import { formatDate, formatSize } from '../format';
|
import { formatDate, formatSize } from '../format';
|
||||||
import { PanelsService } from '../panels.service';
|
import { FocusService } from '../focus.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-overwrite-dialog',
|
selector: 'app-overwrite-dialog',
|
||||||
@@ -125,7 +125,7 @@ export class OverwriteDialog {
|
|||||||
readonly formatDate = formatDate;
|
readonly formatDate = formatDate;
|
||||||
|
|
||||||
private readonly dialogs = inject(DialogsService);
|
private readonly dialogs = inject(DialogsService);
|
||||||
private readonly panels = inject(PanelsService);
|
private readonly focus = inject(FocusService);
|
||||||
private readonly primaryBtn = viewChild.required<ElementRef<HTMLButtonElement>>('primaryBtn');
|
private readonly primaryBtn = viewChild.required<ElementRef<HTMLButtonElement>>('primaryBtn');
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -145,7 +145,7 @@ export class OverwriteDialog {
|
|||||||
pick(choice: OverwriteChoice): void {
|
pick(choice: OverwriteChoice): void {
|
||||||
const req = this.request();
|
const req = this.request();
|
||||||
this.dialogs.overwrite.set(null);
|
this.dialogs.overwrite.set(null);
|
||||||
this.panels.refocus();
|
this.focus.bump();
|
||||||
req.resolve(choice);
|
req.resolve(choice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
viewChild,
|
viewChild,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { DialogsService, type PromptRequest } from '../dialogs.service';
|
import { DialogsService, type PromptRequest } from '../dialogs.service';
|
||||||
import { PanelsService } from '../panels.service';
|
import { FocusService } from '../focus.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-prompt-dialog',
|
selector: 'app-prompt-dialog',
|
||||||
@@ -54,7 +54,7 @@ export class PromptDialog {
|
|||||||
|
|
||||||
private readonly box = viewChild.required<ElementRef<HTMLInputElement>>('box');
|
private readonly box = viewChild.required<ElementRef<HTMLInputElement>>('box');
|
||||||
private readonly dialogs = inject(DialogsService);
|
private readonly dialogs = inject(DialogsService);
|
||||||
private readonly panels = inject(PanelsService);
|
private readonly focus = inject(FocusService);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
afterNextRender(() => {
|
afterNextRender(() => {
|
||||||
@@ -79,6 +79,6 @@ export class PromptDialog {
|
|||||||
|
|
||||||
private close(): void {
|
private close(): void {
|
||||||
this.dialogs.prompt.set(null);
|
this.dialogs.prompt.set(null);
|
||||||
this.panels.refocus();
|
this.focus.bump();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Injectable, signal } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Give keyboard focus back to the main surface" signal. Dependency-free so
|
||||||
|
* dialogs can bump it from ANY window kind — in the main window the file
|
||||||
|
* panels listen (via PanelsService.focusTick); elsewhere nobody does, which
|
||||||
|
* is exactly right.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class FocusService {
|
||||||
|
readonly tick = signal(0);
|
||||||
|
|
||||||
|
bump(): void {
|
||||||
|
this.tick.update((n) => n + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable, computed, signal, type WritableSignal } from '@angular/core';
|
import { Injectable, computed, inject, signal, type WritableSignal } from '@angular/core';
|
||||||
import type { ConnectionConfig, FileEntry, SavedConnection } from '@shared/types';
|
import type { ConnectionConfig, FileEntry, SavedConnection } from '@shared/types';
|
||||||
|
import { FocusService } from './focus.service';
|
||||||
import { RpcService } from './rpc.service';
|
import { RpcService } from './rpc.service';
|
||||||
import { ToastService } from './toast.service';
|
import { ToastService } from './toast.service';
|
||||||
|
|
||||||
@@ -201,8 +202,9 @@ export class PanelsService {
|
|||||||
const list = this.workspaces();
|
const list = this.workspaces();
|
||||||
return list.find((w) => w.id === this.activeWorkspaceId()) ?? list[0];
|
return list.find((w) => w.id === this.activeWorkspaceId()) ?? list[0];
|
||||||
});
|
});
|
||||||
|
private readonly focus = inject(FocusService);
|
||||||
/** Bumped to tell the active panel to grab keyboard focus again (e.g. after a dialog closes). */
|
/** Bumped to tell the active panel to grab keyboard focus again (e.g. after a dialog closes). */
|
||||||
readonly focusTick = signal(0);
|
readonly focusTick = this.focus.tick;
|
||||||
|
|
||||||
private nextWorkspaceId = 1;
|
private nextWorkspaceId = 1;
|
||||||
|
|
||||||
@@ -265,7 +267,7 @@ export class PanelsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refocus(): void {
|
refocus(): void {
|
||||||
this.focusTick.update((n) => n + 1);
|
this.focus.bump();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- workspace (tab) management ----
|
// ---- workspace (tab) management ----
|
||||||
|
|||||||
Reference in New Issue
Block a user