import { useEffect, useMemo, type ChangeEvent } from "react"; import { Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Checkbox, } from "@arronqzy/ui"; import { LIFECYCLE_NODE_TYPE, PAGE_LIFECYCLE_PHASES, VIEW_EVENT_TYPES, type ExecutionTraceEntry, type PageLifecyclePhase, type ViewEventType, } from "@arronqzy/blueprint-dsl"; import { useI18n } from "@arronqzy/i18n/react"; import { FetchNodeConfigPanel } from "./components/FetchNodeConfigPanel"; import { ClockNodeConfigPanel } from "./components/ClockNodeConfigPanel"; import { JsonNodeConfigPanel } from "./components/JsonNodeConfigPanel"; import { LogicNodeConfigPanel } from "./components/LogicNodeConfigPanel"; import { ViewElementMultiSelect } from "./components/ViewElementMultiSelect"; import { ConfigFieldLabel, ConfigHintIcon, ConfigSectionTitle } from "./components/ConfigHintIcon"; import { getLifecyclePhaseLabel, getViewEventTypeLabel, inspectEventNodeInputs, patchNodeConfigSource, pruneViewElementIds, resolveBlueprintConfigSource, resolveNodeEventConfig, resolveViewElementIds, type BlueprintConfigSource, type BlueprintGraphEdge, type BlueprintGraphNode, type BlueprintNodeRole, } from "./graph/document"; export type BlueprintViewElementOption = { id: string; label: string; }; export type BlueprintLibraryOption = { id: string; label: string; }; export type BlueprintNodeConfigSidebarProps = { node: BlueprintGraphNode; graphNodes?: BlueprintGraphNode[]; graphEdges?: BlueprintGraphEdge[]; traceEntries?: ExecutionTraceEntry[]; viewElementOptions?: BlueprintViewElementOption[]; blueprintLibraryOptions?: BlueprintLibraryOption[]; allowFalseSignalPropagation?: boolean; onUpdateAllowFalseSignalPropagation?: (value: boolean) => void; onUpdateNode: ( nodeId: string, patch: Partial< Pick< BlueprintGraphNode, | "label" | "role" | "nodeType" | "configSource" | "viewElementId" | "viewElementIds" | "nestedBlueprintId" | "libraryBlueprintId" | "lifecyclePhase" | "fetchConfig" | "jsonConfig" | "logicConfig" | "clockConfig" | "eventConfig" > > ) => void; }; const ROLE_LABEL_KEYS: Record = { blueprint: "blueprint.config.roleBlueprint", lifecycle: "blueprint.config.roleLifecycle", and: "blueprint.config.roleAnd", fetch: "blueprint.config.roleFetch", json: "blueprint.config.roleJson", logic: "blueprint.config.roleLogic", clock: "blueprint.config.roleClock", event: "blueprint.config.roleEvent", }; export function BlueprintNodeConfigSidebar({ node, graphNodes = [], graphEdges = [], traceEntries = [], viewElementOptions = [], blueprintLibraryOptions = [], allowFalseSignalPropagation = false, onUpdateAllowFalseSignalPropagation, onUpdateNode, }: BlueprintNodeConfigSidebarProps) { const { t } = useI18n(); const configSource = resolveBlueprintConfigSource(node); const linkedViewElementIds = resolveViewElementIds(node); const selectedEventTypes = resolveNodeEventConfig(node).eventTypes; const existingViewElementIdSet = useMemo( () => new Set(viewElementOptions.map((opt) => opt.id)), [viewElementOptions] ); const viewElementLabelById = new Map( viewElementOptions.map((opt) => [opt.id, opt.label]) ); useEffect(() => { if (configSource !== "view" && configSource !== "event") return; // 视图画布尚未就绪时不清理,避免误删有效绑定导致绑定节点「执行了但不写 scope」 if (viewElementOptions.length === 0) return; const linked = resolveViewElementIds(node); if (linked.length === 0) return; const pruned = pruneViewElementIds(linked, existingViewElementIdSet); if (pruned.length === linked.length) return; onUpdateNode(node.id, { viewElementIds: pruned.length > 0 ? pruned : undefined, viewElementId: undefined, configSource, }); }, [ configSource, existingViewElementIdSet, node.id, node.viewElementId, node.viewElementIds, onUpdateNode, viewElementOptions.length, ]); const roleLabel = t(ROLE_LABEL_KEYS[node.role] ?? "blueprint.config.roleLogic"); const eventInputs = useMemo( () => inspectEventNodeInputs(node.id, graphNodes, graphEdges), [graphEdges, graphNodes, node.id] ); return (
{t("blueprint.config.title")}
{roleLabel} · {node.id}
{configSource === "view" || configSource === "event" ? (
onUpdateNode(node.id, { viewElementIds: next, viewElementId: undefined, configSource, }) } /> {linkedViewElementIds.length > 0 ? (

{t("blueprint.config.linkedViewCount", { count: linkedViewElementIds.length, names: linkedViewElementIds .map((id) => viewElementLabelById.get(id) ?? id) .join("、"), })}

) : null}
) : null} {configSource === "blueprint" ? (
{!node.libraryBlueprintId ? (

{t("blueprint.config.selectLibraryFirst")}

) : null}
) : null} {configSource === "event" ? (
{t("blueprint.config.eventListen")}

{t("blueprint.config.eventNoInput")}

{t("blueprint.config.eventHint")}

{eventInputs.nonLifecycleCount > 0 ? (

{t("blueprint.config.eventInputNotLifecycle")}

) : eventInputs.lifecycleCount === 0 ? (

{t("blueprint.config.eventInputMissing")}

) : null}
{VIEW_EVENT_TYPES.map((eventType) => { const checked = selectedEventTypes.includes(eventType); return ( ); })}
) : null} {configSource === "lifecycle" ? (
{t("blueprint.config.lifecycleHook")}

{t("blueprint.config.lifecycleNoInput")}

{node.lifecyclePhase === "blueprintActivated" ? t("blueprint.config.lifecycleBlueprintActivatedHint") : t("blueprint.config.lifecyclePageHint")}

{node.parentId ? (

{t("blueprint.config.parentBlueprintNode", { id: node.parentId })}

) : null}
) : null} {configSource === "fetch" ? ( ) : null} {configSource === "json" ? ( ) : null} {configSource === "clock" ? ( ) : null} {configSource === "and" ? (
) : null} {configSource === "logic" ? ( <> {node.parentId ? (

{t("blueprint.config.parentBlueprintNode", { id: node.parentId })}

) : null} ) : null} {configSource !== "blueprint" && configSource !== "and" ? (
{t("blueprint.config.taskChain")}
) : null}
); }