tiletopia/src/lib/layout/orchestration.tsx

85 lines
3 KiB
TypeScript

import { createContext, useContext, type ReactNode } from "react";
import type { Orientation, NodeId, LeafShellSpec } from "./tree";
import type { PaneId, SshHost } from "../../ipc";
/**
* Orchestration context — every piece of shared state and every operation
* that a Pane / SplitNode / LeafPane might call. Lives in React context so
* descendants can `useOrchestration()` without prop drilling.
*
* activeLeafId comes in as a plain value (re-derived by App's useState).
* React's context is reactive: when the App-level Provider updates the
* value, ALL consumers re-render. No Svelte-style props-don't-propagate
* trap here.
*/
export interface Orchestration {
// Read-only state
activeLeafId: NodeId | null;
/** WSL distros enumerated from `wsl.exe -l -q`. PowerShell is a separate
* shell kind, not in this list. */
distros: string[];
/** Saved SSH hosts loaded from `hosts.json`. Reactive — changes when the
* user edits hosts via {@link openHostManager}. */
hosts: SshHost[];
// Tree mutations
split: (leafId: NodeId, orientation: Orientation) => void;
close: (leafId: NodeId) => void;
/** Change the shell on a leaf (WSL distro / PowerShell / SSH host).
* Always forces a respawn — the helper in tree.ts swaps the leaf id so
* the renderer remounts XtermPane. */
setShell: (leafId: NodeId, spec: LeafShellSpec) => void;
setLabel: (leafId: NodeId, label: string | undefined) => void;
toggleBroadcast: (leafId: NodeId) => void;
/** Flip the per-pane mcpAllow flag. Default-deny; chip in the pane
* toolbar drives this. */
toggleMcpAllow: (leafId: NodeId) => void;
// SSH host management
openHostManager: () => void;
// Per-pane orchestration
setActive: (leafId: NodeId) => void;
registerPaneId: (leafId: NodeId, paneId: PaneId | null) => void;
broadcastFrom: (originLeafId: NodeId, dataB64: string) => void;
notify: (message: string) => void;
// Drag-header-to-swap. dragSourceId / dragOverId are reactive so leaves
// can apply hover/source styling. The lifecycle methods are stable.
dragSourceId: NodeId | null;
dragOverId: NodeId | null;
beginHeaderDrag: (leafId: NodeId) => void;
setHeaderDragOver: (leafId: NodeId | null) => void;
endHeaderDrag: (commitSwap: boolean) => void;
// Per-leaf idle reporting. LeafPanes call reportLeafIdle when their
// own quiet-state crosses the threshold; App aggregates so the titlebar
// can show an "N idle" count without spamming toast notifications.
reportLeafIdle: (leafId: NodeId, idle: boolean) => void;
}
const OrchestrationContext = createContext<Orchestration | null>(null);
export function OrchestrationProvider({
value,
children,
}: {
value: Orchestration;
children: ReactNode;
}) {
return (
<OrchestrationContext.Provider value={value}>
{children}
</OrchestrationContext.Provider>
);
}
export function useOrchestration(): Orchestration {
const orch = useContext(OrchestrationContext);
if (!orch) {
throw new Error(
"useOrchestration() must be called inside <OrchestrationProvider>",
);
}
return orch;
}