feat(ui): fenêtres View système séparées — WebviewWindow Tauri + port WindowGateway (#23)
Détache les vues dans de vraies fenêtres système Tauri (multi-écran, fullscreen). Backend : commandes de gestion de WebviewWindow, capabilities et composition root. Frontend : nouveau port WindowGateway et son adaptateur window, entrée panel-only ViewWindow/ViewPanelBody, détachement câblé dans ProjectsView. QA vert : app-tauri 63, frontend typecheck + vitest 546/546. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
54
frontend/src/adapters/window.ts
Normal file
54
frontend/src/adapters/window.ts
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Tauri adapter for {@link WindowGateway} (ticket #23). Detaches a View into its
|
||||
* own OS window by delegating to the backend's window-lifecycle commands. Like
|
||||
* the sibling adapters, this is the only layer allowed to touch
|
||||
* `@tauri-apps/api`; components reach it exclusively through the port.
|
||||
*
|
||||
* Command/event names and payloads below mirror the backend contract owned by
|
||||
* DevBackend (build-verified). The backend exposes a single lifecycle channel
|
||||
* `view-window://lifecycle`; this adapter narrows it to the port's close-only
|
||||
* `onViewWindowClosed` by filtering on `kind === "closed"`.
|
||||
*/
|
||||
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
import type { Unsubscribe } from "@/domain";
|
||||
import type { ViewWindowClosed, WindowGateway } from "@/ports";
|
||||
|
||||
/** The single Tauri event carrying detached-window lifecycle transitions. */
|
||||
const VIEW_WINDOW_LIFECYCLE = "view-window://lifecycle";
|
||||
|
||||
/** Backend lifecycle payload (discriminated on `kind`). */
|
||||
interface ViewWindowLifecycle {
|
||||
kind: "opened" | "focused" | "closed";
|
||||
panel: string;
|
||||
projectId: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export class TauriWindowGateway implements WindowGateway {
|
||||
async openViewWindow(panel: string, projectId: string): Promise<void> {
|
||||
// camelCase args (Tauri maps them to the command's `panel` / `project_id`).
|
||||
await invoke("open_view_window", { panel, projectId });
|
||||
}
|
||||
|
||||
async closeViewWindow(panel: string, projectId: string): Promise<void> {
|
||||
await invoke("close_view_window", { panel, projectId });
|
||||
}
|
||||
|
||||
async onViewWindowClosed(
|
||||
handler: (event: ViewWindowClosed) => void,
|
||||
): Promise<Unsubscribe> {
|
||||
// One backend channel for opened/focused/closed; the port only surfaces the
|
||||
// close, so drop the other transitions and forward `{ panel, projectId }`.
|
||||
const unlisten = await listen<ViewWindowLifecycle>(
|
||||
VIEW_WINDOW_LIFECYCLE,
|
||||
(e) => {
|
||||
if (e.payload.kind !== "closed") return;
|
||||
handler({ panel: e.payload.panel, projectId: e.payload.projectId });
|
||||
},
|
||||
);
|
||||
return unlisten;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user