Files
mnote/wolai-frontend/src/components/sidebar/tree-shell-host.tsx
T

184 lines
5.8 KiB
TypeScript
Raw Normal View History

2026-04-24 06:10:18 +08:00
"use client";
import type { ReactNode } from "react";
import {
TreeShellIframeHost,
type TreeShellPickerItem,
} from "@/components/sidebar/tree-shell-iframe-host";
2026-04-26 04:29:23 +08:00
import type { KernelFileTreeProjectionItem } from "@/lib/kernel-file-tree";
import type { PageTreeProjectionItem } from "@/lib/tree-projection";
2026-04-24 06:10:18 +08:00
import { cn } from "@/lib/utils";
export type TreeRendererFamily = "react" | "rust_family";
export type TreeShellHostMode = "page" | "filetree" | "picker";
2026-04-26 04:29:23 +08:00
export type TreeShellPickerCommand = {
kind: "next" | "previous" | "home" | "end" | "pick";
seq: number;
};
export type FileTreeShellInternalDropPayload = {
targetDocumentId: string | null;
targetRowId: string | null;
targetRowKind: string | null;
targetAssetId: string | null;
rowIds: string[];
copy: boolean;
};
export type FileTreeShellExternalDropPayload = {
targetDocumentId: string | null;
targetRowId: string | null;
targetRowKind: string | null;
targetAssetId: string | null;
files: FileList | File[];
};
2026-04-24 06:10:18 +08:00
type TreeShellHostProps = {
mode: TreeShellHostMode;
surfaceTestId: string;
rendererFamily?: TreeRendererFamily;
className?: string;
treeShellEnabled?: boolean;
2026-04-26 04:29:23 +08:00
fallbackImplementation?: string;
2026-04-24 06:10:18 +08:00
workspaceId?: string | null;
rootNodeId?: string | null;
activeDocumentId?: string | null;
2026-04-26 04:29:23 +08:00
focusedDocumentId?: string | null;
activePickerItemKey?: string | null;
2026-04-24 06:10:18 +08:00
allowRootPick?: boolean;
excludeIds?: string[];
2026-04-26 04:29:23 +08:00
pickerCommand?: TreeShellPickerCommand | null;
2026-04-24 06:10:18 +08:00
pickerItems?: TreeShellPickerItem[];
2026-04-26 04:29:23 +08:00
pageTreeItems?: PageTreeProjectionItem[];
inlineFileTreeItems?: KernelFileTreeProjectionItem[];
2026-04-24 06:10:18 +08:00
channel?: string;
host?: string;
onNavigate?: (documentId: string) => void;
onPick?: (targetId: string | null) => void;
2026-04-26 04:29:23 +08:00
onPickerFocusChange?: (payload: { itemKey: string | null; documentId: string | null }) => void;
2026-04-24 06:10:18 +08:00
onPageContextMenu?: (payload: { documentId: string; x: number; y: number }) => void;
2026-04-26 04:29:23 +08:00
onPageExpandChange?: (payload: { documentId: string | null; expanded: boolean }) => void;
onPageFocusChange?: (payload: { documentId: string | null }) => void;
2026-04-24 06:10:18 +08:00
onFileTreeContextMenu?: (payload: {
documentId: string | null;
assetId: string | null;
rowId: string | null;
rowKind: string | null;
x: number;
y: number;
}) => void;
onFileTreeSelectionChange?: (payload: {
selectedRowIds: string[];
anchorRowId: string | null;
focusedRowId: string | null;
}) => void;
2026-04-26 04:29:23 +08:00
onInternalDrop?: (payload: FileTreeShellInternalDropPayload) => void;
onDropFiles?: (payload: FileTreeShellExternalDropPayload) => void;
2026-04-24 06:10:18 +08:00
onAssetOpen?: (payload: { assetId: string; documentId: string | null }) => void;
onTreeMutation?: (payload: { type: string; documentId: string | null }) => void;
children: ReactNode;
};
export function TreeShellHost({
mode,
surfaceTestId,
rendererFamily = "react",
className,
treeShellEnabled = true,
2026-04-26 04:29:23 +08:00
fallbackImplementation,
2026-04-24 06:10:18 +08:00
workspaceId = null,
rootNodeId = null,
activeDocumentId = null,
2026-04-26 04:29:23 +08:00
focusedDocumentId = null,
activePickerItemKey = null,
2026-04-24 06:10:18 +08:00
allowRootPick = false,
excludeIds = [],
2026-04-26 04:29:23 +08:00
pickerCommand = null,
pickerItems,
pageTreeItems,
inlineFileTreeItems,
2026-04-24 06:10:18 +08:00
channel,
host,
onNavigate,
onPick,
2026-04-26 04:29:23 +08:00
onPickerFocusChange,
2026-04-24 06:10:18 +08:00
onPageContextMenu,
2026-04-26 04:29:23 +08:00
onPageExpandChange,
onPageFocusChange,
2026-04-24 06:10:18 +08:00
onFileTreeContextMenu,
onFileTreeSelectionChange,
onInternalDrop,
onDropFiles,
onAssetOpen,
onTreeMutation,
children,
}: TreeShellHostProps) {
const useRustHost = rendererFamily === "rust_family";
2026-04-26 04:29:23 +08:00
const useIframeHost = useRustHost && Boolean(workspaceId?.trim());
2026-04-24 06:10:18 +08:00
const hostKind = rendererFamily === "rust_family" ? "rust_family" : "react";
const implementation = useIframeHost
? "mnote_web_iframe_proxy"
2026-04-26 04:29:23 +08:00
: fallbackImplementation ??
(rendererFamily === "rust_family" ? "react_fallback" : "react_primary");
2026-04-24 06:10:18 +08:00
return (
<div
data-testid={surfaceTestId}
data-shell-mode={mode}
data-renderer-family={rendererFamily}
data-tree-host-kind={hostKind}
data-tree-host-implementation={implementation}
2026-04-26 04:29:23 +08:00
data-page-tree-focused-id={mode === "page" ? (focusedDocumentId ?? "") : undefined}
2026-04-24 06:10:18 +08:00
className={cn(className)}
>
{useRustHost ? (
<div
data-testid={`${surfaceTestId}-rust-host`}
data-tree-host-mode={mode}
data-tree-host-kind="rust_family"
data-tree-host-implementation={implementation}
className="contents"
>
{useIframeHost && workspaceId ? (
<TreeShellIframeHost
mode={mode}
surfaceTestId={surfaceTestId}
workspaceId={workspaceId}
rootNodeId={rootNodeId}
activeDocumentId={activeDocumentId}
2026-04-26 04:29:23 +08:00
focusedDocumentId={focusedDocumentId}
activePickerItemKey={activePickerItemKey}
2026-04-24 06:10:18 +08:00
allowRootPick={allowRootPick}
excludeIds={excludeIds}
2026-04-26 04:29:23 +08:00
pickerCommand={pickerCommand}
2026-04-24 06:10:18 +08:00
pickerItems={pickerItems}
2026-04-26 04:29:23 +08:00
pageTreeItems={pageTreeItems}
inlineFileTreeItems={inlineFileTreeItems}
2026-04-24 06:10:18 +08:00
channel={channel}
host={host}
onNavigate={onNavigate}
onPick={onPick}
2026-04-26 04:29:23 +08:00
onPickerFocusChange={onPickerFocusChange}
2026-04-24 06:10:18 +08:00
onPageContextMenu={onPageContextMenu}
2026-04-26 04:29:23 +08:00
onPageExpandChange={onPageExpandChange}
onPageFocusChange={onPageFocusChange}
2026-04-24 06:10:18 +08:00
onFileTreeContextMenu={onFileTreeContextMenu}
onFileTreeSelectionChange={onFileTreeSelectionChange}
onInternalDrop={onInternalDrop}
onDropFiles={onDropFiles}
onAssetOpen={onAssetOpen}
onTreeMutation={onTreeMutation}
/>
) : (
children
)}
</div>
) : (
children
)}
</div>
);
}