@jgengine/editor 0.11.0 โ 0.13.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/CHANGELOG.md +52 -0
- package/README.md +12 -0
- package/dist/AssetBrowser.d.ts +33 -1
- package/dist/AssetBrowser.js +64 -2
- package/dist/AssetDropZone.d.ts +9 -0
- package/dist/AssetDropZone.js +65 -0
- package/dist/CatalogsPanel.d.ts +4 -2
- package/dist/CatalogsPanel.js +162 -9
- package/dist/CityPreview.d.ts +11 -0
- package/dist/CityPreview.js +45 -0
- package/dist/CollectionsPanel.js +5 -5
- package/dist/DebugDraw.d.ts +3 -1
- package/dist/DebugDraw.js +37 -19
- package/dist/EditorApp.d.ts +15 -1
- package/dist/EditorApp.js +147 -35
- package/dist/EditorChrome.d.ts +27 -6
- package/dist/EditorChrome.js +314 -130
- package/dist/InspectorPanel.d.ts +17 -7
- package/dist/InspectorPanel.js +173 -27
- package/dist/LightingPanel.d.ts +11 -0
- package/dist/LightingPanel.js +99 -0
- package/dist/MaterialsWorkspacePanel.d.ts +12 -0
- package/dist/MaterialsWorkspacePanel.js +86 -0
- package/dist/ParentPickerMenu.d.ts +13 -0
- package/dist/ParentPickerMenu.js +33 -0
- package/dist/PerfProbe.js +33 -0
- package/dist/PrefabsPanel.js +5 -5
- package/dist/RuntimePlayBridge.d.ts +5 -3
- package/dist/RuntimePlayBridge.js +6 -13
- package/dist/ScatterPreview.d.ts +3 -1
- package/dist/ScatterPreview.js +10 -7
- package/dist/SchemaInspector.js +21 -8
- package/dist/ScriptingPanel.d.ts +15 -0
- package/dist/ScriptingPanel.js +46 -0
- package/dist/SelectionGizmo.js +114 -30
- package/dist/StandaloneEditor.d.ts +16 -9
- package/dist/StandaloneEditor.js +19 -20
- package/dist/TerrainPanel.d.ts +3 -2
- package/dist/TerrainPanel.js +16 -5
- package/dist/TerrainSculpt.js +98 -59
- package/dist/agent/AgentPanel.d.ts +5 -2
- package/dist/agent/AgentPanel.js +11 -6
- package/dist/agent/toolBridge.js +5 -0
- package/dist/assetImport.d.ts +36 -0
- package/dist/assetImport.js +66 -0
- package/dist/authoredComponentMeta.d.ts +25 -0
- package/dist/authoredComponentMeta.js +65 -0
- package/dist/gizmoPivot.d.ts +4 -0
- package/dist/gizmoPivot.js +34 -0
- package/dist/handlers/catalogs.d.ts +3 -0
- package/dist/handlers/catalogs.js +137 -0
- package/dist/handlers/collections.d.ts +3 -0
- package/dist/handlers/collections.js +57 -0
- package/dist/handlers/context.d.ts +36 -0
- package/dist/handlers/context.js +1 -0
- package/dist/handlers/document.d.ts +3 -0
- package/dist/handlers/document.js +350 -0
- package/dist/handlers/grids.d.ts +3 -0
- package/dist/handlers/grids.js +142 -0
- package/dist/handlers/hierarchy.d.ts +3 -0
- package/dist/handlers/hierarchy.js +55 -0
- package/dist/handlers/index.d.ts +8 -0
- package/dist/handlers/index.js +25 -0
- package/dist/handlers/minimap.d.ts +9 -0
- package/dist/handlers/minimap.js +32 -0
- package/dist/handlers/runtime.d.ts +3 -0
- package/dist/handlers/runtime.js +191 -0
- package/dist/handlers/streets.d.ts +3 -0
- package/dist/handlers/streets.js +159 -0
- package/dist/handlers/terrain.d.ts +3 -0
- package/dist/handlers/terrain.js +204 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/listDocumentTriggers.d.ts +42 -0
- package/dist/listDocumentTriggers.js +49 -0
- package/dist/materialAssignments.d.ts +63 -0
- package/dist/materialAssignments.js +84 -0
- package/dist/mcp/fieldSpec.d.ts +32 -0
- package/dist/mcp/fieldSpec.js +174 -0
- package/dist/mcp/rpcRequest.d.ts +8 -5
- package/dist/mcp/rpcRequest.js +72 -93
- package/dist/mcp/tools.js +105 -912
- package/dist/networkSnapshot.d.ts +82 -0
- package/dist/networkSnapshot.js +73 -0
- package/dist/parentCandidates.d.ts +12 -0
- package/dist/parentCandidates.js +25 -0
- package/dist/session.d.ts +91 -4
- package/dist/session.js +36 -1017
- package/dist/shell/AnimationPanel.d.ts +17 -0
- package/dist/shell/AnimationPanel.js +134 -0
- package/dist/shell/AssetThumbnail.d.ts +12 -0
- package/dist/shell/AssetThumbnail.js +23 -0
- package/dist/shell/BottomDock.d.ts +25 -0
- package/dist/shell/BottomDock.js +40 -0
- package/dist/shell/CommandPalette.d.ts +12 -0
- package/dist/shell/CommandPalette.js +56 -0
- package/dist/shell/ConsolePanel.d.ts +8 -0
- package/dist/shell/ConsolePanel.js +45 -0
- package/dist/shell/ContentBrowser.d.ts +21 -0
- package/dist/shell/ContentBrowser.js +130 -0
- package/dist/shell/HierarchyPanel.d.ts +26 -0
- package/dist/shell/HierarchyPanel.js +387 -0
- package/dist/shell/NetworkWorkspacePanel.d.ts +11 -0
- package/dist/shell/NetworkWorkspacePanel.js +49 -0
- package/dist/shell/PlayModeBar.d.ts +12 -0
- package/dist/shell/PlayModeBar.js +27 -0
- package/dist/shell/ProfilerPanel.d.ts +10 -0
- package/dist/shell/ProfilerPanel.js +41 -0
- package/dist/shell/SceneToolbar.d.ts +43 -0
- package/dist/shell/SceneToolbar.js +122 -0
- package/dist/shell/StatusBar.d.ts +18 -0
- package/dist/shell/StatusBar.js +41 -0
- package/dist/shell/TopAppBar.d.ts +35 -0
- package/dist/shell/TopAppBar.js +64 -0
- package/dist/shell/ViewportOverlays.d.ts +23 -0
- package/dist/shell/ViewportOverlays.js +136 -0
- package/dist/shell/WorkspaceRail.d.ts +20 -0
- package/dist/shell/WorkspaceRail.js +31 -0
- package/dist/shell/cameraTelemetry.d.ts +27 -0
- package/dist/shell/cameraTelemetry.js +24 -0
- package/dist/shell/commandRegistry.d.ts +58 -0
- package/dist/shell/commandRegistry.js +133 -0
- package/dist/shell/consoleSink.d.ts +11 -0
- package/dist/shell/consoleSink.js +17 -0
- package/dist/shell/consoleStore.d.ts +27 -0
- package/dist/shell/consoleStore.js +43 -0
- package/dist/shell/fields.d.ts +39 -0
- package/dist/shell/fields.js +104 -0
- package/dist/shell/formatSavedRelative.d.ts +2 -0
- package/dist/shell/formatSavedRelative.js +14 -0
- package/dist/shell/glbThumbnailCache.d.ts +45 -0
- package/dist/shell/glbThumbnailCache.js +241 -0
- package/dist/shell/hierarchyA11y.d.ts +21 -0
- package/dist/shell/hierarchyA11y.js +35 -0
- package/dist/shell/icons.d.ts +13 -0
- package/dist/shell/icons.js +85 -0
- package/dist/shell/layoutStore.d.ts +68 -0
- package/dist/shell/layoutStore.js +156 -0
- package/dist/shell/pathFlythrough.d.ts +37 -0
- package/dist/shell/pathFlythrough.js +54 -0
- package/dist/shell/perfHistory.d.ts +70 -0
- package/dist/shell/perfHistory.js +130 -0
- package/dist/shell/renameObject.d.ts +3 -0
- package/dist/shell/renameObject.js +25 -0
- package/dist/shell/theme.d.ts +36 -0
- package/dist/shell/theme.js +37 -0
- package/dist/shell/ui.d.ts +84 -0
- package/dist/shell/ui.js +90 -0
- package/dist/shell/useGlbThumbnail.d.ts +8 -0
- package/dist/shell/useGlbThumbnail.js +19 -0
- package/dist/uiStore.d.ts +27 -0
- package/dist/uiStore.js +10 -0
- package/dist/viewportContextMenu.d.ts +4 -2
- package/dist/viewportContextMenu.js +5 -1
- package/package.json +4 -4
- package/skills/jgengine/SKILL.md +8 -4
- package/skills/jgengine/api.md +182 -81
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-editor/SKILL.md +18 -3
- package/skills/jgengine-editor/api.md +403 -20
- package/skills/jgengine-editor/capabilities.md +46 -0
- package/skills/jgengine-editor/reference.md +87 -0
- package/skills/level-design/SKILL.md +54 -0
- package/skills/level-design/agents/openai.yaml +4 -0
- package/skills/level-design/references/application-playbook.md +192 -0
- package/skills/level-design/references/field-guide.md +337 -0
- package/skills/level-design/references/live-editor-loop.md +179 -0
- package/dist/OutlinerPanel.d.ts +0 -14
- package/dist/OutlinerPanel.js +0 -84
- package/skills/jgengine/reference-cartridge.md +0 -72
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { FOCUS_RING, INPUT_CLS, NUMERIC } from "./shell/theme.js";
|
|
4
|
+
/**
|
|
5
|
+
* Searchable parent-target menu for hierarchy / viewport "Parent toโฆ".
|
|
6
|
+
* Parent owns candidate list (cycle-safe) and the set_parent write.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export function ParentPickerMenu({ x, y, candidates, onPick, onClose, }) {
|
|
10
|
+
const [query, setQuery] = useState("");
|
|
11
|
+
const inputRef = useRef(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
inputRef.current?.focus();
|
|
14
|
+
}, []);
|
|
15
|
+
const filtered = useMemo(() => {
|
|
16
|
+
const q = query.trim().toLowerCase();
|
|
17
|
+
if (q.length === 0)
|
|
18
|
+
return candidates;
|
|
19
|
+
return candidates.filter((entry) => entry.label.toLowerCase().includes(q) || entry.id.toLowerCase().includes(q));
|
|
20
|
+
}, [candidates, query]);
|
|
21
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "pointer-events-auto fixed inset-0 z-[60]", onPointerDown: onClose, onContextMenu: (event) => event.preventDefault() }), _jsxs("div", { role: "menu", "aria-label": "Parent to object", className: "pointer-events-auto fixed z-[61] flex max-h-72 w-64 flex-col overflow-hidden rounded-md border border-white/15 bg-neutral-900/95 shadow-2xl backdrop-blur-sm", style: { left: x, top: y }, onContextMenu: (event) => event.preventDefault(), children: [_jsx("div", { className: "border-b border-white/10 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-white/45", children: "Parent to\u2026" }), _jsx("div", { className: "border-b border-white/10 p-1.5", children: _jsx("input", { ref: inputRef, value: query, onChange: (event) => setQuery(event.target.value), onKeyDown: (event) => {
|
|
22
|
+
if (event.key === "Escape") {
|
|
23
|
+
event.preventDefault();
|
|
24
|
+
onClose();
|
|
25
|
+
}
|
|
26
|
+
}, placeholder: "Search objects\u2026", "aria-label": "Filter parent candidates", className: `h-7 w-full px-2 text-[12px] ${INPUT_CLS} ${FOCUS_RING}` }) }), _jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto py-1", children: [_jsx("button", { type: "button", role: "menuitem", onPointerDown: (event) => {
|
|
27
|
+
event.stopPropagation();
|
|
28
|
+
onPick(null);
|
|
29
|
+
}, className: "flex w-full items-center px-3 py-1.5 text-left text-[13px] text-white/85 hover:bg-cyan-400/20 hover:text-white", children: "\u2014 none (root) \u2014" }), filtered.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-[11px] text-white/40", children: candidates.length === 0 ? "No valid parents (would create a cycle)." : "No matches." })) : (filtered.map((entry) => (_jsxs("button", { type: "button", role: "menuitem", onPointerDown: (event) => {
|
|
30
|
+
event.stopPropagation();
|
|
31
|
+
onPick(entry.id);
|
|
32
|
+
}, className: "flex w-full items-center gap-2 px-3 py-1.5 text-left text-[13px] text-white/85 hover:bg-cyan-400/20 hover:text-white", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: entry.label }), entry.label !== entry.id ? (_jsx("span", { className: `shrink-0 text-[10px] text-white/35 ${NUMERIC}`, children: entry.id })) : null] }, entry.id))))] })] })] }));
|
|
33
|
+
}
|
package/dist/PerfProbe.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
2
2
|
import { memo, useRef } from "react";
|
|
3
|
+
import { devtools } from "@jgengine/core/devtools/devtools";
|
|
3
4
|
import { editorPerfMarks } from "./perfMarks.js";
|
|
5
|
+
import { getCameraTelemetry } from "./shell/cameraTelemetry.js";
|
|
6
|
+
import { frameBudgetFromStats } from "./shell/perfHistory.js";
|
|
4
7
|
const SAMPLE_WINDOW_MS = 500;
|
|
5
8
|
/** Sum-of-abs delta on camera position/orientation above which a frame counts as camera activity. */
|
|
6
9
|
const CAMERA_MOVE_EPSILON = 1e-4;
|
|
@@ -30,6 +33,22 @@ export const PerfProbe = memo(function PerfProbe({ api }) {
|
|
|
30
33
|
Math.abs(camera.quaternion.w - cam.qw);
|
|
31
34
|
if (dPos > CAMERA_MOVE_EPSILON || dRot > CAMERA_MOVE_EPSILON)
|
|
32
35
|
activeRef.current = true;
|
|
36
|
+
// Publish the camera basis for the DOM orientation widget (mutable channel, no React state).
|
|
37
|
+
const telemetry = getCameraTelemetry();
|
|
38
|
+
const m = camera.matrixWorld.elements;
|
|
39
|
+
telemetry.rightX = m[0];
|
|
40
|
+
telemetry.rightY = m[1];
|
|
41
|
+
telemetry.rightZ = m[2];
|
|
42
|
+
telemetry.upX = m[4];
|
|
43
|
+
telemetry.upY = m[5];
|
|
44
|
+
telemetry.upZ = m[6];
|
|
45
|
+
// Camera looks down -Z; store the viewing direction so depth<0 means "toward the camera".
|
|
46
|
+
telemetry.forwardX = -m[8];
|
|
47
|
+
telemetry.forwardY = -m[9];
|
|
48
|
+
telemetry.forwardZ = -m[10];
|
|
49
|
+
telemetry.azimuth = Math.atan2(-m[8], -m[10]);
|
|
50
|
+
telemetry.elevation = Math.asin(Math.max(-1, Math.min(1, -m[9])));
|
|
51
|
+
telemetry.version += 1;
|
|
33
52
|
cam.px = camera.position.x;
|
|
34
53
|
cam.py = camera.position.y;
|
|
35
54
|
cam.pz = camera.position.z;
|
|
@@ -49,6 +68,12 @@ export const PerfProbe = memo(function PerfProbe({ api }) {
|
|
|
49
68
|
const active = activeRef.current || authoring.authoringMs > 0 || sceneChanged;
|
|
50
69
|
lastDrawRef.current = drawCalls;
|
|
51
70
|
lastTriRef.current = triangles;
|
|
71
|
+
// Chromium-only; other browsers omit memory rather than fabricating a value.
|
|
72
|
+
const heap = performance.memory;
|
|
73
|
+
const memoryMb = heap === undefined ? undefined : Math.round((heap.usedJSHeapSize / (1024 * 1024)) * 10) / 10;
|
|
74
|
+
// Real sim / outside split from FrameDriver โ devtools.frame (same as debug_snapshot). Omit when
|
|
75
|
+
// no runtime frames have been recorded yet โ never invent a render series from viewport fps alone.
|
|
76
|
+
const budget = frameBudgetFromStats(devtools.frame.stats());
|
|
52
77
|
api.setPerf({
|
|
53
78
|
fps: Math.round(fps * 10) / 10,
|
|
54
79
|
frameMs: Math.round((elapsed / framesRef.current) * 100) / 100,
|
|
@@ -59,6 +84,14 @@ export const PerfProbe = memo(function PerfProbe({ api }) {
|
|
|
59
84
|
raycastMs: authoring.raycastMs,
|
|
60
85
|
rebuildMs: authoring.rebuildMs,
|
|
61
86
|
authoringMs: authoring.authoringMs,
|
|
87
|
+
...(memoryMb === undefined ? {} : { memoryMb }),
|
|
88
|
+
...(budget === null
|
|
89
|
+
? {}
|
|
90
|
+
: {
|
|
91
|
+
simMs: budget.simMs,
|
|
92
|
+
outsideMs: budget.outsideMs,
|
|
93
|
+
...(budget.phases === undefined ? {} : { phases: budget.phases }),
|
|
94
|
+
}),
|
|
62
95
|
});
|
|
63
96
|
framesRef.current = 0;
|
|
64
97
|
windowStartRef.current = now;
|
package/dist/PrefabsPanel.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
|
+
import { CONTROL, INPUT_CLS, MICRO_LABEL } from "./shell/theme.js";
|
|
3
4
|
import { shallowArrayEqual, useStoreSelector } from "./useStoreSelector.js";
|
|
4
|
-
const
|
|
5
|
-
const BTN = "rounded-md bg-white/[0.04] px-2 py-1 text-neutral-300 ring-1 ring-inset ring-white/[0.06] transition-colors hover:bg-white/10 hover:text-neutral-100";
|
|
5
|
+
const BTN = `${CONTROL} px-2 py-1 text-[11px] disabled:opacity-40`;
|
|
6
6
|
function newPrefabId() {
|
|
7
7
|
return `prefab_${Date.now().toString(36)}_${Math.floor(Math.random() * 1296).toString(36)}`;
|
|
8
8
|
}
|
|
@@ -29,14 +29,14 @@ export function PrefabsPanel({ session, api }) {
|
|
|
29
29
|
const focus = api.getFocusTarget() ?? { x: 0, y: 0, z: 0 };
|
|
30
30
|
session.dispatch({ type: "insertPrefab", prefabId: prefab.id, at: focus });
|
|
31
31
|
};
|
|
32
|
-
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-2 p-2", children: [_jsxs("div", { className: "flex gap-1", children: [_jsx("input", { className: `min-w-0 flex-1 ${
|
|
32
|
+
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-2 p-2", children: [_jsx("div", { className: MICRO_LABEL, children: "Prefabs" }), _jsxs("div", { className: "flex gap-1", children: [_jsx("input", { className: `h-7 min-w-0 flex-1 px-2 ${INPUT_CLS}`, placeholder: "New prefab name\u2026", value: name, onChange: (event) => setName(event.target.value), onKeyDown: (event) => {
|
|
33
33
|
if (event.key === "Enter")
|
|
34
34
|
makeFromSelection();
|
|
35
|
-
} }), _jsx("button", { type: "button", className: BTN, disabled: selection.length === 0 || name.trim().length === 0, onClick: makeFromSelection, children: "Make prefab" })] }), selection.length === 0 ? (_jsx("div", { className: "text-[10px] text-neutral-500", children: "Select objects, name them, then make a prefab from the selection." })) : null, _jsx("div", { className: "min-h-0 flex-1 space-y-1 overflow-auto pr-1", children: prefabs.length === 0 ? (_jsx("div", { className: "text-neutral-600", children: "No prefabs yet." })) : (prefabs.map((prefab) => {
|
|
35
|
+
} }), _jsx("button", { type: "button", className: BTN, disabled: selection.length === 0 || name.trim().length === 0, onClick: makeFromSelection, children: "Make prefab" })] }), selection.length === 0 ? (_jsx("div", { className: "text-[10px] text-neutral-500", children: "Select objects, name them, then make a prefab from the selection." })) : null, _jsx("div", { className: "min-h-0 flex-1 space-y-1.5 overflow-auto pr-1", children: prefabs.length === 0 ? (_jsx("div", { className: "text-[11px] text-neutral-600", children: "No prefabs yet." })) : (prefabs.map((prefab) => {
|
|
36
36
|
const count = prefab.fragment.markers.length +
|
|
37
37
|
prefab.fragment.volumes.length +
|
|
38
38
|
prefab.fragment.paths.length +
|
|
39
39
|
prefab.fragment.annotations.length;
|
|
40
|
-
return (_jsxs("div", { className: "flex items-center gap-2 rounded-
|
|
40
|
+
return (_jsxs("div", { className: "flex items-center gap-2 rounded-[6px] border border-white/[0.07] bg-white/[0.02] p-2", children: [_jsxs("div", { className: "min-w-0 flex-1 truncate", children: [_jsx("div", { className: "truncate text-[12px] text-neutral-100", children: prefab.name }), _jsxs("div", { className: "text-[10px] text-neutral-500", children: [count, " objects"] })] }), _jsx("button", { type: "button", className: "shrink-0 rounded-[5px] border border-cyan-400/30 bg-cyan-500/15 px-2 py-0.5 text-[10px] text-cyan-200 transition-colors hover:bg-cyan-500/25", onClick: () => insertAt(prefab), children: "Insert" }), _jsx("button", { type: "button", className: "shrink-0 rounded-[5px] border border-rose-400/25 bg-rose-500/15 px-2 py-0.5 text-[10px] text-rose-200 transition-colors hover:bg-rose-500/25", onClick: () => session.dispatch({ type: "deletePrefab", prefabId: prefab.id }), children: "Delete" })] }, prefab.id));
|
|
41
41
|
})) })] }));
|
|
42
42
|
}
|
|
@@ -8,11 +8,13 @@ export declare function RuntimePlayPublisher({ api }: {
|
|
|
8
8
|
api: EditorHostApi;
|
|
9
9
|
}): null;
|
|
10
10
|
/**
|
|
11
|
-
* Play-mode chrome:
|
|
12
|
-
* Transform nudges go through `runtime_set`
|
|
11
|
+
* Play-mode chrome: slim shell app bar (Pause/Step/Exit via runtime play controls) plus a
|
|
12
|
+
* compact reverse-channel entity inspector. Transform nudges go through `runtime_set`
|
|
13
|
+
* (document write-back by default).
|
|
13
14
|
* @internal
|
|
14
15
|
*/
|
|
15
|
-
export declare function RuntimePlayInspectorChrome({ api, onExit, }: {
|
|
16
|
+
export declare function RuntimePlayInspectorChrome({ gameId, api, onExit, }: {
|
|
17
|
+
gameId: string;
|
|
16
18
|
api: EditorHostApi;
|
|
17
19
|
onExit: () => void;
|
|
18
20
|
}): import("react").JSX.Element;
|
|
@@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
4
|
import { useGameContext } from "@jgengine/react/provider";
|
|
5
5
|
import { MICRO } from "./chromeStyles.js";
|
|
6
|
+
import { PlayModeBar } from "./shell/PlayModeBar.js";
|
|
6
7
|
const PUBLISH_MS = 100;
|
|
7
8
|
function metaValues(meta) {
|
|
8
9
|
if (meta === null || typeof meta !== "object" || Array.isArray(meta))
|
|
@@ -76,11 +77,12 @@ export function RuntimePlayPublisher({ api }) {
|
|
|
76
77
|
return null;
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
|
-
* Play-mode chrome:
|
|
80
|
-
* Transform nudges go through `runtime_set`
|
|
80
|
+
* Play-mode chrome: slim shell app bar (Pause/Step/Exit via runtime play controls) plus a
|
|
81
|
+
* compact reverse-channel entity inspector. Transform nudges go through `runtime_set`
|
|
82
|
+
* (document write-back by default).
|
|
81
83
|
* @internal
|
|
82
84
|
*/
|
|
83
|
-
export function RuntimePlayInspectorChrome({ api, onExit, }) {
|
|
85
|
+
export function RuntimePlayInspectorChrome({ gameId, api, onExit, }) {
|
|
84
86
|
const [play, setPlay] = useState(() => api.getPlayControl());
|
|
85
87
|
const [summary, setSummary] = useState(null);
|
|
86
88
|
const [selectedId, setSelectedId] = useState(null);
|
|
@@ -109,15 +111,6 @@ export function RuntimePlayInspectorChrome({ api, onExit, }) {
|
|
|
109
111
|
}
|
|
110
112
|
setDetail(JSON.stringify(got.result, null, 2));
|
|
111
113
|
}, [api, selectedId, summary?.entityCount, play.pendingSteps, play.paused]);
|
|
112
|
-
const pause = () => {
|
|
113
|
-
api.handle({ method: "runtime_pause" });
|
|
114
|
-
};
|
|
115
|
-
const resume = () => {
|
|
116
|
-
api.handle({ method: "runtime_resume" });
|
|
117
|
-
};
|
|
118
|
-
const step = () => {
|
|
119
|
-
api.handle({ method: "runtime_step", frames: 1 });
|
|
120
|
-
};
|
|
121
114
|
const nudgeSelected = (axis, delta) => {
|
|
122
115
|
if (selectedId === null)
|
|
123
116
|
return;
|
|
@@ -136,5 +129,5 @@ export function RuntimePlayInspectorChrome({ api, onExit, }) {
|
|
|
136
129
|
},
|
|
137
130
|
});
|
|
138
131
|
};
|
|
139
|
-
return (_jsxs("div", { className: "pointer-events-none absolute inset-0 z-50 flex flex-col", children: [_jsx(
|
|
132
|
+
return (_jsxs("div", { className: "pointer-events-none absolute inset-0 z-50 flex flex-col", children: [_jsx(PlayModeBar, { gameId: gameId, api: api, onExit: onExit }), _jsxs("aside", { className: "pointer-events-auto ml-auto mt-2 mr-2 flex w-72 max-h-[70vh] flex-col overflow-hidden rounded-xl border border-white/[0.08] bg-[#0d0f13]/95 text-[11px] text-neutral-200 shadow-2xl shadow-black/50 backdrop-blur-md", children: [_jsxs("div", { className: "flex items-center border-b border-white/[0.06] px-3 py-2", children: [_jsx("div", { className: MICRO, children: "Runtime" }), _jsxs("div", { className: "ml-auto text-neutral-500", children: [summary?.entityCount ?? 0, " entities"] })] }), _jsxs("div", { className: "min-h-0 flex-1 overflow-auto p-2", children: [(summary?.entities ?? []).map((entity) => (_jsxs("button", { type: "button", className: `mb-1 block w-full truncate rounded-md px-2 py-1 text-left ${selectedId === entity.id ? "bg-cyan-500/20 text-cyan-100" : "hover:bg-white/[0.06]"}`, onClick: () => setSelectedId(entity.id), children: [entity.id, entity.valueKeys.length > 0 ? (_jsx("span", { className: "ml-1 text-neutral-500", children: entity.valueKeys.join(",") })) : null] }, entity.id))), (summary?.entities.length ?? 0) === 0 ? (_jsx("div", { className: "px-2 py-3 text-neutral-500", children: "No live entities yet \u2014 play or push_runtime_delta." })) : null] }), selectedId !== null ? (_jsxs("div", { className: "border-t border-white/[0.06] p-2", children: [_jsxs("div", { className: "mb-1 flex gap-1", children: [_jsx("button", { type: "button", className: "rounded-md bg-white/[0.07] px-2 py-1 hover:bg-white/15", onClick: () => nudgeSelected("x", -1), children: "\u2212X" }), _jsx("button", { type: "button", className: "rounded-md bg-white/[0.07] px-2 py-1 hover:bg-white/15", onClick: () => nudgeSelected("x", 1), children: "+X" }), _jsx("button", { type: "button", className: "rounded-md bg-white/[0.07] px-2 py-1 hover:bg-white/15", onClick: () => nudgeSelected("z", -1), children: "\u2212Z" }), _jsx("button", { type: "button", className: "rounded-md bg-white/[0.07] px-2 py-1 hover:bg-white/15", onClick: () => nudgeSelected("z", 1), children: "+Z" })] }), _jsx("pre", { className: "max-h-40 overflow-auto rounded-md border border-white/[0.06] bg-black/40 p-2 text-[10px] text-neutral-400", children: detail }), _jsx("div", { className: "mt-1 text-[10px] text-neutral-500", children: "Tweaks write back to the scene document (undoable)." })] })) : null] })] }));
|
|
140
133
|
}
|
package/dist/ScatterPreview.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type { EditorHostApi } from "./session.js";
|
|
|
3
3
|
* Deterministic instanced preview of every foliage/scatter region in the document, grounded on the
|
|
4
4
|
* sculpt terrain (or the game's ground) and rebuilt whenever a region or its density changes. Renders
|
|
5
5
|
* real per-species proxy models through the shared `InstancedScatter` primitive โ the same renderer a
|
|
6
|
-
* game uses to consume `resolveScatter` at runtime.
|
|
6
|
+
* game uses to consume `resolveScatter` at runtime. Subscribes only to the document slices scatter
|
|
7
|
+
* actually reads (markers/volumes/paths for clearance, terrain for grounding), so selection changes
|
|
8
|
+
* and unrelated edits (notes, grids, catalogs) never re-resolve up to {@link MAX_INSTANCES} placements.
|
|
7
9
|
* @internal โ mounted by `EditorApp`; not a game-author entry point.
|
|
8
10
|
*/
|
|
9
11
|
export declare const ScatterPreview: import("react").MemoExoticComponent<({ api }: {
|
package/dist/ScatterPreview.js
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { memo,
|
|
2
|
+
import { memo, useMemo } from "react";
|
|
3
3
|
import { editableTerrainFromSnapshot } from "@jgengine/core/world/terraform";
|
|
4
4
|
import { resolveScatter } from "@jgengine/core/world/scatterRegion";
|
|
5
5
|
import { useGameContext } from "@jgengine/react/provider";
|
|
6
6
|
import { InstancedScatter } from "@jgengine/shell/scatter";
|
|
7
|
+
import { useStoreSelector } from "./useStoreSelector.js";
|
|
7
8
|
const MAX_INSTANCES = 20000;
|
|
8
9
|
/**
|
|
9
10
|
* Deterministic instanced preview of every foliage/scatter region in the document, grounded on the
|
|
10
11
|
* sculpt terrain (or the game's ground) and rebuilt whenever a region or its density changes. Renders
|
|
11
12
|
* real per-species proxy models through the shared `InstancedScatter` primitive โ the same renderer a
|
|
12
|
-
* game uses to consume `resolveScatter` at runtime.
|
|
13
|
+
* game uses to consume `resolveScatter` at runtime. Subscribes only to the document slices scatter
|
|
14
|
+
* actually reads (markers/volumes/paths for clearance, terrain for grounding), so selection changes
|
|
15
|
+
* and unrelated edits (notes, grids, catalogs) never re-resolve up to {@link MAX_INSTANCES} placements.
|
|
13
16
|
* @internal โ mounted by `EditorApp`; not a game-author entry point.
|
|
14
17
|
*/
|
|
15
18
|
export const ScatterPreview = memo(function ScatterPreview({ api }) {
|
|
16
19
|
const ctx = useGameContext();
|
|
17
20
|
const session = api.getSession();
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const snapshot = state.document.terrain ?? null;
|
|
21
|
+
const markers = useStoreSelector(session, (state) => state.document.markers);
|
|
22
|
+
const volumes = useStoreSelector(session, (state) => state.document.volumes);
|
|
23
|
+
const paths = useStoreSelector(session, (state) => state.document.paths);
|
|
24
|
+
const snapshot = useStoreSelector(session, (state) => state.document.terrain ?? null);
|
|
22
25
|
const baseGround = ctx.world.ground;
|
|
23
26
|
const terrain = useMemo(() => {
|
|
24
27
|
if (snapshot !== null)
|
|
25
28
|
return editableTerrainFromSnapshot(snapshot, baseGround);
|
|
26
29
|
return { sampleHeight: baseGround.sampleHeight, sampleNormal: baseGround.sampleNormal };
|
|
27
30
|
}, [snapshot, baseGround]);
|
|
28
|
-
const instances = useMemo(() => resolveScatter(
|
|
31
|
+
const instances = useMemo(() => resolveScatter({ markers, volumes, paths }, terrain), [markers, volumes, paths, terrain]);
|
|
29
32
|
return _jsx(InstancedScatter, { instances: instances, maxInstances: MAX_INSTANCES });
|
|
30
33
|
});
|
package/dist/SchemaInspector.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { parseParams, randomizeGroupParams, resetGroupParams, } from "@jgengine/core/scene/sceneKinds";
|
|
4
|
-
|
|
5
|
-
const
|
|
4
|
+
import { BORDER, CONTROL, FOCUS_RING, INPUT_CLS, MICRO_LABEL } from "./shell/theme.js";
|
|
5
|
+
const INPUT = `px-2 py-1 ${INPUT_CLS}`;
|
|
6
|
+
const MICRO = MICRO_LABEL;
|
|
7
|
+
const BTN = `${CONTROL} px-2 py-1 text-[10px] disabled:opacity-40`;
|
|
8
|
+
/** One-click archetype bundles: picking a preset writes its whole value bag as a single meta patch. */
|
|
9
|
+
function PresetRow({ schema, onMeta }) {
|
|
10
|
+
const presets = schema.presets ?? [];
|
|
11
|
+
if (presets.length === 0)
|
|
12
|
+
return null;
|
|
13
|
+
return (_jsxs("label", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: MICRO, children: "preset" }), _jsxs("select", { className: `w-32 ${INPUT}`, value: "", onChange: (event) => {
|
|
14
|
+
const preset = presets.find((entry) => entry.id === event.target.value);
|
|
15
|
+
if (preset !== undefined)
|
|
16
|
+
onMeta({ ...preset.values }, `preset:${preset.id}`);
|
|
17
|
+
}, children: [_jsx("option", { value: "", disabled: true, children: "apply preset\u2026" }), presets.map((preset) => (_jsx("option", { value: preset.id, children: preset.label ?? preset.id }, preset.id)))] })] }));
|
|
18
|
+
}
|
|
6
19
|
function labelOf(field) {
|
|
7
20
|
return field.label ?? field.key;
|
|
8
21
|
}
|
|
@@ -32,7 +45,7 @@ function FieldRow({ field, meta, onMeta }) {
|
|
|
32
45
|
}
|
|
33
46
|
case "color": {
|
|
34
47
|
const value = params[field.key];
|
|
35
|
-
return (_jsxs("label", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: MICRO, children: labelOf(field) }), _jsx("input", { type: "color", className:
|
|
48
|
+
return (_jsxs("label", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: MICRO, children: labelOf(field) }), _jsx("input", { type: "color", className: `h-7 w-14 cursor-pointer rounded-[5px] border border-white/10 bg-black/40 ${FOCUS_RING}`, value: value, onChange: (event) => onMeta({ [field.key]: event.target.value }, coalesce) })] }));
|
|
36
49
|
}
|
|
37
50
|
case "text": {
|
|
38
51
|
const value = params[field.key];
|
|
@@ -40,12 +53,12 @@ function FieldRow({ field, meta, onMeta }) {
|
|
|
40
53
|
}
|
|
41
54
|
case "seed": {
|
|
42
55
|
const value = params[field.key];
|
|
43
|
-
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("label", { className: "flex min-w-0 flex-1 items-center gap-2", children: [_jsx("span", { className: MICRO, children: labelOf(field) }), _jsx("input", { className: `w-full min-w-0 ${INPUT}`, value: value, placeholder: "reroll\u2026", onChange: (event) => onMeta({ [field.key]: event.target.value }, coalesce) })] }), _jsx("button", { type: "button", className:
|
|
56
|
+
return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("label", { className: "flex min-w-0 flex-1 items-center gap-2", children: [_jsx("span", { className: MICRO, children: labelOf(field) }), _jsx("input", { className: `w-full min-w-0 ${INPUT}`, value: value, placeholder: "reroll\u2026", onChange: (event) => onMeta({ [field.key]: event.target.value }, coalesce) })] }), _jsx("button", { type: "button", className: BTN, title: "Reroll seed", onClick: () => onMeta({ [field.key]: `r${Math.abs(Math.round((value.length + 1) * 2654435761)).toString(36).slice(0, 6)}${value.length}` }, coalesce), children: "\u27F3" })] }));
|
|
44
57
|
}
|
|
45
58
|
case "weightedList": {
|
|
46
59
|
const list = params[field.key];
|
|
47
60
|
const set = (next) => onMeta({ [field.key]: next }, coalesce);
|
|
48
|
-
return (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: MICRO, children: labelOf(field) }), list.map((entry, index) => (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("input", { className: `min-w-0 flex-1 ${INPUT}`, value: entry.item, placeholder: field.itemLabel ?? "id", onChange: (event) => set(list.map((e, i) => (i === index ? { ...e, item: event.target.value } : e))) }), _jsx("input", { type: "number", step: 1, min: 0, className: `w-14 ${INPUT}`, value: entry.weight, onChange: (event) => set(list.map((e, i) => (i === index ? { ...e, weight: Math.max(0, Number(event.target.value)) } : e))) }), _jsx("button", { type: "button", className:
|
|
61
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: MICRO, children: labelOf(field) }), list.map((entry, index) => (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("input", { className: `min-w-0 flex-1 ${INPUT}`, value: entry.item, placeholder: field.itemLabel ?? "id", onChange: (event) => set(list.map((e, i) => (i === index ? { ...e, item: event.target.value } : e))) }), _jsx("input", { type: "number", step: 1, min: 0, className: `w-14 ${INPUT}`, value: entry.weight, onChange: (event) => set(list.map((e, i) => (i === index ? { ...e, weight: Math.max(0, Number(event.target.value)) } : e))) }), _jsx("button", { type: "button", className: `${CONTROL} px-1.5 py-1 text-neutral-400 hover:bg-rose-500/20 hover:text-rose-200 disabled:opacity-40`, disabled: list.length <= 1, onClick: () => set(list.filter((_, i) => i !== index)), children: "\u00D7" })] }, index))), _jsx("button", { type: "button", className: `w-full ${BTN}`, onClick: () => set([...list, { item: "", weight: 1 }]), children: "+ row" })] }));
|
|
49
62
|
}
|
|
50
63
|
case "action":
|
|
51
64
|
return null; // Action buttons are rendered by the group section, which owns the whole schema.
|
|
@@ -54,12 +67,12 @@ function FieldRow({ field, meta, onMeta }) {
|
|
|
54
67
|
/** A schema action button (randomize / reset a group) โ computed patches from the core helpers. */
|
|
55
68
|
function ActionButton({ field, schema, onMeta }) {
|
|
56
69
|
const run = () => onMeta(field.action === "randomize" ? randomizeGroupParams(schema, field.group, Math.random) : resetGroupParams(schema, field.group), `${field.action}:${field.group ?? "all"}`);
|
|
57
|
-
return (_jsxs("button", { type: "button", className:
|
|
70
|
+
return (_jsxs("button", { type: "button", className: `w-full ${BTN} font-medium`, onClick: run, children: [field.action === "randomize" ? "๐ฒ " : "โบ ", labelOf(field)] }));
|
|
58
71
|
}
|
|
59
72
|
/** A collapsible group section: header (click to toggle) + its fields + any action buttons. */
|
|
60
73
|
function GroupSection({ title, accent, fields, schema, meta, onMeta, defaultCollapsed, }) {
|
|
61
74
|
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
62
|
-
return (_jsxs("div", { className:
|
|
75
|
+
return (_jsxs("div", { className: `rounded-[6px] border ${BORDER}`, children: [_jsxs("button", { type: "button", className: "flex w-full items-center gap-1 px-2 py-1 text-left text-[9px] font-semibold uppercase tracking-[0.12em] text-neutral-300 transition-colors hover:bg-white/[0.04]", onClick: () => setCollapsed((value) => !value), children: [_jsx("span", { className: "text-neutral-500", children: collapsed ? "โธ" : "โพ" }), _jsx("span", { style: { color: accent }, children: title })] }), collapsed ? null : (_jsx("div", { className: "space-y-2 px-2 pb-2", children: fields.map((field) => field.type === "action" ? (_jsx(ActionButton, { field: field, schema: schema, onMeta: onMeta }, field.key)) : (_jsx(FieldRow, { field: field, meta: meta, onMeta: onMeta }, field.key))) }))] }));
|
|
63
76
|
}
|
|
64
77
|
/**
|
|
65
78
|
* Renders any parameter schema as an inspector panel โ one row per field
|
|
@@ -74,5 +87,5 @@ export function SchemaInspector({ schema, label, accent = "#34d399", note, meta,
|
|
|
74
87
|
const grouped = new Set(groups.map((group) => group.id));
|
|
75
88
|
// Fields with no group (or a group not declared in schema.groups) render first, headerless.
|
|
76
89
|
const ungrouped = schema.fields.filter((field) => field.group === undefined || !grouped.has(field.group));
|
|
77
|
-
return (_jsxs("div", { className: "space-y-2 rounded-lg border p-2.5", style: { borderColor: `${accent}26`, backgroundColor: `${accent}10` }, children: [_jsx("div", { className: "text-[9px] font-semibold uppercase tracking-[0.14em]", style: { color: accent }, children: label }), ungrouped.map((field) => field.type === "action" ? (_jsx(ActionButton, { field: field, schema: schema, onMeta: onMeta }, field.key)) : (_jsx(FieldRow, { field: field, meta: meta, onMeta: onMeta }, field.key))), groups.map((group) => (_jsx(GroupSection, { title: group.label, accent: accent, fields: schema.fields.filter((field) => field.group === group.id), schema: schema, meta: meta, onMeta: onMeta, defaultCollapsed: group.collapsed ?? false }, group.id))), note !== undefined && note.length > 0 ? _jsx("div", { className: "text-[10px] text-neutral-500", children: note }) : null] }));
|
|
90
|
+
return (_jsxs("div", { className: "space-y-2 rounded-lg border p-2.5", style: { borderColor: `${accent}26`, backgroundColor: `${accent}10` }, children: [_jsx("div", { className: "text-[9px] font-semibold uppercase tracking-[0.14em]", style: { color: accent }, children: label }), _jsx(PresetRow, { schema: schema, onMeta: onMeta }), ungrouped.map((field) => field.type === "action" ? (_jsx(ActionButton, { field: field, schema: schema, onMeta: onMeta }, field.key)) : (_jsx(FieldRow, { field: field, meta: meta, onMeta: onMeta }, field.key))), groups.map((group) => (_jsx(GroupSection, { title: group.label, accent: accent, fields: schema.fields.filter((field) => field.group === group.id), schema: schema, meta: meta, onMeta: onMeta, defaultCollapsed: group.collapsed ?? false }, group.id))), note !== undefined && note.length > 0 ? _jsx("div", { className: "text-[10px] text-neutral-500", children: note }) : null] }));
|
|
78
91
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scripting workspace: lists every document object with an authored behavior trigger, selects them
|
|
3
|
+
* for framing/inspection, and reuses {@link TriggerInspector} for edits. Writes go only through
|
|
4
|
+
* session `setMarker` / `setVolume` meta patches (same path as the inspector Components tab).
|
|
5
|
+
*/
|
|
6
|
+
import type { EditorSession } from "@jgengine/core/editor/index";
|
|
7
|
+
import type { EditorHostApi } from "./session.js";
|
|
8
|
+
/**
|
|
9
|
+
* Left-dock scripting workspace: document-wide trigger list + selected-object editor.
|
|
10
|
+
* @internal โ mounted by `EditorChrome` when the scripting rail workspace is active.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ScriptingPanel: import("react").MemoExoticComponent<({ session, api, }: {
|
|
13
|
+
session: EditorSession;
|
|
14
|
+
api: EditorHostApi;
|
|
15
|
+
}) => import("react").JSX.Element>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Scripting workspace: lists every document object with an authored behavior trigger, selects them
|
|
4
|
+
* for framing/inspection, and reuses {@link TriggerInspector} for edits. Writes go only through
|
|
5
|
+
* session `setMarker` / `setVolume` meta patches (same path as the inspector Components tab).
|
|
6
|
+
*/
|
|
7
|
+
import { memo, useMemo } from "react";
|
|
8
|
+
import { listDocumentTriggers } from "./listDocumentTriggers.js";
|
|
9
|
+
import { TriggerInspector } from "./TriggerInspector.js";
|
|
10
|
+
import { Icon } from "./shell/icons.js";
|
|
11
|
+
import { BORDER, CONTROL, CONTROL_ACTIVE, FOCUS_RING, MICRO_LABEL, TEXT_MUTED } from "./shell/theme.js";
|
|
12
|
+
import { shallowArrayEqual, useStoreSelector } from "./useStoreSelector.js";
|
|
13
|
+
function bindingSummary(entry) {
|
|
14
|
+
if (entry.bindings.length === 0)
|
|
15
|
+
return "โ";
|
|
16
|
+
return entry.bindings.map((binding) => `${binding.on} โ ${binding.action}`).join(" ยท ");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Left-dock scripting workspace: document-wide trigger list + selected-object editor.
|
|
20
|
+
* @internal โ mounted by `EditorChrome` when the scripting rail workspace is active.
|
|
21
|
+
*/
|
|
22
|
+
export const ScriptingPanel = memo(function ScriptingPanel({ session, api, }) {
|
|
23
|
+
const document = useStoreSelector(session, (state) => state.document);
|
|
24
|
+
const selection = useStoreSelector(session, (state) => state.selection, shallowArrayEqual);
|
|
25
|
+
const selectedId = selection[0];
|
|
26
|
+
const entries = useMemo(() => listDocumentTriggers(document), [document]);
|
|
27
|
+
const selectedEntry = selectedId === undefined ? undefined : entries.find((entry) => entry.sourceId === selectedId);
|
|
28
|
+
const selectedMarker = selectedEntry?.sourceKind === "marker" ? document.markers.find((marker) => marker.id === selectedId) : undefined;
|
|
29
|
+
const selectedVolume = selectedEntry?.sourceKind === "volume" ? document.volumes.find((volume) => volume.id === selectedId) : undefined;
|
|
30
|
+
const selectEntry = (entry) => {
|
|
31
|
+
session.dispatch({ type: "select", ids: [entry.sourceId] });
|
|
32
|
+
api.handle({ method: "camera_goto", id: entry.sourceId });
|
|
33
|
+
};
|
|
34
|
+
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [_jsxs("div", { className: "min-h-0 flex-1 space-y-1 overflow-auto p-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: MICRO_LABEL, children: "Authored triggers" }), _jsx("span", { className: `ml-auto tabular-nums ${TEXT_MUTED}`, children: entries.length })] }), entries.length === 0 ? (_jsxs("div", { className: "rounded-[6px] border border-dashed border-white/[0.08] bg-white/[0.02] px-3 py-4 text-[11px] leading-relaxed text-neutral-500", children: ["No behavior triggers on this scene yet. Select a marker or volume in the hierarchy, open the Components tab, and add a trigger \u2014 or place a zone and set ", _jsx("span", { className: "text-neutral-400", children: "on" }), " /", " ", _jsx("span", { className: "text-neutral-400", children: "action" }), " in the inspector."] })) : (entries.map((entry) => {
|
|
35
|
+
const selected = entry.sourceId === selectedId;
|
|
36
|
+
return (_jsxs("button", { type: "button", onClick: () => selectEntry(entry), className: `flex w-full flex-col gap-0.5 rounded-[6px] px-2.5 py-2 text-left transition-colors ${FOCUS_RING} ${selected ? CONTROL_ACTIVE : `${CONTROL} hover:bg-white/[0.06]`}`, children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(Icon, { name: entry.sourceKind === "volume" ? "cube" : "target", size: 12 }), _jsx("span", { className: "min-w-0 flex-1 truncate text-[12px] text-neutral-100", children: entry.label }), _jsx("span", { className: `shrink-0 text-[10px] ${TEXT_MUTED}`, children: entry.sourceKind })] }), _jsxs("div", { className: `truncate pl-[18px] text-[10px] ${TEXT_MUTED}`, children: [entry.objectKind, " \u00B7 ", bindingSummary(entry)] })] }, `${entry.sourceKind}:${entry.sourceId}`));
|
|
37
|
+
}))] }), selectedMarker !== undefined ? (_jsxs("div", { className: `shrink-0 space-y-2 border-t ${BORDER} p-2.5`, children: [_jsxs("div", { className: MICRO_LABEL, children: ["Edit \u00B7 ", selectedMarker.label ?? selectedMarker.id] }), _jsx(TriggerInspector, { target: "marker", meta: selectedMarker.meta, onMeta: (patch, coalesce) => session.dispatch({
|
|
38
|
+
type: "setMarker",
|
|
39
|
+
id: selectedMarker.id,
|
|
40
|
+
patch: { meta: { ...selectedMarker.meta, ...patch } },
|
|
41
|
+
}, { coalesce: `${coalesce}:${selectedMarker.id}` }) })] })) : null, selectedVolume !== undefined ? (_jsxs("div", { className: `shrink-0 space-y-2 border-t ${BORDER} p-2.5`, children: [_jsxs("div", { className: MICRO_LABEL, children: ["Edit \u00B7 ", selectedVolume.label ?? selectedVolume.id] }), _jsx(TriggerInspector, { target: "volume", meta: selectedVolume.meta, onMeta: (patch, coalesce) => session.dispatch({
|
|
42
|
+
type: "setVolume",
|
|
43
|
+
id: selectedVolume.id,
|
|
44
|
+
patch: { meta: { ...selectedVolume.meta, ...patch } },
|
|
45
|
+
}, { coalesce: `${coalesce}:${selectedVolume.id}` }) })] })) : null, selectedId !== undefined && selectedEntry === undefined ? (_jsx("div", { className: `shrink-0 border-t ${BORDER} px-2.5 py-2 text-[10px] text-neutral-500`, children: "Selection has no authored trigger. Pick a row above, or add a trigger on this object in the inspector." })) : null] }));
|
|
46
|
+
});
|