@umodeapporg/ui 0.22.3 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/umode-ui.css +105 -0
- package/dist/components/UI/Flow/FlowBlockNode.d.ts +35 -0
- package/dist/components/UI/Flow/FlowBlockNode.js +84 -0
- package/dist/components/UI/Flow/FlowBlockNode.js.map +1 -0
- package/dist/components/UI/Flow/FlowCanvas.d.ts +22 -0
- package/dist/components/UI/Flow/FlowCanvas.js +263 -0
- package/dist/components/UI/Flow/FlowCanvas.js.map +1 -0
- package/dist/components/UI/Flow/FlowConnectionEdge.d.ts +10 -0
- package/dist/components/UI/Flow/FlowConnectionEdge.js +54 -0
- package/dist/components/UI/Flow/FlowConnectionEdge.js.map +1 -0
- package/dist/components/UI/Flow/FlowEditorProvider.d.ts +17 -0
- package/dist/components/UI/Flow/FlowEditorProvider.js +27 -0
- package/dist/components/UI/Flow/FlowEditorProvider.js.map +1 -0
- package/dist/components/UI/Flow/FlowInspector.d.ts +11 -0
- package/dist/components/UI/Flow/FlowInspector.js +124 -0
- package/dist/components/UI/Flow/FlowInspector.js.map +1 -0
- package/dist/components/UI/Flow/FlowIssueList.d.ts +8 -0
- package/dist/components/UI/Flow/FlowIssueList.js +55 -0
- package/dist/components/UI/Flow/FlowIssueList.js.map +1 -0
- package/dist/components/UI/Flow/FlowLaneMap.d.ts +30 -0
- package/dist/components/UI/Flow/FlowLaneMap.js +206 -0
- package/dist/components/UI/Flow/FlowLaneMap.js.map +1 -0
- package/dist/components/UI/Flow/FlowNodeShell.d.ts +20 -0
- package/dist/components/UI/Flow/FlowNodeShell.js +81 -0
- package/dist/components/UI/Flow/FlowNodeShell.js.map +1 -0
- package/dist/components/UI/Flow/FlowPalette.d.ts +7 -0
- package/dist/components/UI/Flow/FlowPalette.js +90 -0
- package/dist/components/UI/Flow/FlowPalette.js.map +1 -0
- package/dist/components/UI/Flow/FlowResizablePanel.d.ts +20 -0
- package/dist/components/UI/Flow/FlowResizablePanel.js +117 -0
- package/dist/components/UI/Flow/FlowResizablePanel.js.map +1 -0
- package/dist/components/UI/Flow/FlowZoomControls.d.ts +12 -0
- package/dist/components/UI/Flow/FlowZoomControls.js +53 -0
- package/dist/components/UI/Flow/FlowZoomControls.js.map +1 -0
- package/dist/components/UI/Flow/flowBlockRegistry.d.ts +12 -0
- package/dist/components/UI/Flow/flowBlockRegistry.js +15 -0
- package/dist/components/UI/Flow/flowBlockRegistry.js.map +1 -0
- package/dist/components/UI/Flow/flowConstants.d.ts +2 -0
- package/dist/components/UI/Flow/flowConstants.js +5 -0
- package/dist/components/UI/Flow/flowConstants.js.map +1 -0
- package/dist/components/UI/Flow/flowEditorStore.d.ts +44 -0
- package/dist/components/UI/Flow/flowEditorStore.js +258 -0
- package/dist/components/UI/Flow/flowEditorStore.js.map +1 -0
- package/dist/components/UI/Flow/flowGraph.d.ts +32 -0
- package/dist/components/UI/Flow/flowGraph.js +156 -0
- package/dist/components/UI/Flow/flowGraph.js.map +1 -0
- package/dist/components/UI/Flow/flowTypes.d.ts +74 -0
- package/dist/components/UI/Flow/flowTypes.js +2 -0
- package/dist/components/UI/Flow/flowTypes.js.map +1 -0
- package/dist/components/UI/Flow/index.d.ts +14 -0
- package/dist/components/UI/Flow/index.js +43 -0
- package/dist/components/UI/Flow/index.js.map +1 -0
- package/dist/components/UI/Flow/useFlowLabels.d.ts +10 -0
- package/dist/components/UI/Flow/useFlowLabels.js +51 -0
- package/dist/components/UI/Flow/useFlowLabels.js.map +1 -0
- package/dist/locales/en/ui.json.d.ts +67 -0
- package/dist/locales/en/ui.json.js +3 -0
- package/dist/locales/en/ui.json.js.map +1 -1
- package/dist/locales/index.d.ts +134 -0
- package/dist/locales/pt/ui.json.d.ts +67 -0
- package/dist/locales/pt/ui.json.js +3 -0
- package/dist/locales/pt/ui.json.js.map +1 -1
- package/package.json +10 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { IFlowBlockRegistry } from './flowBlockRegistry';
|
|
3
|
+
import { IFlowEditorState, TFlowEditorStore } from './flowEditorStore';
|
|
4
|
+
import { IFlowGraph } from './flowTypes';
|
|
5
|
+
interface IFlowEditorProviderProps {
|
|
6
|
+
/** Read once, when the provider mounts. Load a different flow with `replaceGraph` or a new `key`. */
|
|
7
|
+
initialGraph: IFlowGraph;
|
|
8
|
+
registry: IFlowBlockRegistry;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/** Scopes one editor: its store, its block registry and the canvas viewport. */
|
|
12
|
+
declare const FlowEditorProvider: React.FC<IFlowEditorProviderProps>;
|
|
13
|
+
export declare const useFlowEditor: <TSelected>(selector: (state: IFlowEditorState) => TSelected) => TSelected;
|
|
14
|
+
/** The store itself, for reading state inside event handlers without subscribing. */
|
|
15
|
+
export declare const useFlowEditorStore: () => TFlowEditorStore;
|
|
16
|
+
export declare const useFlowBlockRegistry: () => IFlowBlockRegistry;
|
|
17
|
+
export default FlowEditorProvider;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useState, useMemo, useEffect, useContext } from "react";
|
|
3
|
+
import { useStore } from "zustand";
|
|
4
|
+
import { ReactFlowProvider } from "@xyflow/react";
|
|
5
|
+
import { createFlowEditorStore } from "./flowEditorStore.js";
|
|
6
|
+
const FlowEditorContext = createContext(null);
|
|
7
|
+
const FlowEditorProvider = ({ initialGraph, registry, children }) => {
|
|
8
|
+
const [store] = useState(() => createFlowEditorStore(initialGraph, registry));
|
|
9
|
+
const contextValue = useMemo(() => ({ store, registry }), [store, registry]);
|
|
10
|
+
useEffect(() => store.getState().setRegistry(registry), [store, registry]);
|
|
11
|
+
return /* @__PURE__ */ jsx(FlowEditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(ReactFlowProvider, { children }) });
|
|
12
|
+
};
|
|
13
|
+
const useFlowEditorContext = () => {
|
|
14
|
+
const context = useContext(FlowEditorContext);
|
|
15
|
+
if (!context) throw new Error("Flow editor components must be rendered inside FlowEditorProvider");
|
|
16
|
+
return context;
|
|
17
|
+
};
|
|
18
|
+
const useFlowEditor = (selector) => useStore(useFlowEditorContext().store, selector);
|
|
19
|
+
const useFlowEditorStore = () => useFlowEditorContext().store;
|
|
20
|
+
const useFlowBlockRegistry = () => useFlowEditorContext().registry;
|
|
21
|
+
export {
|
|
22
|
+
FlowEditorProvider as default,
|
|
23
|
+
useFlowBlockRegistry,
|
|
24
|
+
useFlowEditor,
|
|
25
|
+
useFlowEditorStore
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=FlowEditorProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowEditorProvider.js","sources":["../../../../src/components/UI/Flow/FlowEditorProvider.tsx"],"sourcesContent":["import { createContext, ReactNode, useContext, useEffect, useMemo, useState } from 'react';\nimport { useStore } from 'zustand';\n\nimport { ReactFlowProvider } from '@xyflow/react';\n\nimport type { IFlowBlockRegistry } from './flowBlockRegistry';\nimport { createFlowEditorStore, IFlowEditorState, TFlowEditorStore } from './flowEditorStore';\nimport type { IFlowGraph } from './flowTypes';\n\ninterface IFlowEditorContextValue {\n store: TFlowEditorStore;\n registry: IFlowBlockRegistry;\n}\n\nconst FlowEditorContext = createContext<IFlowEditorContextValue | null>(null);\n\ninterface IFlowEditorProviderProps {\n /** Read once, when the provider mounts. Load a different flow with `replaceGraph` or a new `key`. */\n initialGraph: IFlowGraph;\n registry: IFlowBlockRegistry;\n children: ReactNode;\n}\n\n/** Scopes one editor: its store, its block registry and the canvas viewport. */\nconst FlowEditorProvider: React.FC<IFlowEditorProviderProps> = ({ initialGraph, registry, children }) => {\n const [store] = useState(() => createFlowEditorStore(initialGraph, registry));\n const contextValue = useMemo(() => ({ store, registry }), [store, registry]);\n\n useEffect(() => store.getState().setRegistry(registry), [store, registry]);\n\n return (\n <FlowEditorContext.Provider value={contextValue}>\n <ReactFlowProvider>{children}</ReactFlowProvider>\n </FlowEditorContext.Provider>\n );\n};\n\nconst useFlowEditorContext = (): IFlowEditorContextValue => {\n const context = useContext(FlowEditorContext);\n if (!context) throw new Error('Flow editor components must be rendered inside FlowEditorProvider');\n return context;\n};\n\nexport const useFlowEditor = <TSelected,>(selector: (state: IFlowEditorState) => TSelected): TSelected =>\n useStore(useFlowEditorContext().store, selector);\n\n/** The store itself, for reading state inside event handlers without subscribing. */\nexport const useFlowEditorStore = (): TFlowEditorStore => useFlowEditorContext().store;\n\nexport const useFlowBlockRegistry = (): IFlowBlockRegistry => useFlowEditorContext().registry;\n\nexport default FlowEditorProvider;\n"],"names":[],"mappings":";;;;;AAcA,MAAM,oBAAoB,cAA8C,IAAI;AAU5E,MAAM,qBAAyD,CAAC,EAAE,cAAc,UAAU,eAAe;AACvG,QAAM,CAAC,KAAK,IAAI,SAAS,MAAM,sBAAsB,cAAc,QAAQ,CAAC;AAC5E,QAAM,eAAe,QAAQ,OAAO,EAAE,OAAO,aAAa,CAAC,OAAO,QAAQ,CAAC;AAE3E,YAAU,MAAM,MAAM,WAAW,YAAY,QAAQ,GAAG,CAAC,OAAO,QAAQ,CAAC;AAEzE,SACE,oBAAC,kBAAkB,UAAlB,EAA2B,OAAO,cACjC,UAAA,oBAAC,mBAAA,EAAmB,SAAA,CAAS,EAAA,CAC/B;AAEJ;AAEA,MAAM,uBAAuB,MAA+B;AAC1D,QAAM,UAAU,WAAW,iBAAiB;AAC5C,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,mEAAmE;AACjG,SAAO;AACT;AAEO,MAAM,gBAAgB,CAAa,aACxC,SAAS,qBAAA,EAAuB,OAAO,QAAQ;AAG1C,MAAM,qBAAqB,MAAwB,uBAAuB;AAE1E,MAAM,uBAAuB,MAA0B,uBAAuB;"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IFlowIssue } from './flowTypes';
|
|
2
|
+
interface IFlowInspectorProps {
|
|
3
|
+
issues?: Array<IFlowIssue>;
|
|
4
|
+
readOnly?: boolean;
|
|
5
|
+
/** A floating panel opens with the selection instead of showing an empty state. */
|
|
6
|
+
hideWhenEmpty?: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
/** The side panel of the selected block: its name, its own settings panel and its issues. */
|
|
10
|
+
declare const FlowInspector: React.FC<IFlowInspectorProps>;
|
|
11
|
+
export default FlowInspector;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
import ControlledButton from "../../Form/ControlledButton.js";
|
|
5
|
+
import ControlledInput from "../../Form/ControlledInput.js";
|
|
6
|
+
import ControlledTooltip from "../ControlledTooltip.js";
|
|
7
|
+
import { MaterialSymbol } from "../MaterialSymbol/index.js";
|
|
8
|
+
import NoDataFound from "../NoDataFound/index.js";
|
|
9
|
+
import { useFlowBlockRegistry, useFlowEditorStore, useFlowEditor } from "./FlowEditorProvider.js";
|
|
10
|
+
import { useFlowLabels } from "./useFlowLabels.js";
|
|
11
|
+
const FlowInspector = ({
|
|
12
|
+
issues = [],
|
|
13
|
+
readOnly = false,
|
|
14
|
+
hideWhenEmpty = false,
|
|
15
|
+
className
|
|
16
|
+
}) => {
|
|
17
|
+
const { t } = useTranslation("ui");
|
|
18
|
+
const labels = useFlowLabels();
|
|
19
|
+
const registry = useFlowBlockRegistry();
|
|
20
|
+
const store = useFlowEditorStore();
|
|
21
|
+
const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);
|
|
22
|
+
const selectedNode = useFlowEditor(
|
|
23
|
+
(state) => state.selectedNodeIds.length === 1 ? state.graph.nodes.find((node) => node.nodeId === state.selectedNodeIds[0]) : void 0
|
|
24
|
+
);
|
|
25
|
+
const shellClass = twMerge(
|
|
26
|
+
"flex h-full min-h-0 flex-col gap-y-3 rounded-xl border border-border bg-surface p-4",
|
|
27
|
+
className
|
|
28
|
+
);
|
|
29
|
+
if (!selectedNode || selectedNodeIds.length !== 1) {
|
|
30
|
+
if (hideWhenEmpty) return null;
|
|
31
|
+
return /* @__PURE__ */ jsx("aside", { className: shellClass, "aria-label": t("flow.inspector.title"), children: /* @__PURE__ */ jsx(NoDataFound, { size: "sm", variant: "waiting", message: t("flow.inspector.empty") }) });
|
|
32
|
+
}
|
|
33
|
+
const definition = registry.get(selectedNode.type);
|
|
34
|
+
const nodeIssues = (Array.isArray(issues) ? issues : []).filter((issue) => issue.nodeId === selectedNode.nodeId);
|
|
35
|
+
const ConfigPanel = definition?.ConfigPanel;
|
|
36
|
+
return /* @__PURE__ */ jsxs("aside", { className: shellClass, "aria-label": t("flow.inspector.title"), children: [
|
|
37
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
38
|
+
/* @__PURE__ */ jsx("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground-muted", children: /* @__PURE__ */ jsx(
|
|
39
|
+
MaterialSymbol,
|
|
40
|
+
{
|
|
41
|
+
icon: definition?.resolveIcon?.(selectedNode.config) ?? definition?.icon ?? "help",
|
|
42
|
+
size: 18,
|
|
43
|
+
"aria-hidden": true
|
|
44
|
+
}
|
|
45
|
+
) }),
|
|
46
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col", children: [
|
|
47
|
+
definition && /* @__PURE__ */ jsx("span", { className: "text-xxs font-semibold uppercase tracking-wide text-foreground-subtle", children: labels.categories[definition.category] }),
|
|
48
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: definition?.resolveLabel?.(selectedNode.config) ?? definition?.label ?? selectedNode.type })
|
|
49
|
+
] }),
|
|
50
|
+
/* @__PURE__ */ jsx(ControlledTooltip, { content: t("flow.inspector.close"), placement: "left", children: /* @__PURE__ */ jsx(
|
|
51
|
+
ControlledButton,
|
|
52
|
+
{
|
|
53
|
+
defaultHtmlButton: true,
|
|
54
|
+
onClick: () => store.getState().setSelection({ nodeIds: [], edgeIds: [] }),
|
|
55
|
+
className: "interactive ml-auto flex size-8 shrink-0 items-center justify-center rounded-lg text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
56
|
+
children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "close", size: 18, "aria-hidden": true })
|
|
57
|
+
}
|
|
58
|
+
) })
|
|
59
|
+
] }),
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
ControlledInput,
|
|
62
|
+
{
|
|
63
|
+
label: t("flow.inspector.label"),
|
|
64
|
+
value: selectedNode.label ?? "",
|
|
65
|
+
disabled: readOnly,
|
|
66
|
+
onChange: (event) => store.getState().updateNodeLabel(selectedNode.nodeId, event.target.value)
|
|
67
|
+
}
|
|
68
|
+
),
|
|
69
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-y-3 overflow-y-auto", children: [
|
|
70
|
+
ConfigPanel ? /* @__PURE__ */ jsx(
|
|
71
|
+
ConfigPanel,
|
|
72
|
+
{
|
|
73
|
+
config: selectedNode.config,
|
|
74
|
+
readOnly,
|
|
75
|
+
onChange: (next) => store.getState().updateNodeConfig(selectedNode.nodeId, next)
|
|
76
|
+
}
|
|
77
|
+
) : /* @__PURE__ */ jsx(NoDataFound, { compact: true, variant: "error", message: t("flow.inspector.unknownBlock") }),
|
|
78
|
+
nodeIssues.length > 0 && /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-y-1", children: nodeIssues.map((issue) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-x-2", children: [
|
|
79
|
+
/* @__PURE__ */ jsx(
|
|
80
|
+
MaterialSymbol,
|
|
81
|
+
{
|
|
82
|
+
icon: issue.severity === "error" ? "error" : "warning",
|
|
83
|
+
size: 16,
|
|
84
|
+
className: issue.severity === "error" ? "text-danger-emphasis" : "text-warning-emphasis",
|
|
85
|
+
"aria-hidden": true
|
|
86
|
+
}
|
|
87
|
+
),
|
|
88
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-foreground-muted", children: labels.issueMessage(issue) })
|
|
89
|
+
] }, `${issue.code}|${issue.edgeId ?? ""}`)) })
|
|
90
|
+
] }),
|
|
91
|
+
!readOnly && /* @__PURE__ */ jsxs("div", { className: "flex gap-x-2", children: [
|
|
92
|
+
definition?.category !== "trigger" && /* @__PURE__ */ jsxs(
|
|
93
|
+
ControlledButton,
|
|
94
|
+
{
|
|
95
|
+
size: "sm",
|
|
96
|
+
variant: "outlined",
|
|
97
|
+
tone: "neutral",
|
|
98
|
+
onClick: () => store.getState().duplicateSelected(),
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ jsx(MaterialSymbol, { icon: "content_copy", size: 16, "aria-hidden": true }),
|
|
101
|
+
t("flow.inspector.duplicate")
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
),
|
|
105
|
+
/* @__PURE__ */ jsxs(
|
|
106
|
+
ControlledButton,
|
|
107
|
+
{
|
|
108
|
+
size: "sm",
|
|
109
|
+
variant: "outlined",
|
|
110
|
+
tone: "danger",
|
|
111
|
+
onClick: () => store.getState().removeSelected(),
|
|
112
|
+
children: [
|
|
113
|
+
/* @__PURE__ */ jsx(MaterialSymbol, { icon: "delete", size: 16, "aria-hidden": true }),
|
|
114
|
+
t("flow.inspector.delete")
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
] })
|
|
119
|
+
] });
|
|
120
|
+
};
|
|
121
|
+
export {
|
|
122
|
+
FlowInspector as default
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=FlowInspector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowInspector.js","sources":["../../../../src/components/UI/Flow/FlowInspector.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledInput from '@/components/Form/ControlledInput';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport NoDataFound from '@/components/UI/NoDataFound';\n\nimport { useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport type { IFlowIssue } from './flowTypes';\nimport { useFlowLabels } from './useFlowLabels';\n\ninterface IFlowInspectorProps {\n issues?: Array<IFlowIssue>;\n readOnly?: boolean;\n /** A floating panel opens with the selection instead of showing an empty state. */\n hideWhenEmpty?: boolean;\n className?: string;\n}\n\n/** The side panel of the selected block: its name, its own settings panel and its issues. */\nconst FlowInspector: React.FC<IFlowInspectorProps> = ({\n issues = [],\n readOnly = false,\n hideWhenEmpty = false,\n className,\n}) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const registry = useFlowBlockRegistry();\n const store = useFlowEditorStore();\n const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);\n const selectedNode = useFlowEditor((state) =>\n state.selectedNodeIds.length === 1\n ? state.graph.nodes.find((node) => node.nodeId === state.selectedNodeIds[0])\n : undefined,\n );\n\n const shellClass = twMerge(\n 'flex h-full min-h-0 flex-col gap-y-3 rounded-xl border border-border bg-surface p-4',\n className,\n );\n\n if (!selectedNode || selectedNodeIds.length !== 1) {\n if (hideWhenEmpty) return null;\n return (\n <aside className={shellClass} aria-label={t('flow.inspector.title')}>\n <NoDataFound size=\"sm\" variant=\"waiting\" message={t('flow.inspector.empty')} />\n </aside>\n );\n }\n\n const definition = registry.get(selectedNode.type);\n const nodeIssues = (Array.isArray(issues) ? issues : []).filter((issue) => issue.nodeId === selectedNode.nodeId);\n const ConfigPanel = definition?.ConfigPanel;\n\n return (\n <aside className={shellClass} aria-label={t('flow.inspector.title')}>\n <div className=\"flex items-center gap-x-2\">\n <span className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground-muted\">\n <MaterialSymbol\n icon={definition?.resolveIcon?.(selectedNode.config) ?? definition?.icon ?? 'help'}\n size={18}\n aria-hidden\n />\n </span>\n <div className=\"flex min-w-0 flex-col\">\n {definition && (\n <span className=\"text-xxs font-semibold uppercase tracking-wide text-foreground-subtle\">\n {labels.categories[definition.category]}\n </span>\n )}\n <span className=\"truncate text-sm font-semibold text-foreground\">\n {definition?.resolveLabel?.(selectedNode.config) ?? definition?.label ?? selectedNode.type}\n </span>\n </div>\n <ControlledTooltip content={t('flow.inspector.close')} placement=\"left\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => store.getState().setSelection({ nodeIds: [], edgeIds: [] })}\n className=\"interactive ml-auto flex size-8 shrink-0 items-center justify-center rounded-lg text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MaterialSymbol icon=\"close\" size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n\n <ControlledInput\n label={t('flow.inspector.label')}\n value={selectedNode.label ?? ''}\n disabled={readOnly}\n onChange={(event) => store.getState().updateNodeLabel(selectedNode.nodeId, event.target.value)}\n />\n\n <div className=\"flex min-h-0 flex-1 flex-col gap-y-3 overflow-y-auto\">\n {ConfigPanel ? (\n <ConfigPanel\n config={selectedNode.config}\n readOnly={readOnly}\n onChange={(next) => store.getState().updateNodeConfig(selectedNode.nodeId, next)}\n />\n ) : (\n <NoDataFound compact variant=\"error\" message={t('flow.inspector.unknownBlock')} />\n )}\n\n {nodeIssues.length > 0 && (\n <ul className=\"flex flex-col gap-y-1\">\n {nodeIssues.map((issue) => (\n <li key={`${issue.code}|${issue.edgeId ?? ''}`} className=\"flex items-start gap-x-2\">\n <MaterialSymbol\n icon={issue.severity === 'error' ? 'error' : 'warning'}\n size={16}\n className={issue.severity === 'error' ? 'text-danger-emphasis' : 'text-warning-emphasis'}\n aria-hidden\n />\n <span className=\"text-xs text-foreground-muted\">{labels.issueMessage(issue)}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n {!readOnly && (\n <div className=\"flex gap-x-2\">\n {definition?.category !== 'trigger' && (\n <ControlledButton\n size=\"sm\"\n variant=\"outlined\"\n tone=\"neutral\"\n onClick={() => store.getState().duplicateSelected()}\n >\n <MaterialSymbol icon=\"content_copy\" size={16} aria-hidden />\n {t('flow.inspector.duplicate')}\n </ControlledButton>\n )}\n <ControlledButton\n size=\"sm\"\n variant=\"outlined\"\n tone=\"danger\"\n onClick={() => store.getState().removeSelected()}\n >\n <MaterialSymbol icon=\"delete\" size={16} aria-hidden />\n {t('flow.inspector.delete')}\n </ControlledButton>\n </div>\n )}\n </aside>\n );\n};\n\nexport default FlowInspector;\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IFlowIssue } from './flowTypes';
|
|
2
|
+
interface IFlowIssueListProps {
|
|
3
|
+
issues: Array<IFlowIssue>;
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
/** Every issue of the flow; clicking one selects and centers the block it points to. */
|
|
7
|
+
declare const FlowIssueList: React.FC<IFlowIssueListProps>;
|
|
8
|
+
export default FlowIssueList;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
import ControlledButton from "../../Form/ControlledButton.js";
|
|
5
|
+
import { MaterialSymbol } from "../MaterialSymbol/index.js";
|
|
6
|
+
import NoDataFound from "../NoDataFound/index.js";
|
|
7
|
+
import { useReactFlow } from "@xyflow/react";
|
|
8
|
+
import { useFlowEditorStore, useFlowEditor } from "./FlowEditorProvider.js";
|
|
9
|
+
import { useFlowLabels } from "./useFlowLabels.js";
|
|
10
|
+
const issueKey = (issue) => `${issue.code}|${issue.nodeId ?? ""}|${issue.edgeId ?? ""}`;
|
|
11
|
+
const FlowIssueList = ({ issues, className }) => {
|
|
12
|
+
const { t } = useTranslation("ui");
|
|
13
|
+
const labels = useFlowLabels();
|
|
14
|
+
const store = useFlowEditorStore();
|
|
15
|
+
const nodes = useFlowEditor((state) => state.graph.nodes);
|
|
16
|
+
const { setCenter } = useReactFlow();
|
|
17
|
+
const safeIssues = Array.isArray(issues) ? issues : [];
|
|
18
|
+
const uniqueIssues = safeIssues.filter(
|
|
19
|
+
(issue, index) => safeIssues.findIndex((other) => issueKey(other) === issueKey(issue)) === index
|
|
20
|
+
);
|
|
21
|
+
const focusNode = (nodeId) => {
|
|
22
|
+
const node = nodes.find((candidate) => candidate.nodeId === nodeId);
|
|
23
|
+
if (!node) return;
|
|
24
|
+
store.getState().setSelection({ nodeIds: [node.nodeId], edgeIds: [] });
|
|
25
|
+
void setCenter(node.position.x + 128, node.position.y + 48, { zoom: 1, duration: 200 });
|
|
26
|
+
};
|
|
27
|
+
return /* @__PURE__ */ jsxs("section", { className: twMerge("flex flex-col gap-y-2", className), children: [
|
|
28
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: t("flow.issues.title") }),
|
|
29
|
+
uniqueIssues.length === 0 ? /* @__PURE__ */ jsx(NoDataFound, { compact: true, variant: "success", message: t("flow.issues.none") }) : /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-y-1", children: uniqueIssues.map((issue) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
30
|
+
ControlledButton,
|
|
31
|
+
{
|
|
32
|
+
defaultHtmlButton: true,
|
|
33
|
+
disabled: !issue.nodeId,
|
|
34
|
+
onClick: () => focusNode(issue.nodeId),
|
|
35
|
+
className: "interactive flex w-full items-start gap-x-2 rounded-lg px-2 py-1.5 text-left hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:hover:bg-transparent",
|
|
36
|
+
children: [
|
|
37
|
+
/* @__PURE__ */ jsx(
|
|
38
|
+
MaterialSymbol,
|
|
39
|
+
{
|
|
40
|
+
icon: issue.severity === "error" ? "error" : "warning",
|
|
41
|
+
size: 16,
|
|
42
|
+
className: issue.severity === "error" ? "text-danger-emphasis" : "text-warning-emphasis",
|
|
43
|
+
"aria-hidden": true
|
|
44
|
+
}
|
|
45
|
+
),
|
|
46
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-foreground-muted", children: labels.issueMessage(issue) })
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
) }, issueKey(issue))) })
|
|
50
|
+
] });
|
|
51
|
+
};
|
|
52
|
+
export {
|
|
53
|
+
FlowIssueList as default
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=FlowIssueList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowIssueList.js","sources":["../../../../src/components/UI/Flow/FlowIssueList.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport NoDataFound from '@/components/UI/NoDataFound';\nimport { useReactFlow } from '@xyflow/react';\n\nimport { useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport type { IFlowIssue } from './flowTypes';\nimport { useFlowLabels } from './useFlowLabels';\n\ninterface IFlowIssueListProps {\n issues: Array<IFlowIssue>;\n className?: string;\n}\n\nconst issueKey = (issue: IFlowIssue): string => `${issue.code}|${issue.nodeId ?? ''}|${issue.edgeId ?? ''}`;\n\n/** Every issue of the flow; clicking one selects and centers the block it points to. */\nconst FlowIssueList: React.FC<IFlowIssueListProps> = ({ issues, className }) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const store = useFlowEditorStore();\n const nodes = useFlowEditor((state) => state.graph.nodes);\n const { setCenter } = useReactFlow();\n const safeIssues = Array.isArray(issues) ? issues : [];\n const uniqueIssues = safeIssues.filter(\n (issue, index) => safeIssues.findIndex((other) => issueKey(other) === issueKey(issue)) === index,\n );\n\n const focusNode = (nodeId: string | undefined) => {\n const node = nodes.find((candidate) => candidate.nodeId === nodeId);\n if (!node) return;\n store.getState().setSelection({ nodeIds: [node.nodeId], edgeIds: [] });\n void setCenter(node.position.x + 128, node.position.y + 48, { zoom: 1, duration: 200 });\n };\n\n return (\n <section className={twMerge('flex flex-col gap-y-2', className)}>\n <span className=\"text-sm font-semibold text-foreground\">{t('flow.issues.title')}</span>\n {uniqueIssues.length === 0 ? (\n <NoDataFound compact variant=\"success\" message={t('flow.issues.none')} />\n ) : (\n <ul className=\"flex flex-col gap-y-1\">\n {uniqueIssues.map((issue) => (\n <li key={issueKey(issue)}>\n <ControlledButton\n defaultHtmlButton\n disabled={!issue.nodeId}\n onClick={() => focusNode(issue.nodeId)}\n className=\"interactive flex w-full items-start gap-x-2 rounded-lg px-2 py-1.5 text-left hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:hover:bg-transparent\"\n >\n <MaterialSymbol\n icon={issue.severity === 'error' ? 'error' : 'warning'}\n size={16}\n className={issue.severity === 'error' ? 'text-danger-emphasis' : 'text-warning-emphasis'}\n aria-hidden\n />\n <span className=\"text-xs text-foreground-muted\">{labels.issueMessage(issue)}</span>\n </ControlledButton>\n </li>\n ))}\n </ul>\n )}\n </section>\n );\n};\n\nexport default FlowIssueList;\n"],"names":[],"mappings":";;;;;;;;;AAiBA,MAAM,WAAW,CAAC,UAA8B,GAAG,MAAM,IAAI,IAAI,MAAM,UAAU,EAAE,IAAI,MAAM,UAAU,EAAE;AAGzG,MAAM,gBAA+C,CAAC,EAAE,QAAQ,gBAAgB;AAC9E,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,SAAS,cAAA;AACf,QAAM,QAAQ,mBAAA;AACd,QAAM,QAAQ,cAAc,CAAC,UAAU,MAAM,MAAM,KAAK;AACxD,QAAM,EAAE,UAAA,IAAc,aAAA;AACtB,QAAM,aAAa,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAA;AACpD,QAAM,eAAe,WAAW;AAAA,IAC9B,CAAC,OAAO,UAAU,WAAW,UAAU,CAAC,UAAU,SAAS,KAAK,MAAM,SAAS,KAAK,CAAC,MAAM;AAAA,EAAA;AAG7F,QAAM,YAAY,CAAC,WAA+B;AAChD,UAAM,OAAO,MAAM,KAAK,CAAC,cAAc,UAAU,WAAW,MAAM;AAClE,QAAI,CAAC,KAAM;AACX,UAAM,SAAA,EAAW,aAAa,EAAE,SAAS,CAAC,KAAK,MAAM,GAAG,SAAS,CAAA,GAAI;AACrE,SAAK,UAAU,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,EAAE,MAAM,GAAG,UAAU,KAAK;AAAA,EACxF;AAEA,8BACG,WAAA,EAAQ,WAAW,QAAQ,yBAAyB,SAAS,GAC5D,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAK,WAAU,yCAAyC,UAAA,EAAE,mBAAmB,GAAE;AAAA,IAC/E,aAAa,WAAW,IACvB,oBAAC,eAAY,SAAO,MAAC,SAAQ,WAAU,SAAS,EAAE,kBAAkB,EAAA,CAAG,IAEvE,oBAAC,MAAA,EAAG,WAAU,yBACX,uBAAa,IAAI,CAAC,UACjB,oBAAC,MAAA,EACC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,mBAAiB;AAAA,QACjB,UAAU,CAAC,MAAM;AAAA,QACjB,SAAS,MAAM,UAAU,MAAM,MAAM;AAAA,QACrC,WAAU;AAAA,QAEV,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAM,MAAM,aAAa,UAAU,UAAU;AAAA,cAC7C,MAAM;AAAA,cACN,WAAW,MAAM,aAAa,UAAU,yBAAyB;AAAA,cACjE,eAAW;AAAA,YAAA;AAAA,UAAA;AAAA,8BAEZ,QAAA,EAAK,WAAU,iCAAiC,UAAA,OAAO,aAAa,KAAK,EAAA,CAAE;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,EAC9E,GAdO,SAAS,KAAK,CAevB,CACD,EAAA,CACH;AAAA,EAAA,GAEJ;AAEJ;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
export interface IFlowLane {
|
|
3
|
+
laneId: string;
|
|
4
|
+
title: string;
|
|
5
|
+
subtitle?: string;
|
|
6
|
+
/** Counters or chips under the title (rules acting on entry, stage requirements). */
|
|
7
|
+
badges?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export type TFlowArcTone = 'default' | 'warning' | 'danger';
|
|
10
|
+
export interface IFlowArc {
|
|
11
|
+
arcId: string;
|
|
12
|
+
sourceLaneId: string;
|
|
13
|
+
targetLaneId: string;
|
|
14
|
+
label: string;
|
|
15
|
+
/** `over` arcs above the lanes, `under` below them; the host decides what each side means. */
|
|
16
|
+
side: 'over' | 'under';
|
|
17
|
+
tone?: TFlowArcTone;
|
|
18
|
+
dashed?: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface IFlowLaneMapProps {
|
|
21
|
+
lanes: Array<IFlowLane>;
|
|
22
|
+
arcs: Array<IFlowArc>;
|
|
23
|
+
onArcClick?: (arcId: string) => void;
|
|
24
|
+
onLaneClick?: (laneId: string) => void;
|
|
25
|
+
selectedArcId?: string | null;
|
|
26
|
+
ariaLabel?: string;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
declare const FlowLaneMap: React.FC<IFlowLaneMapProps>;
|
|
30
|
+
export default FlowLaneMap;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
import { twMerge } from "tailwind-merge";
|
|
6
|
+
import ControlledButton from "../../Form/ControlledButton.js";
|
|
7
|
+
import { ReactFlowProvider, MarkerType, ReactFlow, Background, BackgroundVariant, BaseEdge, EdgeLabelRenderer, Handle, Position } from "@xyflow/react";
|
|
8
|
+
import OutlinedChip from "../Chips/OutlinedChip.js";
|
|
9
|
+
import FlowZoomControls from "./FlowZoomControls.js";
|
|
10
|
+
import "@xyflow/react/dist/base.css";
|
|
11
|
+
/* empty css */
|
|
12
|
+
const LANE_WIDTH = 192;
|
|
13
|
+
const LANE_HEIGHT = 168;
|
|
14
|
+
const LANE_GAP = 80;
|
|
15
|
+
const HIDDEN_HANDLE_CLASS = "!size-px !min-h-0 !min-w-0 !border-0 !bg-transparent";
|
|
16
|
+
const LaneNode = ({ data }) => {
|
|
17
|
+
const { lane, onLaneClick } = data;
|
|
18
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
19
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: lane.title }),
|
|
20
|
+
lane.subtitle && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-foreground-subtle", children: lane.subtitle }),
|
|
21
|
+
lane.badges && /* @__PURE__ */ jsx("div", { className: "mt-auto flex flex-wrap gap-1", children: lane.badges })
|
|
22
|
+
] });
|
|
23
|
+
return /* @__PURE__ */ jsxs("div", { style: { width: LANE_WIDTH, height: LANE_HEIGHT }, className: "relative", children: [
|
|
24
|
+
/* @__PURE__ */ jsx(
|
|
25
|
+
Handle,
|
|
26
|
+
{
|
|
27
|
+
id: "over-out",
|
|
28
|
+
type: "source",
|
|
29
|
+
position: Position.Top,
|
|
30
|
+
isConnectable: false,
|
|
31
|
+
className: HIDDEN_HANDLE_CLASS
|
|
32
|
+
}
|
|
33
|
+
),
|
|
34
|
+
/* @__PURE__ */ jsx(
|
|
35
|
+
Handle,
|
|
36
|
+
{
|
|
37
|
+
id: "over-in",
|
|
38
|
+
type: "target",
|
|
39
|
+
position: Position.Top,
|
|
40
|
+
isConnectable: false,
|
|
41
|
+
className: HIDDEN_HANDLE_CLASS
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
onLaneClick ? /* @__PURE__ */ jsx(
|
|
45
|
+
ControlledButton,
|
|
46
|
+
{
|
|
47
|
+
defaultHtmlButton: true,
|
|
48
|
+
onClick: () => onLaneClick(lane.laneId),
|
|
49
|
+
className: "interactive flex size-full flex-col gap-y-1 rounded-xl border border-border bg-surface p-3 text-left shadow-sm hover:border-border-emphasis focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
50
|
+
children: content
|
|
51
|
+
}
|
|
52
|
+
) : /* @__PURE__ */ jsx("div", { className: "flex size-full flex-col gap-y-1 rounded-xl border border-border bg-surface p-3 shadow-sm", children: content }),
|
|
53
|
+
/* @__PURE__ */ jsx(
|
|
54
|
+
Handle,
|
|
55
|
+
{
|
|
56
|
+
id: "under-out",
|
|
57
|
+
type: "source",
|
|
58
|
+
position: Position.Bottom,
|
|
59
|
+
isConnectable: false,
|
|
60
|
+
className: HIDDEN_HANDLE_CLASS
|
|
61
|
+
}
|
|
62
|
+
),
|
|
63
|
+
/* @__PURE__ */ jsx(
|
|
64
|
+
Handle,
|
|
65
|
+
{
|
|
66
|
+
id: "under-in",
|
|
67
|
+
type: "target",
|
|
68
|
+
position: Position.Bottom,
|
|
69
|
+
isConnectable: false,
|
|
70
|
+
className: HIDDEN_HANDLE_CLASS
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
] });
|
|
74
|
+
};
|
|
75
|
+
const ArcEdge = ({
|
|
76
|
+
id,
|
|
77
|
+
sourceX,
|
|
78
|
+
sourceY,
|
|
79
|
+
targetX,
|
|
80
|
+
targetY,
|
|
81
|
+
data,
|
|
82
|
+
markerEnd,
|
|
83
|
+
selected
|
|
84
|
+
}) => {
|
|
85
|
+
const arc = data?.arc;
|
|
86
|
+
const isOver = arc?.side !== "under";
|
|
87
|
+
const isBackward = targetX < sourceX;
|
|
88
|
+
const lift = 40 + Math.abs(targetX - sourceX) * 0.2 + (isBackward ? 32 : 0);
|
|
89
|
+
const controlY = isOver ? Math.min(sourceY, targetY) - lift * 1.3 : Math.max(sourceY, targetY) + lift * 1.3;
|
|
90
|
+
const path = `M ${sourceX},${sourceY} C ${sourceX},${controlY} ${targetX},${controlY} ${targetX},${targetY}`;
|
|
91
|
+
const labelX = (sourceX + targetX) / 2;
|
|
92
|
+
const labelY = 0.125 * (sourceY + targetY) + 0.75 * controlY;
|
|
93
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
BaseEdge,
|
|
96
|
+
{
|
|
97
|
+
id,
|
|
98
|
+
path,
|
|
99
|
+
markerEnd,
|
|
100
|
+
interactionWidth: 16,
|
|
101
|
+
className: classNames("umode-flow-arc", {
|
|
102
|
+
"umode-flow-arc--warning": arc?.tone === "warning",
|
|
103
|
+
"umode-flow-arc--danger": arc?.tone === "danger",
|
|
104
|
+
"umode-flow-arc--dashed": arc?.dashed,
|
|
105
|
+
"umode-flow-arc--selected": selected
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
),
|
|
109
|
+
arc?.label && /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx(
|
|
110
|
+
"div",
|
|
111
|
+
{
|
|
112
|
+
className: "nodrag nopan pointer-events-none absolute whitespace-nowrap",
|
|
113
|
+
style: { transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` },
|
|
114
|
+
children: /* @__PURE__ */ jsx(
|
|
115
|
+
OutlinedChip,
|
|
116
|
+
{
|
|
117
|
+
label: arc.label,
|
|
118
|
+
tone: arc.tone === "warning" ? "warning" : arc.tone === "danger" ? "danger" : "neutral",
|
|
119
|
+
variant: "outlined",
|
|
120
|
+
size: "xs",
|
|
121
|
+
preserveLabelCase: true
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
) })
|
|
126
|
+
] });
|
|
127
|
+
};
|
|
128
|
+
const ATTRIBUTION_POSITION = "bottom-left";
|
|
129
|
+
const NODE_TYPES = { flowLane: LaneNode };
|
|
130
|
+
const EDGE_TYPES = { flowArc: ArcEdge };
|
|
131
|
+
const FlowLaneMapContent = ({
|
|
132
|
+
lanes,
|
|
133
|
+
arcs,
|
|
134
|
+
onArcClick,
|
|
135
|
+
onLaneClick,
|
|
136
|
+
selectedArcId,
|
|
137
|
+
ariaLabel,
|
|
138
|
+
className
|
|
139
|
+
}) => {
|
|
140
|
+
const { t } = useTranslation("ui");
|
|
141
|
+
const safeLanes = useMemo(() => Array.isArray(lanes) ? lanes : [], [lanes]);
|
|
142
|
+
const safeArcs = useMemo(() => Array.isArray(arcs) ? arcs : [], [arcs]);
|
|
143
|
+
const nodes = useMemo(
|
|
144
|
+
() => safeLanes.map((lane, index) => ({
|
|
145
|
+
id: lane.laneId,
|
|
146
|
+
type: "flowLane",
|
|
147
|
+
position: { x: index * (LANE_WIDTH + LANE_GAP), y: 0 },
|
|
148
|
+
draggable: false,
|
|
149
|
+
selectable: false,
|
|
150
|
+
data: { lane, onLaneClick }
|
|
151
|
+
})),
|
|
152
|
+
[safeLanes, onLaneClick]
|
|
153
|
+
);
|
|
154
|
+
const laneIds = useMemo(() => new Set(safeLanes.map((lane) => lane.laneId)), [safeLanes]);
|
|
155
|
+
const edges = useMemo(
|
|
156
|
+
() => safeArcs.filter(
|
|
157
|
+
(arc) => laneIds.has(arc.sourceLaneId) && laneIds.has(arc.targetLaneId) && arc.sourceLaneId !== arc.targetLaneId
|
|
158
|
+
).map((arc) => ({
|
|
159
|
+
id: arc.arcId,
|
|
160
|
+
type: "flowArc",
|
|
161
|
+
source: arc.sourceLaneId,
|
|
162
|
+
sourceHandle: `${arc.side}-out`,
|
|
163
|
+
target: arc.targetLaneId,
|
|
164
|
+
targetHandle: `${arc.side}-in`,
|
|
165
|
+
selected: arc.arcId === selectedArcId,
|
|
166
|
+
markerEnd: { type: MarkerType.ArrowClosed, width: 14, height: 14 },
|
|
167
|
+
data: { arc }
|
|
168
|
+
})),
|
|
169
|
+
[safeArcs, laneIds, selectedArcId]
|
|
170
|
+
);
|
|
171
|
+
return /* @__PURE__ */ jsx(
|
|
172
|
+
"div",
|
|
173
|
+
{
|
|
174
|
+
role: "region",
|
|
175
|
+
"aria-label": ariaLabel ?? t("flow.laneMap.label"),
|
|
176
|
+
className: twMerge("umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas", className),
|
|
177
|
+
children: /* @__PURE__ */ jsxs(
|
|
178
|
+
ReactFlow,
|
|
179
|
+
{
|
|
180
|
+
nodes,
|
|
181
|
+
edges,
|
|
182
|
+
nodeTypes: NODE_TYPES,
|
|
183
|
+
edgeTypes: EDGE_TYPES,
|
|
184
|
+
nodesDraggable: false,
|
|
185
|
+
nodesConnectable: false,
|
|
186
|
+
deleteKeyCode: null,
|
|
187
|
+
onEdgeClick: (_event, edge) => onArcClick?.(edge.id),
|
|
188
|
+
minZoom: 0.25,
|
|
189
|
+
maxZoom: 1.5,
|
|
190
|
+
fitView: true,
|
|
191
|
+
fitViewOptions: { padding: 0.15, minZoom: 0.75 },
|
|
192
|
+
attributionPosition: ATTRIBUTION_POSITION,
|
|
193
|
+
children: [
|
|
194
|
+
/* @__PURE__ */ jsx(Background, { variant: BackgroundVariant.Dots, gap: 16, size: 1 }),
|
|
195
|
+
/* @__PURE__ */ jsx(FlowZoomControls, {})
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
};
|
|
202
|
+
const FlowLaneMap = (props) => /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(FlowLaneMapContent, { ...props }) });
|
|
203
|
+
export {
|
|
204
|
+
FlowLaneMap as default
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=FlowLaneMap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowLaneMap.js","sources":["../../../../src/components/UI/Flow/FlowLaneMap.tsx"],"sourcesContent":["import { ReactNode, useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport {\n Background,\n BackgroundVariant,\n BaseEdge,\n type Edge,\n EdgeLabelRenderer,\n type EdgeProps,\n Handle,\n MarkerType,\n type Node,\n type NodeProps,\n type PanelPosition,\n Position,\n ReactFlow,\n ReactFlowProvider,\n} from '@xyflow/react';\n\nimport OutlinedChip from '../Chips/OutlinedChip';\n\nimport FlowZoomControls from './FlowZoomControls';\n\nimport '@xyflow/react/dist/base.css';\nimport '../../../styles/flow.css';\n\nexport interface IFlowLane {\n laneId: string;\n title: string;\n subtitle?: string;\n /** Counters or chips under the title (rules acting on entry, stage requirements). */\n badges?: ReactNode;\n}\n\nexport type TFlowArcTone = 'default' | 'warning' | 'danger';\n\nexport interface IFlowArc {\n arcId: string;\n sourceLaneId: string;\n targetLaneId: string;\n label: string;\n /** `over` arcs above the lanes, `under` below them; the host decides what each side means. */\n side: 'over' | 'under';\n tone?: TFlowArcTone;\n dashed?: boolean;\n}\n\ninterface IFlowLaneMapProps {\n lanes: Array<IFlowLane>;\n arcs: Array<IFlowArc>;\n onArcClick?: (arcId: string) => void;\n onLaneClick?: (laneId: string) => void;\n selectedArcId?: string | null;\n ariaLabel?: string;\n className?: string;\n}\n\nconst LANE_WIDTH = 192;\nconst LANE_HEIGHT = 168;\nconst LANE_GAP = 80;\n\ninterface ILaneNodeData extends Record<string, unknown> {\n lane: IFlowLane;\n onLaneClick?: (laneId: string) => void;\n}\n\ninterface IArcEdgeData extends Record<string, unknown> {\n arc: IFlowArc;\n}\n\ntype TLaneNode = Node<ILaneNodeData, 'flowLane'>;\ntype TArcEdge = Edge<IArcEdgeData, 'flowArc'>;\n\nconst HIDDEN_HANDLE_CLASS = '!size-px !min-h-0 !min-w-0 !border-0 !bg-transparent';\n\nconst LaneNode: React.FC<NodeProps<TLaneNode>> = ({ data }) => {\n const { lane, onLaneClick } = data;\n const content = (\n <>\n <span className=\"truncate text-sm font-semibold text-foreground\">{lane.title}</span>\n {lane.subtitle && <span className=\"truncate text-xs text-foreground-subtle\">{lane.subtitle}</span>}\n {lane.badges && <div className=\"mt-auto flex flex-wrap gap-1\">{lane.badges}</div>}\n </>\n );\n\n return (\n <div style={{ width: LANE_WIDTH, height: LANE_HEIGHT }} className=\"relative\">\n <Handle\n id=\"over-out\"\n type=\"source\"\n position={Position.Top}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n <Handle\n id=\"over-in\"\n type=\"target\"\n position={Position.Top}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n {onLaneClick ? (\n <ControlledButton\n defaultHtmlButton\n onClick={() => onLaneClick(lane.laneId)}\n className=\"interactive flex size-full flex-col gap-y-1 rounded-xl border border-border bg-surface p-3 text-left shadow-sm hover:border-border-emphasis focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {content}\n </ControlledButton>\n ) : (\n <div className=\"flex size-full flex-col gap-y-1 rounded-xl border border-border bg-surface p-3 shadow-sm\">\n {content}\n </div>\n )}\n <Handle\n id=\"under-out\"\n type=\"source\"\n position={Position.Bottom}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n <Handle\n id=\"under-in\"\n type=\"target\"\n position={Position.Bottom}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n </div>\n );\n};\n\n/** A curve between two lane edges whose height grows with the distance, so long arcs clear the short ones. */\nconst ArcEdge: React.FC<EdgeProps<TArcEdge>> = ({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n data,\n markerEnd,\n selected,\n}) => {\n const arc = data?.arc;\n const isOver = arc?.side !== 'under';\n const isBackward = targetX < sourceX;\n const lift = 40 + Math.abs(targetX - sourceX) * 0.2 + (isBackward ? 32 : 0);\n const controlY = isOver ? Math.min(sourceY, targetY) - lift * 1.3 : Math.max(sourceY, targetY) + lift * 1.3;\n const path = `M ${sourceX},${sourceY} C ${sourceX},${controlY} ${targetX},${controlY} ${targetX},${targetY}`;\n const labelX = (sourceX + targetX) / 2;\n const labelY = 0.125 * (sourceY + targetY) + 0.75 * controlY;\n\n return (\n <>\n <BaseEdge\n id={id}\n path={path}\n markerEnd={markerEnd}\n interactionWidth={16}\n className={classNames('umode-flow-arc', {\n 'umode-flow-arc--warning': arc?.tone === 'warning',\n 'umode-flow-arc--danger': arc?.tone === 'danger',\n 'umode-flow-arc--dashed': arc?.dashed,\n 'umode-flow-arc--selected': selected,\n })}\n />\n {arc?.label && (\n <EdgeLabelRenderer>\n <div\n className=\"nodrag nopan pointer-events-none absolute whitespace-nowrap\"\n style={{ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` }}\n >\n <OutlinedChip\n label={arc.label}\n tone={arc.tone === 'warning' ? 'warning' : arc.tone === 'danger' ? 'danger' : 'neutral'}\n variant=\"outlined\"\n size=\"xs\"\n preserveLabelCase\n />\n </div>\n </EdgeLabelRenderer>\n )}\n </>\n );\n};\n\nconst ATTRIBUTION_POSITION: PanelPosition = 'bottom-left';\nconst NODE_TYPES = { flowLane: LaneNode };\nconst EDGE_TYPES = { flowArc: ArcEdge };\n\n/**\n * A read-only map of fixed columns joined by arcs: the shape of a pipeline seen from\n * above. Lanes keep the order they are given, so no graph layout is needed.\n */\nconst FlowLaneMapContent: React.FC<IFlowLaneMapProps> = ({\n lanes,\n arcs,\n onArcClick,\n onLaneClick,\n selectedArcId,\n ariaLabel,\n className,\n}) => {\n const { t } = useTranslation('ui');\n const safeLanes = useMemo(() => (Array.isArray(lanes) ? lanes : []), [lanes]);\n const safeArcs = useMemo(() => (Array.isArray(arcs) ? arcs : []), [arcs]);\n\n const nodes = useMemo<Array<TLaneNode>>(\n () =>\n safeLanes.map((lane, index) => ({\n id: lane.laneId,\n type: 'flowLane',\n position: { x: index * (LANE_WIDTH + LANE_GAP), y: 0 },\n draggable: false,\n selectable: false,\n data: { lane, onLaneClick },\n })),\n [safeLanes, onLaneClick],\n );\n\n const laneIds = useMemo(() => new Set(safeLanes.map((lane) => lane.laneId)), [safeLanes]);\n\n const edges = useMemo<Array<TArcEdge>>(\n () =>\n safeArcs\n .filter(\n (arc) =>\n laneIds.has(arc.sourceLaneId) && laneIds.has(arc.targetLaneId) && arc.sourceLaneId !== arc.targetLaneId,\n )\n .map((arc) => ({\n id: arc.arcId,\n type: 'flowArc',\n source: arc.sourceLaneId,\n sourceHandle: `${arc.side}-out`,\n target: arc.targetLaneId,\n targetHandle: `${arc.side}-in`,\n selected: arc.arcId === selectedArcId,\n markerEnd: { type: MarkerType.ArrowClosed, width: 14, height: 14 },\n data: { arc },\n })),\n [safeArcs, laneIds, selectedArcId],\n );\n\n return (\n <div\n role=\"region\"\n aria-label={ariaLabel ?? t('flow.laneMap.label')}\n className={twMerge('umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas', className)}\n >\n <ReactFlow<TLaneNode, TArcEdge>\n nodes={nodes}\n edges={edges}\n nodeTypes={NODE_TYPES}\n edgeTypes={EDGE_TYPES}\n nodesDraggable={false}\n nodesConnectable={false}\n deleteKeyCode={null}\n onEdgeClick={(_event, edge) => onArcClick?.(edge.id)}\n minZoom={0.25}\n maxZoom={1.5}\n fitView\n fitViewOptions={{ padding: 0.15, minZoom: 0.75 }}\n attributionPosition={ATTRIBUTION_POSITION}\n >\n <Background variant={BackgroundVariant.Dots} gap={16} size={1} />\n <FlowZoomControls />\n </ReactFlow>\n </div>\n );\n};\n\nconst FlowLaneMap: React.FC<IFlowLaneMapProps> = (props) => (\n <ReactFlowProvider>\n <FlowLaneMapContent {...props} />\n </ReactFlowProvider>\n);\n\nexport default FlowLaneMap;\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { SymbolCodepoints } from '../MaterialSymbol';
|
|
3
|
+
import { TFlowIssueSeverity, TFlowRunStepStatus } from './flowTypes';
|
|
4
|
+
export interface IFlowNodeShellProps {
|
|
5
|
+
icon: SymbolCodepoints;
|
|
6
|
+
title: string;
|
|
7
|
+
summary?: ReactNode;
|
|
8
|
+
categoryLabel?: string;
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
issueSeverity?: TFlowIssueSeverity;
|
|
12
|
+
issueCount?: number;
|
|
13
|
+
runStatus?: TFlowRunStepStatus;
|
|
14
|
+
/** The ports, rendered by the canvas at the block's edges. */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
/** The look of one block: icon, title, a one-line summary and its status badges. */
|
|
19
|
+
declare const FlowNodeShell: React.FC<IFlowNodeShellProps>;
|
|
20
|
+
export default FlowNodeShell;
|