@ultrst-armada/floks 0.0.0-stage → 1.0.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/README.md +2 -3
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +67 -4
- package/packages/canvas-ui/dist/canvas/index.d.ts +54 -0
- package/packages/canvas-ui/dist/canvas/index.js +12 -0
- package/packages/canvas-ui/dist/chunk-2GLR35UM.js +391 -0
- package/packages/canvas-ui/dist/chunk-34YRDZPJ.js +189 -0
- package/packages/canvas-ui/dist/chunk-6VB74RCH.js +147 -0
- package/packages/canvas-ui/dist/chunk-QTTWWBNN.js +114 -0
- package/packages/canvas-ui/dist/chunk-QWQ4PCXQ.js +718 -0
- package/packages/canvas-ui/dist/chunk-SA3JADUG.js +160 -0
- package/packages/canvas-ui/dist/chunk-UEIMM6PV.js +508 -0
- package/packages/canvas-ui/dist/dock/index.d.ts +37 -0
- package/packages/canvas-ui/dist/dock/index.js +10 -0
- package/packages/canvas-ui/dist/editor/index.d.ts +44 -0
- package/packages/canvas-ui/dist/editor/index.js +10 -0
- package/packages/canvas-ui/dist/graph-2d/index.d.ts +270 -0
- package/packages/canvas-ui/dist/graph-2d/index.js +50 -0
- package/packages/canvas-ui/dist/index.d.ts +176 -0
- package/packages/canvas-ui/dist/index.js +1171 -0
- package/packages/canvas-ui/dist/predictive/index.d.ts +74 -0
- package/packages/canvas-ui/dist/predictive/index.js +13 -0
- package/packages/canvas-ui/dist/tile-menu/index.d.ts +45 -0
- package/packages/canvas-ui/dist/tile-menu/index.js +12 -0
- package/packages/canvas-ui/dist/types-D3RRfQUo.d.ts +46 -0
- package/packages/canvas-ui/floks.manifest.json +35 -0
- package/packages/canvas-ui/package.json +82 -0
- package/packages/ui/dist/BottomStrip-DYhAUQR4.d.ts +12 -0
- package/packages/ui/dist/chunk-5ZMXFCD4.js +0 -0
- package/packages/ui/dist/chunk-62ITRDJX.js +141 -0
- package/packages/ui/dist/chunk-A2CDQISJ.js +32 -0
- package/packages/ui/dist/chunk-C32CB5WO.js +1161 -0
- package/packages/ui/dist/chunk-C3L53YN4.js +372 -0
- package/packages/ui/dist/chunk-DABYTYOU.js +351 -0
- package/packages/ui/dist/chunk-DZJ3NSCX.js +665 -0
- package/packages/ui/dist/chunk-ELZOBTWX.js +562 -0
- package/packages/ui/dist/chunk-GKP6KHTI.js +75 -0
- package/packages/ui/dist/chunk-GWW3BGOB.js +238 -0
- package/packages/ui/dist/chunk-KIFQA7L6.js +167 -0
- package/packages/ui/dist/chunk-OHISLM3Z.js +703 -0
- package/packages/ui/dist/chunk-OR5ZNJI4.js +62 -0
- package/packages/ui/dist/chunk-QWXEW7WH.js +238 -0
- package/packages/ui/dist/chunk-RLQ2ALJG.js +182 -0
- package/packages/ui/dist/chunk-S227WJ5E.js +152 -0
- package/packages/ui/dist/chunk-SLJBDDSN.js +0 -0
- package/packages/ui/dist/chunk-TXKLROFC.js +370 -0
- package/packages/ui/dist/chunk-UOR4P6PO.js +32 -0
- package/packages/ui/dist/chunk-W42SJGOJ.js +19 -0
- package/packages/ui/dist/chunk-XXIH2HWI.js +297 -0
- package/packages/ui/dist/chunk-ZQK5I5ZL.js +277 -0
- package/packages/ui/dist/components/auth/index.css +183 -0
- package/packages/ui/dist/components/auth/index.d.ts +10 -0
- package/packages/ui/dist/components/auth/index.js +8 -0
- package/packages/ui/dist/components/base/index.css +183 -0
- package/packages/ui/dist/components/base/index.d.ts +74 -0
- package/packages/ui/dist/components/base/index.js +33 -0
- package/packages/ui/dist/components/bottom-strip/index.css +183 -0
- package/packages/ui/dist/components/bottom-strip/index.d.ts +67 -0
- package/packages/ui/dist/components/bottom-strip/index.js +22 -0
- package/packages/ui/dist/components/composite/index.css +273 -0
- package/packages/ui/dist/components/composite/index.d.ts +104 -0
- package/packages/ui/dist/components/composite/index.js +33 -0
- package/packages/ui/dist/components/containers/index.css +404 -0
- package/packages/ui/dist/components/containers/index.d.ts +93 -0
- package/packages/ui/dist/components/containers/index.js +35 -0
- package/packages/ui/dist/components/layout/index.css +246 -0
- package/packages/ui/dist/components/layout/index.d.ts +53 -0
- package/packages/ui/dist/components/layout/index.js +16 -0
- package/packages/ui/dist/components/settings/index.css +183 -0
- package/packages/ui/dist/components/settings/index.d.ts +14 -0
- package/packages/ui/dist/components/settings/index.js +12 -0
- package/packages/ui/dist/components/workspace/index.d.ts +101 -0
- package/packages/ui/dist/components/workspace/index.js +16 -0
- package/packages/ui/dist/hierarchy/index.css +183 -0
- package/packages/ui/dist/hierarchy/index.d.ts +168 -0
- package/packages/ui/dist/hierarchy/index.js +36 -0
- package/packages/ui/dist/index.css +467 -0
- package/packages/ui/dist/index.d.ts +16 -0
- package/packages/ui/dist/index.js +282 -0
- package/packages/ui/dist/palette/index.d.ts +29 -0
- package/packages/ui/dist/palette/index.js +27 -0
- package/packages/ui/dist/palettes-De5u5iID.d.ts +394 -0
- package/packages/ui/dist/theme/index.d.ts +12063 -0
- package/packages/ui/dist/theme/index.js +96 -0
- package/packages/ui/floks.manifest.json +119 -0
- package/packages/ui/package.json +92 -0
- package/packages/ui/src/styles/base.css +130 -0
- package/packages/ui/src/styles/composite.css +6 -0
- package/packages/ui/src/styles/containers.css +10 -0
- package/packages/ui/src/styles/details-panel.css +147 -0
- package/packages/ui/src/styles/layout.css +58 -0
- package/packages/ui/src/theme/tokens.css +93 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/tile-menu/Tile.tsx
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
function Tile({ tile, onClick, onContextMenu, dragHandleProps, isDragging }) {
|
|
4
|
+
return /* @__PURE__ */ jsxs(
|
|
5
|
+
"div",
|
|
6
|
+
{
|
|
7
|
+
style: {
|
|
8
|
+
backgroundColor: "rgba(30, 30, 30, 0.7)",
|
|
9
|
+
backdropFilter: "blur(12px)",
|
|
10
|
+
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
11
|
+
borderRadius: "12px",
|
|
12
|
+
padding: "16px",
|
|
13
|
+
display: "flex",
|
|
14
|
+
flexDirection: "column",
|
|
15
|
+
gap: "8px",
|
|
16
|
+
cursor: "pointer",
|
|
17
|
+
opacity: isDragging ? 0.5 : 1,
|
|
18
|
+
boxShadow: isDragging ? "0 8px 16px rgba(0,0,0,0.5)" : "0 2px 4px rgba(0,0,0,0.2)",
|
|
19
|
+
transition: "transform 0.2s, box-shadow 0.2s"
|
|
20
|
+
},
|
|
21
|
+
onClick: () => onClick?.(tile.id),
|
|
22
|
+
onContextMenu: (e) => onContextMenu?.(e, tile.id),
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
25
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
|
|
26
|
+
tile.icon && /* @__PURE__ */ jsx("span", { style: { fontSize: "24px" }, children: tile.icon }),
|
|
27
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column" }, children: [
|
|
28
|
+
/* @__PURE__ */ jsx("span", { style: { color: "#fff", fontWeight: 600, fontSize: "14px" }, children: tile.title }),
|
|
29
|
+
tile.subtitle && /* @__PURE__ */ jsx("span", { style: { color: "#aaa", fontSize: "12px" }, children: tile.subtitle })
|
|
30
|
+
] })
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsx("div", { ...dragHandleProps, style: { cursor: "grab", padding: "4px", color: "#666" }, children: "\u283F" })
|
|
33
|
+
] }),
|
|
34
|
+
tile.badges && tile.badges.length > 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "4px", flexWrap: "wrap", marginTop: "8px" }, children: tile.badges.map((badge) => /* @__PURE__ */ jsx("span", { style: {
|
|
35
|
+
fontSize: "10px",
|
|
36
|
+
backgroundColor: "rgba(255,255,255,0.1)",
|
|
37
|
+
padding: "2px 6px",
|
|
38
|
+
borderRadius: "12px",
|
|
39
|
+
color: "#ccc"
|
|
40
|
+
}, children: badge }, badge)) })
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/tile-menu/TileGrid.tsx
|
|
47
|
+
import { useState, useRef, useEffect } from "react";
|
|
48
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
49
|
+
function TileGrid({
|
|
50
|
+
tiles,
|
|
51
|
+
onReorder: _onReorder,
|
|
52
|
+
onTileClick
|
|
53
|
+
}) {
|
|
54
|
+
const containerRef = useRef(null);
|
|
55
|
+
const [columns, setColumns] = useState(3);
|
|
56
|
+
const [tileMinHeight, setTileMinHeight] = useState(110);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!containerRef.current) return;
|
|
59
|
+
const observer = new ResizeObserver((entries) => {
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
const { width, height } = entry.contentRect;
|
|
62
|
+
if (width <= 0) return;
|
|
63
|
+
let cols = 1;
|
|
64
|
+
if (width >= 1200) cols = 4;
|
|
65
|
+
else if (width >= 860) cols = 3;
|
|
66
|
+
else if (width >= 540) cols = 2;
|
|
67
|
+
else cols = 1;
|
|
68
|
+
const estimatedRows = Math.ceil(tiles.length / cols);
|
|
69
|
+
let dynamicHeight = 110;
|
|
70
|
+
if (height > 0 && estimatedRows > 0) {
|
|
71
|
+
const availableRowHeight = (height - (estimatedRows + 1) * 16) / estimatedRows;
|
|
72
|
+
dynamicHeight = Math.max(90, Math.min(180, Math.floor(availableRowHeight)));
|
|
73
|
+
}
|
|
74
|
+
setColumns(cols);
|
|
75
|
+
setTileMinHeight(dynamicHeight);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
observer.observe(containerRef.current);
|
|
79
|
+
return () => observer.disconnect();
|
|
80
|
+
}, [tiles.length]);
|
|
81
|
+
return /* @__PURE__ */ jsx2(
|
|
82
|
+
"div",
|
|
83
|
+
{
|
|
84
|
+
ref: containerRef,
|
|
85
|
+
className: "skipo-tile-grid-container",
|
|
86
|
+
style: {
|
|
87
|
+
display: "grid",
|
|
88
|
+
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
|
|
89
|
+
gridAutoRows: `minmax(${tileMinHeight}px, auto)`,
|
|
90
|
+
gap: "14px",
|
|
91
|
+
padding: "14px",
|
|
92
|
+
width: "100%",
|
|
93
|
+
height: "100%",
|
|
94
|
+
boxSizing: "border-box",
|
|
95
|
+
overflowY: "auto"
|
|
96
|
+
},
|
|
97
|
+
children: tiles.map((tile) => /* @__PURE__ */ jsx2(Tile, { tile, onClick: onTileClick }, tile.id))
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/tile-menu/TileCreateButton.tsx
|
|
103
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
104
|
+
function TileCreateButton({ onClick }) {
|
|
105
|
+
return /* @__PURE__ */ jsx3(
|
|
106
|
+
"div",
|
|
107
|
+
{
|
|
108
|
+
onClick,
|
|
109
|
+
style: {
|
|
110
|
+
border: "2px dashed rgba(255, 255, 255, 0.2)",
|
|
111
|
+
borderRadius: "12px",
|
|
112
|
+
padding: "16px",
|
|
113
|
+
display: "flex",
|
|
114
|
+
justifyContent: "center",
|
|
115
|
+
alignItems: "center",
|
|
116
|
+
cursor: "pointer",
|
|
117
|
+
minHeight: "100px",
|
|
118
|
+
color: "rgba(255, 255, 255, 0.5)",
|
|
119
|
+
transition: "all 0.2s"
|
|
120
|
+
},
|
|
121
|
+
onMouseEnter: (e) => {
|
|
122
|
+
e.currentTarget.style.borderColor = "rgba(255, 255, 255, 0.5)";
|
|
123
|
+
e.currentTarget.style.color = "#fff";
|
|
124
|
+
},
|
|
125
|
+
onMouseLeave: (e) => {
|
|
126
|
+
e.currentTarget.style.borderColor = "rgba(255, 255, 255, 0.2)";
|
|
127
|
+
e.currentTarget.style.color = "rgba(255, 255, 255, 0.5)";
|
|
128
|
+
},
|
|
129
|
+
children: /* @__PURE__ */ jsx3("span", { style: { fontSize: "32px" }, children: "+" })
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/tile-menu/useTileMenu.ts
|
|
135
|
+
import { useState as useState2, useCallback } from "react";
|
|
136
|
+
function useTileMenu(initialTiles = []) {
|
|
137
|
+
const [tiles, setTiles] = useState2(initialTiles);
|
|
138
|
+
const addTile = useCallback((tile) => {
|
|
139
|
+
setTiles((prev) => [...prev, tile]);
|
|
140
|
+
}, []);
|
|
141
|
+
const removeTile = useCallback((id) => {
|
|
142
|
+
setTiles((prev) => prev.filter((t) => t.id !== id));
|
|
143
|
+
}, []);
|
|
144
|
+
const reorderTiles = useCallback((newOrder) => {
|
|
145
|
+
setTiles(newOrder);
|
|
146
|
+
}, []);
|
|
147
|
+
return {
|
|
148
|
+
tiles,
|
|
149
|
+
addTile,
|
|
150
|
+
removeTile,
|
|
151
|
+
reorderTiles
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
Tile,
|
|
157
|
+
TileGrid,
|
|
158
|
+
TileCreateButton,
|
|
159
|
+
useTileMenu
|
|
160
|
+
};
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSocketLocalOffset,
|
|
3
|
+
getSocketScreenPosition,
|
|
4
|
+
renderSpriteIcon
|
|
5
|
+
} from "./chunk-2GLR35UM.js";
|
|
6
|
+
|
|
7
|
+
// src/predictive/PredictiveGhostNodeCard/PredictiveGhostNodeCard.tsx
|
|
8
|
+
import { Zap } from "lucide-react";
|
|
9
|
+
import { getNodeSockets } from "@ultrst-armada/kloks/state-graph";
|
|
10
|
+
import { NodeAssetRegistry } from "@ultrst-armada/kloks/state-graph";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
var PredictiveGhostNodeCard = ({
|
|
13
|
+
ghost,
|
|
14
|
+
screenPos,
|
|
15
|
+
zoom,
|
|
16
|
+
onSolidify,
|
|
17
|
+
isHovered = false,
|
|
18
|
+
onHoverChange
|
|
19
|
+
}) => {
|
|
20
|
+
const node = ghost.prototypeNode;
|
|
21
|
+
const { inputs, outputs } = getNodeSockets(node);
|
|
22
|
+
const sprite = NodeAssetRegistry.get2DSpriteSetting(node);
|
|
23
|
+
const isRetroRecorder = node.type === "recorder" && node.recordingMode === "retrospective";
|
|
24
|
+
const isStandardRecorder = node.type === "recorder" && !isRetroRecorder;
|
|
25
|
+
const isContainer = node.type === "container";
|
|
26
|
+
const isButton = node.type === "button";
|
|
27
|
+
const isTrigger = node.type === "trigger";
|
|
28
|
+
const isTriggerFractions = isTrigger && node.triggerType === "beat_fractions";
|
|
29
|
+
const isStringNode = node.type === "primitive_string";
|
|
30
|
+
const isNumberNode = node.type === "primitive_number";
|
|
31
|
+
const isBooleanNode = node.type === "primitive_boolean";
|
|
32
|
+
const isObjectNode = node.type === "primitive_object";
|
|
33
|
+
const isFolderPathNode = node.type === "folder_path";
|
|
34
|
+
const triggerWidth = isTriggerFractions ? "w-[260px]" : "w-[170px]";
|
|
35
|
+
const cardWidthClass = isTrigger ? triggerWidth : isRetroRecorder ? "w-[200px]" : isStandardRecorder ? "w-[165px]" : isContainer ? "w-[190px]" : isFolderPathNode || isObjectNode ? "w-[180px]" : isStringNode ? "w-[165px]" : isNumberNode ? "w-[145px]" : isBooleanNode ? "w-[130px]" : isButton ? "w-[100px] h-[100px]" : "w-[130px]";
|
|
36
|
+
const maxSockets = Math.max(inputs.length, outputs.length);
|
|
37
|
+
const dynamicMinHeight = isButton ? "100px" : maxSockets > 1 ? `${Math.max(64, maxSockets * 32 + 10)}px` : "64px";
|
|
38
|
+
return /* @__PURE__ */ jsx(
|
|
39
|
+
"div",
|
|
40
|
+
{
|
|
41
|
+
style: {
|
|
42
|
+
position: "absolute",
|
|
43
|
+
left: `${screenPos.x}px`,
|
|
44
|
+
top: `${screenPos.y}px`,
|
|
45
|
+
transform: `translate(-50%, -50%) scale(${Math.max(0.65, Math.min(1.15, zoom))})`,
|
|
46
|
+
transformOrigin: "center center"
|
|
47
|
+
},
|
|
48
|
+
className: "pointer-events-auto z-30 select-none transition-transform",
|
|
49
|
+
onMouseEnter: () => onHoverChange?.(true),
|
|
50
|
+
onMouseLeave: () => onHoverChange?.(false),
|
|
51
|
+
children: /* @__PURE__ */ jsxs(
|
|
52
|
+
"div",
|
|
53
|
+
{
|
|
54
|
+
className: `relative ${cardWidthClass} rounded-lg bg-slate-950/65 backdrop-blur-md border-2 border-dashed border-cyan-400/70 p-2 flex flex-col items-center justify-center transition-all duration-200 ${isHovered ? "border-cyan-300 bg-slate-950/80 shadow-[0_0_30px_rgba(56,189,248,0.5)] scale-105" : "shadow-[0_0_18px_rgba(56,189,248,0.25)]"}`,
|
|
55
|
+
style: { minHeight: dynamicMinHeight },
|
|
56
|
+
children: [
|
|
57
|
+
/* @__PURE__ */ jsxs("div", { className: "w-full flex items-center justify-between pb-1 border-b border-cyan-500/20 text-[8px] font-mono tracking-wider text-cyan-300/80", children: [
|
|
58
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
59
|
+
/* @__PURE__ */ jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-cyan-400 animate-ping" }),
|
|
60
|
+
/* @__PURE__ */ jsx("span", { className: "font-extrabold uppercase truncate max-w-[90px]", children: node.name || ghost.label })
|
|
61
|
+
] }),
|
|
62
|
+
/* @__PURE__ */ jsxs("span", { className: "text-cyan-400/60 font-bold", children: [
|
|
63
|
+
"[",
|
|
64
|
+
ghost.candidateIndex + 1,
|
|
65
|
+
"/",
|
|
66
|
+
ghost.totalCandidates,
|
|
67
|
+
"]"
|
|
68
|
+
] })
|
|
69
|
+
] }),
|
|
70
|
+
isContainer ? /* @__PURE__ */ jsxs("div", { className: "w-full py-1.5 flex flex-col items-center gap-1", children: [
|
|
71
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
72
|
+
renderSpriteIcon(sprite.iconName, "#38bdf8", "w-4 h-4"),
|
|
73
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[10px] font-mono font-bold text-cyan-200", children: [
|
|
74
|
+
node.containerType?.toUpperCase(),
|
|
75
|
+
" CONTAINER"
|
|
76
|
+
] })
|
|
77
|
+
] }),
|
|
78
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-4 rounded bg-cyan-950/40 border border-cyan-500/20 flex items-center justify-around px-1", children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "w-1.5 rounded-sm bg-cyan-400/40", style: { height: `${30 + i % 3 * 30}%` } }, i)) })
|
|
79
|
+
] }) : isStandardRecorder || isRetroRecorder ? /* @__PURE__ */ jsxs("div", { className: "w-full py-1.5 flex flex-col items-center gap-1", children: [
|
|
80
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
81
|
+
renderSpriteIcon(sprite.iconName, "#f43f5e", "w-4 h-4"),
|
|
82
|
+
/* @__PURE__ */ jsx("span", { className: "text-[9px] font-mono font-bold text-rose-300", children: isRetroRecorder ? "RETRO RECORDER" : "LIVE RECORDER" })
|
|
83
|
+
] }),
|
|
84
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-3 rounded bg-rose-950/30 border border-rose-500/20 flex items-center px-1", children: /* @__PURE__ */ jsx("div", { className: "w-2/3 h-1.5 rounded-sm bg-rose-500/50 animate-pulse" }) })
|
|
85
|
+
] }) : isTrigger ? /* @__PURE__ */ jsxs("div", { className: "w-full py-1 flex flex-col items-center gap-1", children: [
|
|
86
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
87
|
+
renderSpriteIcon(sprite.iconName, "#eab308", "w-3.5 h-3.5"),
|
|
88
|
+
/* @__PURE__ */ jsx("span", { className: "text-[9px] font-mono font-bold text-amber-300", children: "STEP SEQUENCER" })
|
|
89
|
+
] }),
|
|
90
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-1", children: [...Array(8)].map((_, i) => /* @__PURE__ */ jsx("div", { className: `w-2 h-2 rounded-sm border ${i % 2 === 0 ? "bg-amber-400/60 border-amber-300" : "bg-slate-900 border-amber-500/30"}` }, i)) })
|
|
91
|
+
] }) : isButton ? /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center justify-center my-auto", children: /* @__PURE__ */ jsx("div", { className: "w-8 h-8 rounded-full border border-emerald-400/60 bg-emerald-950/30 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "w-3 h-3 rounded-full bg-emerald-400/80 animate-pulse" }) }) }) : isStringNode || isNumberNode || isBooleanNode || isObjectNode || isFolderPathNode ? /* @__PURE__ */ jsxs("div", { className: "w-full py-1 flex flex-col items-center gap-0.5 text-center", children: [
|
|
92
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono font-bold text-cyan-200 truncate", children: ghost.label }),
|
|
93
|
+
/* @__PURE__ */ jsx("span", { className: "text-[8px] font-mono text-cyan-400/60", children: ghost.badge })
|
|
94
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-1.5 text-center", children: [
|
|
95
|
+
renderSpriteIcon(sprite.iconName, sprite.accentColor || "#38bdf8", "w-6 h-6"),
|
|
96
|
+
/* @__PURE__ */ jsx("span", { className: "text-[9px] font-mono font-bold text-cyan-200 mt-1 truncate max-w-[110px]", children: node.name || ghost.label })
|
|
97
|
+
] }),
|
|
98
|
+
inputs.map((socket, idx) => {
|
|
99
|
+
const localPos = getSocketLocalOffset(node, socket.socketId, false, idx, inputs.length);
|
|
100
|
+
const isTargetMatching = ghost.matchingSocketId === socket.socketId;
|
|
101
|
+
return /* @__PURE__ */ jsx(
|
|
102
|
+
"div",
|
|
103
|
+
{
|
|
104
|
+
style: {
|
|
105
|
+
position: "absolute",
|
|
106
|
+
left: `calc(50% + ${localPos.x}px)`,
|
|
107
|
+
top: `calc(50% + ${localPos.y}px)`,
|
|
108
|
+
transform: "translate(-50%, -50%)"
|
|
109
|
+
},
|
|
110
|
+
className: `w-2.5 h-2.5 rounded-full border ${isTargetMatching ? "bg-cyan-400 border-white shadow-[0_0_10px_#38bdf8] scale-125" : "bg-cyan-950 border-cyan-500/60"}`,
|
|
111
|
+
title: `Input: ${socket.label}`
|
|
112
|
+
},
|
|
113
|
+
socket.socketId
|
|
114
|
+
);
|
|
115
|
+
}),
|
|
116
|
+
outputs.map((socket, idx) => {
|
|
117
|
+
const localPos = getSocketLocalOffset(node, socket.socketId, true, idx, outputs.length);
|
|
118
|
+
const isTargetMatching = ghost.matchingSocketId === socket.socketId;
|
|
119
|
+
return /* @__PURE__ */ jsx(
|
|
120
|
+
"div",
|
|
121
|
+
{
|
|
122
|
+
style: {
|
|
123
|
+
position: "absolute",
|
|
124
|
+
left: `calc(50% + ${localPos.x}px)`,
|
|
125
|
+
top: `calc(50% + ${localPos.y}px)`,
|
|
126
|
+
transform: "translate(-50%, -50%)"
|
|
127
|
+
},
|
|
128
|
+
className: `w-2.5 h-2.5 rounded-full border ${isTargetMatching ? "bg-cyan-400 border-white shadow-[0_0_10px_#38bdf8] scale-125" : "bg-cyan-950 border-cyan-500/60"}`,
|
|
129
|
+
title: `Output: ${socket.label}`
|
|
130
|
+
},
|
|
131
|
+
socket.socketId
|
|
132
|
+
);
|
|
133
|
+
}),
|
|
134
|
+
/* @__PURE__ */ jsx(
|
|
135
|
+
"button",
|
|
136
|
+
{
|
|
137
|
+
onMouseDown: (e) => {
|
|
138
|
+
e.stopPropagation();
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
},
|
|
141
|
+
onPointerDown: (e) => {
|
|
142
|
+
e.stopPropagation();
|
|
143
|
+
},
|
|
144
|
+
onClick: (e) => {
|
|
145
|
+
e.stopPropagation();
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
onSolidify(ghost);
|
|
148
|
+
},
|
|
149
|
+
className: "absolute -top-3 -right-3 z-40 w-7 h-7 rounded-full bg-slate-950/95 border-2 border-cyan-400 text-cyan-300 hover:bg-cyan-900 hover:text-white hover:border-cyan-200 hover:scale-125 shadow-[0_0_15px_rgba(6,182,212,0.8)] flex items-center justify-center transition-all cursor-pointer group/btn",
|
|
150
|
+
title: `Solidify ${node.name || ghost.label} & wire connection`,
|
|
151
|
+
children: /* @__PURE__ */ jsx(Zap, { className: "w-3.5 h-3.5 text-cyan-300 group-hover/btn:scale-110" })
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/predictive/PredictiveHudToolbar.tsx
|
|
162
|
+
import { ChevronLeft, ChevronRight, Zap as Zap2, X, Sparkles } from "lucide-react";
|
|
163
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
164
|
+
var PredictiveHudToolbar = ({
|
|
165
|
+
isActive,
|
|
166
|
+
scenarioIndex,
|
|
167
|
+
totalGhosts,
|
|
168
|
+
selectedCount,
|
|
169
|
+
onToggleActive,
|
|
170
|
+
onNextScenario,
|
|
171
|
+
onPrevScenario,
|
|
172
|
+
onSolidifyAll,
|
|
173
|
+
onClose
|
|
174
|
+
}) => {
|
|
175
|
+
if (selectedCount === 0) return null;
|
|
176
|
+
if (!isActive) {
|
|
177
|
+
return /* @__PURE__ */ jsx2("div", { className: "absolute top-4 left-1/2 -translate-x-1/2 z-40 pointer-events-auto", children: /* @__PURE__ */ jsxs2(
|
|
178
|
+
"button",
|
|
179
|
+
{
|
|
180
|
+
onClick: onToggleActive,
|
|
181
|
+
className: "flex items-center gap-2 px-3 py-1.5 rounded-full bg-slate-900/90 hover:bg-cyan-950 border border-cyan-500/40 hover:border-cyan-400 text-cyan-300 hover:text-white text-xs font-mono tracking-wide shadow-lg backdrop-blur-md transition-all hover:scale-105 group",
|
|
182
|
+
title: "Press Ctrl+Tab or Cmd+Tab to view prospective ghost nodes",
|
|
183
|
+
children: [
|
|
184
|
+
/* @__PURE__ */ jsx2(Sparkles, { className: "w-3.5 h-3.5 text-cyan-400 group-hover:rotate-45 transition-transform animate-pulse" }),
|
|
185
|
+
/* @__PURE__ */ jsx2("span", { children: "Predictive Nodes" }),
|
|
186
|
+
/* @__PURE__ */ jsx2("span", { className: "px-1.5 py-0.5 rounded bg-cyan-950/80 border border-cyan-500/30 text-[10px] text-cyan-200", children: "\u2318/Ctrl+Tab" })
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
) });
|
|
190
|
+
}
|
|
191
|
+
return /* @__PURE__ */ jsxs2("div", { className: "absolute top-4 left-1/2 -translate-x-1/2 z-40 pointer-events-auto flex flex-col items-center gap-1.5", children: [
|
|
192
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 px-3 py-1.5 rounded-full bg-slate-950/95 border border-cyan-400/80 shadow-[0_0_25px_rgba(6,182,212,0.4)] backdrop-blur-md", children: [
|
|
193
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-cyan-950/80 border border-cyan-500/40 text-cyan-300 text-[10px] font-mono font-bold tracking-widest uppercase", children: [
|
|
194
|
+
/* @__PURE__ */ jsx2("span", { className: "w-2 h-2 rounded-full bg-cyan-400 animate-ping" }),
|
|
195
|
+
/* @__PURE__ */ jsx2("span", { children: "PREDICTIVE ACTIVE" })
|
|
196
|
+
] }),
|
|
197
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-1 bg-slate-900/80 rounded-full px-1 border border-slate-700", children: [
|
|
198
|
+
/* @__PURE__ */ jsx2(
|
|
199
|
+
"button",
|
|
200
|
+
{
|
|
201
|
+
onClick: onPrevScenario,
|
|
202
|
+
className: "p-1 rounded-full text-slate-300 hover:text-white hover:bg-slate-800 transition-colors",
|
|
203
|
+
title: "Previous Scenario (Shift+Tab)",
|
|
204
|
+
children: /* @__PURE__ */ jsx2(ChevronLeft, { className: "w-3.5 h-3.5" })
|
|
205
|
+
}
|
|
206
|
+
),
|
|
207
|
+
/* @__PURE__ */ jsxs2("span", { className: "px-1.5 text-[11px] font-mono font-semibold text-cyan-200", children: [
|
|
208
|
+
"Scenario #",
|
|
209
|
+
scenarioIndex + 1
|
|
210
|
+
] }),
|
|
211
|
+
/* @__PURE__ */ jsx2(
|
|
212
|
+
"button",
|
|
213
|
+
{
|
|
214
|
+
onClick: onNextScenario,
|
|
215
|
+
className: "p-1 rounded-full text-slate-300 hover:text-white hover:bg-slate-800 transition-colors",
|
|
216
|
+
title: "Next Scenario (Tab)",
|
|
217
|
+
children: /* @__PURE__ */ jsx2(ChevronRight, { className: "w-3.5 h-3.5" })
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
] }),
|
|
221
|
+
totalGhosts > 0 && /* @__PURE__ */ jsxs2(
|
|
222
|
+
"button",
|
|
223
|
+
{
|
|
224
|
+
onMouseDown: (e) => {
|
|
225
|
+
e.stopPropagation();
|
|
226
|
+
e.preventDefault();
|
|
227
|
+
},
|
|
228
|
+
onPointerDown: (e) => {
|
|
229
|
+
e.stopPropagation();
|
|
230
|
+
},
|
|
231
|
+
onClick: (e) => {
|
|
232
|
+
e.stopPropagation();
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
onSolidifyAll();
|
|
235
|
+
},
|
|
236
|
+
className: "relative p-1.5 rounded-full bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-400 hover:to-blue-500 text-slate-950 transition-transform hover:scale-110 shadow-[0_0_15px_rgba(6,182,212,0.6)] flex items-center justify-center cursor-pointer group",
|
|
237
|
+
title: `Solidify all (${totalGhosts}) predicted nodes & wires`,
|
|
238
|
+
children: [
|
|
239
|
+
/* @__PURE__ */ jsx2(Zap2, { className: "w-3.5 h-3.5 text-slate-950 group-hover:rotate-12 transition-transform animate-pulse" }),
|
|
240
|
+
/* @__PURE__ */ jsx2("span", { className: "absolute -top-1 -right-1 px-1 min-w-[14px] h-3.5 rounded-full bg-slate-950 text-cyan-300 border border-cyan-400 font-black text-[8px] flex items-center justify-center font-mono shadow-sm", children: totalGhosts })
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
),
|
|
244
|
+
/* @__PURE__ */ jsx2(
|
|
245
|
+
"button",
|
|
246
|
+
{
|
|
247
|
+
onClick: onClose,
|
|
248
|
+
className: "p-1 rounded-full text-slate-400 hover:text-slate-100 hover:bg-white/10 transition-colors ml-1",
|
|
249
|
+
title: "Exit Predictive Mode (Esc / \u2318+Tab)",
|
|
250
|
+
children: /* @__PURE__ */ jsx2(X, { className: "w-3.5 h-3.5" })
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
] }),
|
|
254
|
+
/* @__PURE__ */ jsx2("div", { className: "text-[9px] font-mono text-cyan-300/80 bg-slate-950/80 px-2.5 py-0.5 rounded-full border border-cyan-500/20 backdrop-blur-sm", children: "Tab: next scenario \u2022 Shift+Tab: prev \u2022 Click Solidify on node or ghost" })
|
|
255
|
+
] });
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// src/predictive/PredictiveLayer2D.tsx
|
|
259
|
+
import { useState } from "react";
|
|
260
|
+
import { Zap as Zap3 } from "lucide-react";
|
|
261
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
262
|
+
var PredictiveLayer2D = ({
|
|
263
|
+
nodes,
|
|
264
|
+
ghostNodes,
|
|
265
|
+
traces,
|
|
266
|
+
selectedNodesWithGhosts,
|
|
267
|
+
worldToScreen,
|
|
268
|
+
camera,
|
|
269
|
+
onSolidifyGhost,
|
|
270
|
+
onSolidifyAllForNode,
|
|
271
|
+
isActive = false,
|
|
272
|
+
targetNodes = [],
|
|
273
|
+
onToggleActive
|
|
274
|
+
}) => {
|
|
275
|
+
const [hoveredGhostId, setHoveredGhostId] = useState(null);
|
|
276
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
277
|
+
const ghostMap = new Map(ghostNodes.map((g) => [g.id, g]));
|
|
278
|
+
if (!isActive) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
if (ghostNodes.length === 0) return null;
|
|
282
|
+
return /* @__PURE__ */ jsxs3("div", { className: "absolute inset-0 pointer-events-none z-20", children: [
|
|
283
|
+
/* @__PURE__ */ jsxs3("svg", { className: "w-full h-full absolute inset-0", children: [
|
|
284
|
+
/* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsxs3("filter", { id: "ghost-trace-glow", x: "-20%", y: "-20%", width: "140%", height: "140%", children: [
|
|
285
|
+
/* @__PURE__ */ jsx3("feGaussianBlur", { stdDeviation: "3", result: "blur" }),
|
|
286
|
+
/* @__PURE__ */ jsxs3("feMerge", { children: [
|
|
287
|
+
/* @__PURE__ */ jsx3("feMergeNode", { in: "blur" }),
|
|
288
|
+
/* @__PURE__ */ jsx3("feMergeNode", { in: "SourceGraphic" })
|
|
289
|
+
] })
|
|
290
|
+
] }) }),
|
|
291
|
+
traces.map((trace) => {
|
|
292
|
+
const ghost = ghostMap.get(trace.ghostNodeId);
|
|
293
|
+
if (!ghost) return null;
|
|
294
|
+
let p1;
|
|
295
|
+
let p2;
|
|
296
|
+
if (trace.isFromGhostToParent) {
|
|
297
|
+
const parent = nodeMap.get(trace.targetNodeId);
|
|
298
|
+
if (!parent) return null;
|
|
299
|
+
p1 = getSocketScreenPosition(ghost.prototypeNode, trace.sourceHandleId, true, worldToScreen, camera);
|
|
300
|
+
p2 = getSocketScreenPosition(parent, trace.targetHandleId, false, worldToScreen, camera);
|
|
301
|
+
} else {
|
|
302
|
+
const parent = nodeMap.get(trace.sourceNodeId);
|
|
303
|
+
if (!parent) return null;
|
|
304
|
+
p1 = getSocketScreenPosition(parent, trace.sourceHandleId, true, worldToScreen, camera);
|
|
305
|
+
p2 = getSocketScreenPosition(ghost.prototypeNode, trace.targetHandleId, false, worldToScreen, camera);
|
|
306
|
+
}
|
|
307
|
+
const isHovered = hoveredGhostId === ghost.id;
|
|
308
|
+
const strokeColor = trace.color || "#38bdf8";
|
|
309
|
+
const dx = Math.abs(p2.x - p1.x) * 0.45;
|
|
310
|
+
const path = `M ${p1.x} ${p1.y} C ${p1.x + dx} ${p1.y}, ${p2.x - dx} ${p2.y}, ${p2.x} ${p2.y}`;
|
|
311
|
+
return /* @__PURE__ */ jsx3("g", { children: /* @__PURE__ */ jsx3(
|
|
312
|
+
"path",
|
|
313
|
+
{
|
|
314
|
+
d: path,
|
|
315
|
+
fill: "none",
|
|
316
|
+
stroke: strokeColor,
|
|
317
|
+
strokeWidth: isHovered ? 2.5 : 1.5,
|
|
318
|
+
opacity: isHovered ? 0.9 : 0.6
|
|
319
|
+
}
|
|
320
|
+
) }, trace.id);
|
|
321
|
+
})
|
|
322
|
+
] }),
|
|
323
|
+
ghostNodes.map((ghost) => {
|
|
324
|
+
const screenPos = worldToScreen(ghost.worldPosition[0], ghost.worldPosition[2]);
|
|
325
|
+
return /* @__PURE__ */ jsx3(
|
|
326
|
+
PredictiveGhostNodeCard,
|
|
327
|
+
{
|
|
328
|
+
ghost,
|
|
329
|
+
screenPos,
|
|
330
|
+
zoom: camera.zoom,
|
|
331
|
+
onSolidify: onSolidifyGhost,
|
|
332
|
+
isHovered: hoveredGhostId === ghost.id,
|
|
333
|
+
onHoverChange: (hovered) => setHoveredGhostId(hovered ? ghost.id : null)
|
|
334
|
+
},
|
|
335
|
+
ghost.id
|
|
336
|
+
);
|
|
337
|
+
}),
|
|
338
|
+
Array.from(selectedNodesWithGhosts.entries()).map(([parentNodeId, ghosts]) => {
|
|
339
|
+
const parent = nodeMap.get(parentNodeId);
|
|
340
|
+
if (!parent || ghosts.length === 0) return null;
|
|
341
|
+
const screenPos = worldToScreen(parent.position[0], parent.position[2]);
|
|
342
|
+
const yOffset = -54 * Math.max(0.7, Math.min(1.2, camera.zoom));
|
|
343
|
+
return /* @__PURE__ */ jsx3(
|
|
344
|
+
"div",
|
|
345
|
+
{
|
|
346
|
+
style: {
|
|
347
|
+
position: "absolute",
|
|
348
|
+
left: `${screenPos.x}px`,
|
|
349
|
+
top: `${screenPos.y + yOffset}px`,
|
|
350
|
+
transform: "translate(-50%, -100%)"
|
|
351
|
+
},
|
|
352
|
+
className: "pointer-events-auto z-40",
|
|
353
|
+
children: /* @__PURE__ */ jsxs3(
|
|
354
|
+
"button",
|
|
355
|
+
{
|
|
356
|
+
onMouseDown: (e) => {
|
|
357
|
+
e.stopPropagation();
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
},
|
|
360
|
+
onPointerDown: (e) => {
|
|
361
|
+
e.stopPropagation();
|
|
362
|
+
},
|
|
363
|
+
onClick: (e) => {
|
|
364
|
+
e.stopPropagation();
|
|
365
|
+
e.preventDefault();
|
|
366
|
+
onSolidifyAllForNode(parentNodeId);
|
|
367
|
+
},
|
|
368
|
+
className: "relative w-8 h-8 rounded-full bg-slate-950/95 border-2 border-cyan-400 text-cyan-300 hover:text-white hover:bg-cyan-900 hover:border-cyan-200 hover:scale-125 shadow-[0_0_20px_rgba(6,182,212,0.8)] flex items-center justify-center transition-all cursor-pointer group",
|
|
369
|
+
title: `Solidify all (${ghosts.length}) predicted nodes for ${parent.name}`,
|
|
370
|
+
children: [
|
|
371
|
+
/* @__PURE__ */ jsx3(Zap3, { className: "w-4 h-4 text-cyan-300 group-hover:rotate-12 transition-transform" }),
|
|
372
|
+
ghosts.length > 1 && /* @__PURE__ */ jsx3("span", { className: "absolute -top-1.5 -right-1.5 px-1 min-w-[15px] h-3.5 rounded-full bg-cyan-500 text-slate-950 font-black text-[8px] flex items-center justify-center font-mono shadow-sm", children: ghosts.length })
|
|
373
|
+
]
|
|
374
|
+
}
|
|
375
|
+
)
|
|
376
|
+
},
|
|
377
|
+
`solidify-all-${parentNodeId}`
|
|
378
|
+
);
|
|
379
|
+
})
|
|
380
|
+
] });
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/predictive/usePredictiveGraph2D.ts
|
|
384
|
+
import { useState as useState2, useEffect, useMemo, useCallback } from "react";
|
|
385
|
+
import { computePredictiveGraph } from "@ultrst-armada/kloks/state-graph";
|
|
386
|
+
function usePredictiveGraph2D({
|
|
387
|
+
nodes,
|
|
388
|
+
connections,
|
|
389
|
+
selectedNodeId,
|
|
390
|
+
selectedNodeIds = [],
|
|
391
|
+
onSolidifyPredictiveNodes
|
|
392
|
+
}) {
|
|
393
|
+
const [isActive, setIsActive] = useState2(false);
|
|
394
|
+
const [scenarioIndex, setScenarioIndex] = useState2(0);
|
|
395
|
+
const targetNodes = useMemo(() => {
|
|
396
|
+
const idSet = new Set(selectedNodeIds || []);
|
|
397
|
+
if (selectedNodeId) idSet.add(selectedNodeId);
|
|
398
|
+
return nodes.filter((n) => idSet.has(n.id));
|
|
399
|
+
}, [nodes, selectedNodeId, selectedNodeIds]);
|
|
400
|
+
useEffect(() => {
|
|
401
|
+
if (targetNodes.length === 0 && isActive) {
|
|
402
|
+
setIsActive(false);
|
|
403
|
+
setScenarioIndex(0);
|
|
404
|
+
}
|
|
405
|
+
}, [targetNodes.length, isActive]);
|
|
406
|
+
const { ghostNodes, traces, selectedNodesWithGhosts } = useMemo(() => {
|
|
407
|
+
if (!isActive || targetNodes.length === 0) {
|
|
408
|
+
return {
|
|
409
|
+
ghostNodes: [],
|
|
410
|
+
traces: [],
|
|
411
|
+
selectedNodesWithGhosts: /* @__PURE__ */ new Map()
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return computePredictiveGraph(targetNodes, connections, scenarioIndex);
|
|
415
|
+
}, [isActive, targetNodes, connections, scenarioIndex]);
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
const handleKeyDown = (e) => {
|
|
418
|
+
const activeEl = document.activeElement;
|
|
419
|
+
if (activeEl && ["INPUT", "TEXTAREA", "SELECT"].includes(activeEl.tagName)) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const isExplicitToggleKey = (e.ctrlKey || e.metaKey || e.altKey) && ["Tab", "p", "P", "g", "G"].includes(e.key) || ["p", "P", "g", "G", "`", "~"].includes(e.key);
|
|
423
|
+
if (!isActive && targetNodes.length > 0 && (e.key === "Tab" || isExplicitToggleKey)) {
|
|
424
|
+
e.preventDefault();
|
|
425
|
+
e.stopPropagation();
|
|
426
|
+
setIsActive(true);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (isActive && isExplicitToggleKey) {
|
|
430
|
+
e.preventDefault();
|
|
431
|
+
e.stopPropagation();
|
|
432
|
+
setIsActive((prev) => !prev);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (isActive && e.key === "Tab") {
|
|
436
|
+
e.preventDefault();
|
|
437
|
+
e.stopPropagation();
|
|
438
|
+
if (e.shiftKey) {
|
|
439
|
+
setScenarioIndex((i) => i <= 0 ? 12 : i - 1);
|
|
440
|
+
} else {
|
|
441
|
+
setScenarioIndex((i) => i + 1);
|
|
442
|
+
}
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (isActive && e.key === "Escape") {
|
|
446
|
+
e.preventDefault();
|
|
447
|
+
setIsActive(false);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
window.addEventListener("keydown", handleKeyDown, { capture: true });
|
|
451
|
+
return () => {
|
|
452
|
+
window.removeEventListener("keydown", handleKeyDown, { capture: true });
|
|
453
|
+
};
|
|
454
|
+
}, [isActive, targetNodes.length]);
|
|
455
|
+
const toggleActive = useCallback(() => {
|
|
456
|
+
if (targetNodes.length > 0) {
|
|
457
|
+
setIsActive((prev) => !prev);
|
|
458
|
+
}
|
|
459
|
+
}, [targetNodes.length]);
|
|
460
|
+
const nextScenario = useCallback(() => {
|
|
461
|
+
setScenarioIndex((i) => i + 1);
|
|
462
|
+
}, []);
|
|
463
|
+
const prevScenario = useCallback(() => {
|
|
464
|
+
setScenarioIndex((i) => i <= 0 ? 12 : i - 1);
|
|
465
|
+
}, []);
|
|
466
|
+
const solidifyGhost = useCallback(
|
|
467
|
+
(ghost) => {
|
|
468
|
+
onSolidifyPredictiveNodes?.([ghost]);
|
|
469
|
+
},
|
|
470
|
+
[onSolidifyPredictiveNodes]
|
|
471
|
+
);
|
|
472
|
+
const solidifyAllForNode = useCallback(
|
|
473
|
+
(parentNodeId) => {
|
|
474
|
+
const ghosts = selectedNodesWithGhosts.get(parentNodeId);
|
|
475
|
+
if (ghosts && ghosts.length > 0) {
|
|
476
|
+
onSolidifyPredictiveNodes?.(ghosts);
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
[selectedNodesWithGhosts, onSolidifyPredictiveNodes]
|
|
480
|
+
);
|
|
481
|
+
const solidifyAll = useCallback(() => {
|
|
482
|
+
if (ghostNodes.length > 0) {
|
|
483
|
+
onSolidifyPredictiveNodes?.(ghostNodes);
|
|
484
|
+
}
|
|
485
|
+
}, [ghostNodes, onSolidifyPredictiveNodes]);
|
|
486
|
+
return {
|
|
487
|
+
isActive,
|
|
488
|
+
scenarioIndex,
|
|
489
|
+
targetNodes,
|
|
490
|
+
ghostNodes,
|
|
491
|
+
traces,
|
|
492
|
+
selectedNodesWithGhosts,
|
|
493
|
+
toggleActive,
|
|
494
|
+
nextScenario,
|
|
495
|
+
prevScenario,
|
|
496
|
+
solidifyGhost,
|
|
497
|
+
solidifyAllForNode,
|
|
498
|
+
solidifyAll,
|
|
499
|
+
close: () => setIsActive(false)
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export {
|
|
504
|
+
PredictiveGhostNodeCard,
|
|
505
|
+
PredictiveHudToolbar,
|
|
506
|
+
PredictiveLayer2D,
|
|
507
|
+
usePredictiveGraph2D
|
|
508
|
+
};
|