@nbt-dev/devtools 0.0.1 → 0.0.4
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/LICENSE +177 -0
- package/README.md +3 -2
- package/TRADEMARKS.md +49 -0
- package/dist/components/devtools/data-browser/bulk-decoder.d.ts +0 -3
- package/dist/components/devtools/data-browser/data-store.d.ts +3 -1
- package/dist/components/devtools/data-browser/value-popover.d.ts +18 -0
- package/dist/components/devtools/devtools-context.d.ts +24 -1
- package/dist/components/devtools/entity-graph/diagram-tab.d.ts +4 -1
- package/dist/components/devtools/sources/file-tree.d.ts +16 -0
- package/dist/components/devtools/sources/lsp-client.d.ts +57 -0
- package/dist/components/devtools/sources/lsp-extensions.d.ts +4 -0
- package/dist/components/devtools/sources/nbt-editor.d.ts +13 -0
- package/dist/components/devtools/sources/nbt-language.d.ts +7 -0
- package/dist/components/devtools/sources/sources-tab.d.ts +3 -0
- package/dist/components/devtools/sources/use-dev-files.d.ts +41 -0
- package/dist/generated/bulk-protocol.d.ts +0 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2173 -469
- package/dist/index.js.map +4 -4
- package/dist/styles.css +1 -1
- package/package.json +15 -4
package/dist/index.js
CHANGED
|
@@ -26,6 +26,24 @@ function wsBaseFrom(apiBaseUrl) {
|
|
|
26
26
|
import React2, { useEffect } from "react";
|
|
27
27
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
28
28
|
var Ctx = React2.createContext(null);
|
|
29
|
+
var STORAGE_KEY = "nimbit-devtools:v1";
|
|
30
|
+
var DEFAULT_FILTER = {
|
|
31
|
+
info: true,
|
|
32
|
+
warn: true,
|
|
33
|
+
error: true,
|
|
34
|
+
plain: true
|
|
35
|
+
};
|
|
36
|
+
function loadPersisted() {
|
|
37
|
+
if (typeof window === "undefined") return {};
|
|
38
|
+
try {
|
|
39
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
40
|
+
if (!raw) return {};
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
return typeof parsed === "object" && parsed ? parsed : {};
|
|
43
|
+
} catch {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
29
47
|
var DevToolsProvider = ({ children, defaultActiveTab }) => {
|
|
30
48
|
const [open, setOpen] = React2.useState(false);
|
|
31
49
|
const [dock, setDock] = React2.useState("bottom");
|
|
@@ -36,7 +54,87 @@ var DevToolsProvider = ({ children, defaultActiveTab }) => {
|
|
|
36
54
|
const [maximized, setMaximized] = React2.useState(false);
|
|
37
55
|
const [dataCart, setDataCart] = React2.useState(null);
|
|
38
56
|
const [dataEntity, setDataEntity] = React2.useState(null);
|
|
57
|
+
const [view, setView] = React2.useState("table");
|
|
58
|
+
const [graphCarts, setGraphCarts] = React2.useState(null);
|
|
59
|
+
const [hiddenEntities, setHiddenEntities] = React2.useState(
|
|
60
|
+
() => /* @__PURE__ */ new Set()
|
|
61
|
+
);
|
|
62
|
+
const [graphLeftW, setGraphLeftW] = React2.useState(176);
|
|
63
|
+
const [graphRightW, setGraphRightW] = React2.useState(440);
|
|
64
|
+
const [consoleFilter, setConsoleFilter] = React2.useState(DEFAULT_FILTER);
|
|
65
|
+
const [consolePaused, setConsolePaused] = React2.useState(false);
|
|
66
|
+
const [sourcesCart, setSourcesCart] = React2.useState(null);
|
|
67
|
+
const [sourcesFile, setSourcesFile] = React2.useState(null);
|
|
68
|
+
const [sourcesTreeW, setSourcesTreeW] = React2.useState(200);
|
|
69
|
+
const [hydrated, setHydrated] = React2.useState(false);
|
|
39
70
|
const toggle = React2.useCallback(() => setOpen((o) => !o), []);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const saved = loadPersisted();
|
|
73
|
+
if (saved.open != null) setOpen(saved.open);
|
|
74
|
+
if (saved.dock != null) setDock(saved.dock);
|
|
75
|
+
if (saved.activeTab != null) setActiveTab(saved.activeTab);
|
|
76
|
+
if (saved.size != null) setSize(saved.size);
|
|
77
|
+
if (saved.maximized != null) setMaximized(saved.maximized);
|
|
78
|
+
if (saved.dataCart != null) setDataCart(saved.dataCart);
|
|
79
|
+
if (saved.dataEntity != null) setDataEntity(saved.dataEntity);
|
|
80
|
+
if (saved.view != null) setView(saved.view);
|
|
81
|
+
if (saved.graphCarts != null) setGraphCarts(new Set(saved.graphCarts));
|
|
82
|
+
if (saved.hiddenEntities != null)
|
|
83
|
+
setHiddenEntities(new Set(saved.hiddenEntities));
|
|
84
|
+
if (saved.graphLeftW != null) setGraphLeftW(saved.graphLeftW);
|
|
85
|
+
if (saved.graphRightW != null) setGraphRightW(saved.graphRightW);
|
|
86
|
+
if (saved.consoleFilter != null) setConsoleFilter(saved.consoleFilter);
|
|
87
|
+
if (saved.consolePaused != null) setConsolePaused(saved.consolePaused);
|
|
88
|
+
if (saved.sourcesCart != null) setSourcesCart(saved.sourcesCart);
|
|
89
|
+
if (saved.sourcesFile != null) setSourcesFile(saved.sourcesFile);
|
|
90
|
+
if (saved.sourcesTreeW != null) setSourcesTreeW(saved.sourcesTreeW);
|
|
91
|
+
setHydrated(true);
|
|
92
|
+
}, []);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (typeof window === "undefined" || !hydrated) return;
|
|
95
|
+
const snapshot = {
|
|
96
|
+
open,
|
|
97
|
+
dock,
|
|
98
|
+
activeTab,
|
|
99
|
+
size,
|
|
100
|
+
maximized,
|
|
101
|
+
dataCart,
|
|
102
|
+
dataEntity,
|
|
103
|
+
view,
|
|
104
|
+
graphCarts: graphCarts ? [...graphCarts] : null,
|
|
105
|
+
hiddenEntities: [...hiddenEntities],
|
|
106
|
+
graphLeftW,
|
|
107
|
+
graphRightW,
|
|
108
|
+
consoleFilter,
|
|
109
|
+
consolePaused,
|
|
110
|
+
sourcesCart,
|
|
111
|
+
sourcesFile,
|
|
112
|
+
sourcesTreeW
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(snapshot));
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}, [
|
|
119
|
+
hydrated,
|
|
120
|
+
open,
|
|
121
|
+
dock,
|
|
122
|
+
activeTab,
|
|
123
|
+
size,
|
|
124
|
+
maximized,
|
|
125
|
+
dataCart,
|
|
126
|
+
dataEntity,
|
|
127
|
+
view,
|
|
128
|
+
graphCarts,
|
|
129
|
+
hiddenEntities,
|
|
130
|
+
graphLeftW,
|
|
131
|
+
graphRightW,
|
|
132
|
+
consoleFilter,
|
|
133
|
+
consolePaused,
|
|
134
|
+
sourcesCart,
|
|
135
|
+
sourcesFile,
|
|
136
|
+
sourcesTreeW
|
|
137
|
+
]);
|
|
40
138
|
const value = React2.useMemo(
|
|
41
139
|
() => ({
|
|
42
140
|
open,
|
|
@@ -53,15 +151,64 @@ var DevToolsProvider = ({ children, defaultActiveTab }) => {
|
|
|
53
151
|
dataCart,
|
|
54
152
|
setDataCart,
|
|
55
153
|
dataEntity,
|
|
56
|
-
setDataEntity
|
|
154
|
+
setDataEntity,
|
|
155
|
+
view,
|
|
156
|
+
setView,
|
|
157
|
+
graphCarts,
|
|
158
|
+
setGraphCarts,
|
|
159
|
+
hiddenEntities,
|
|
160
|
+
setHiddenEntities,
|
|
161
|
+
graphLeftW,
|
|
162
|
+
setGraphLeftW,
|
|
163
|
+
graphRightW,
|
|
164
|
+
setGraphRightW,
|
|
165
|
+
consoleFilter,
|
|
166
|
+
setConsoleFilter,
|
|
167
|
+
consolePaused,
|
|
168
|
+
setConsolePaused,
|
|
169
|
+
sourcesCart,
|
|
170
|
+
setSourcesCart,
|
|
171
|
+
sourcesFile,
|
|
172
|
+
setSourcesFile,
|
|
173
|
+
sourcesTreeW,
|
|
174
|
+
setSourcesTreeW
|
|
57
175
|
}),
|
|
58
|
-
[
|
|
176
|
+
[
|
|
177
|
+
open,
|
|
178
|
+
toggle,
|
|
179
|
+
dock,
|
|
180
|
+
activeTab,
|
|
181
|
+
size,
|
|
182
|
+
maximized,
|
|
183
|
+
dataCart,
|
|
184
|
+
dataEntity,
|
|
185
|
+
view,
|
|
186
|
+
graphCarts,
|
|
187
|
+
hiddenEntities,
|
|
188
|
+
graphLeftW,
|
|
189
|
+
graphRightW,
|
|
190
|
+
consoleFilter,
|
|
191
|
+
consolePaused,
|
|
192
|
+
sourcesCart,
|
|
193
|
+
sourcesFile,
|
|
194
|
+
sourcesTreeW
|
|
195
|
+
]
|
|
59
196
|
);
|
|
60
197
|
useEffect(() => {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
198
|
+
const onKey = (e) => {
|
|
199
|
+
if (e.ctrlKey && (e.key === "F12" || e.code === "F12")) {
|
|
200
|
+
e.preventDefault();
|
|
201
|
+
toggle();
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const onEvent = () => toggle();
|
|
205
|
+
window.addEventListener("keydown", onKey);
|
|
206
|
+
window.addEventListener("devtools-toggle", onEvent);
|
|
207
|
+
return () => {
|
|
208
|
+
window.removeEventListener("keydown", onKey);
|
|
209
|
+
window.removeEventListener("devtools-toggle", onEvent);
|
|
210
|
+
};
|
|
211
|
+
}, [toggle]);
|
|
65
212
|
return /* @__PURE__ */ jsx2(Ctx.Provider, { value, children });
|
|
66
213
|
};
|
|
67
214
|
function useDevTools() {
|
|
@@ -71,8 +218,9 @@ function useDevTools() {
|
|
|
71
218
|
}
|
|
72
219
|
|
|
73
220
|
// src/components/devtools/dev-tools.tsx
|
|
74
|
-
import
|
|
75
|
-
import {
|
|
221
|
+
import React12 from "react";
|
|
222
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
223
|
+
import { Maximize2, Minimize2, PanelBottom, PanelRight, X as X2 } from "lucide-react";
|
|
76
224
|
|
|
77
225
|
// src/lib/utils.ts
|
|
78
226
|
import { clsx } from "clsx";
|
|
@@ -125,14 +273,13 @@ var LEVEL_BADGE = {
|
|
|
125
273
|
};
|
|
126
274
|
var ConsoleTab = () => {
|
|
127
275
|
const { apiBaseUrl } = useDevToolsConfig();
|
|
276
|
+
const {
|
|
277
|
+
consoleFilter: filter,
|
|
278
|
+
setConsoleFilter: setFilter,
|
|
279
|
+
consolePaused: paused,
|
|
280
|
+
setConsolePaused: setPaused
|
|
281
|
+
} = useDevTools();
|
|
128
282
|
const [entries, setEntries] = React3.useState([]);
|
|
129
|
-
const [paused, setPaused] = React3.useState(false);
|
|
130
|
-
const [filter, setFilter] = React3.useState({
|
|
131
|
-
info: true,
|
|
132
|
-
warn: true,
|
|
133
|
-
error: true,
|
|
134
|
-
plain: true
|
|
135
|
-
});
|
|
136
283
|
const [expanded, setExpanded] = React3.useState({});
|
|
137
284
|
const scrollerRef = React3.useRef(null);
|
|
138
285
|
const stickToBottomRef = React3.useRef(true);
|
|
@@ -193,7 +340,7 @@ var ConsoleTab = () => {
|
|
|
193
340
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
194
341
|
if (socket) socket.close();
|
|
195
342
|
};
|
|
196
|
-
}, []);
|
|
343
|
+
}, [apiBaseUrl]);
|
|
197
344
|
const handleScroll = React3.useCallback(() => {
|
|
198
345
|
const el = scrollerRef.current;
|
|
199
346
|
if (!el) return;
|
|
@@ -407,8 +554,8 @@ var NetworkTab = () => {
|
|
|
407
554
|
var network_tab_default = NetworkTab;
|
|
408
555
|
|
|
409
556
|
// src/components/devtools/data-tab.tsx
|
|
410
|
-
import
|
|
411
|
-
import { Network, Table2 } from "lucide-react";
|
|
557
|
+
import React8 from "react";
|
|
558
|
+
import { Network, Table2, X } from "lucide-react";
|
|
412
559
|
|
|
413
560
|
// src/hooks/use-cartridge-info.ts
|
|
414
561
|
import { useEffect as useEffect2, useState } from "react";
|
|
@@ -438,6 +585,8 @@ function useLiveBulkRegistry() {
|
|
|
438
585
|
useEffect2(() => {
|
|
439
586
|
const ac = new AbortController();
|
|
440
587
|
let cancelled = false;
|
|
588
|
+
setLoading(true);
|
|
589
|
+
setError(null);
|
|
441
590
|
(async () => {
|
|
442
591
|
try {
|
|
443
592
|
const r = await fetch(`${apiBaseUrl}/_console/contracts`, {
|
|
@@ -460,7 +609,7 @@ function useLiveBulkRegistry() {
|
|
|
460
609
|
cancelled = true;
|
|
461
610
|
ac.abort();
|
|
462
611
|
};
|
|
463
|
-
}, []);
|
|
612
|
+
}, [apiBaseUrl]);
|
|
464
613
|
const carts = Object.keys(registry).sort();
|
|
465
614
|
return { registry, carts, loading, error };
|
|
466
615
|
}
|
|
@@ -481,8 +630,6 @@ var FRAME_DATA_END = 3;
|
|
|
481
630
|
var FRAME_DELTA_INS = 4;
|
|
482
631
|
var FRAME_DELTA_UPD = 5;
|
|
483
632
|
var FRAME_DELTA_DEL = 6;
|
|
484
|
-
var FRAME_SEARCH_RESULT = 7;
|
|
485
|
-
var FRAME_SEARCH_END = 8;
|
|
486
633
|
var FRAME_ERROR = 255;
|
|
487
634
|
var TYPE_U8 = 1;
|
|
488
635
|
var TYPE_U16 = 2;
|
|
@@ -543,27 +690,39 @@ var BulkDataStore = class {
|
|
|
543
690
|
this._fullRows = [];
|
|
544
691
|
this._fullTotalRows = 0;
|
|
545
692
|
this._idColIndex = -1;
|
|
693
|
+
this._query = "";
|
|
546
694
|
}
|
|
547
695
|
applySchema(schema) {
|
|
548
696
|
this.columns = schema.columns;
|
|
549
697
|
this.totalRows = schema.totalRows;
|
|
550
698
|
this.rows = [];
|
|
699
|
+
this._fullRows = [];
|
|
700
|
+
this._fullTotalRows = schema.totalRows;
|
|
701
|
+
this.searchActive = false;
|
|
702
|
+
this._query = "";
|
|
551
703
|
this._idColIndex = schema.columns.findIndex((c) => c.name === "id");
|
|
552
704
|
}
|
|
553
705
|
appendChunk(chunk) {
|
|
554
|
-
for (let i = 0; i < chunk.length; i++)
|
|
706
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
707
|
+
const row = chunk[i];
|
|
708
|
+
this._fullRows.push(row);
|
|
709
|
+
if (!this.searchActive) this.rows.push(row);
|
|
710
|
+
}
|
|
555
711
|
}
|
|
556
712
|
applyDelta(delta) {
|
|
557
|
-
const target = this.
|
|
713
|
+
const target = this._fullRows;
|
|
558
714
|
if (delta.op === FRAME_DELTA_INS && delta.rowData) {
|
|
559
715
|
target.push(delta.rowData);
|
|
560
|
-
|
|
561
|
-
|
|
716
|
+
this._fullTotalRows++;
|
|
717
|
+
this.totalRows++;
|
|
718
|
+
if (!this.searchActive) this.rows = this._fullRows;
|
|
719
|
+
else this._applySearch();
|
|
562
720
|
return;
|
|
563
721
|
}
|
|
564
722
|
if (delta.op === FRAME_DELTA_UPD && delta.rowData) {
|
|
565
723
|
const idx = this._findRowById(target, delta.rowData);
|
|
566
724
|
if (idx >= 0) target[idx] = delta.rowData;
|
|
725
|
+
if (this.searchActive) this._applySearch();
|
|
567
726
|
return;
|
|
568
727
|
}
|
|
569
728
|
if (delta.op === FRAME_DELTA_DEL && delta.id !== void 0) {
|
|
@@ -571,29 +730,30 @@ var BulkDataStore = class {
|
|
|
571
730
|
const idx = this._findRowByIdStr(target, idStr);
|
|
572
731
|
if (idx >= 0) {
|
|
573
732
|
target.splice(idx, 1);
|
|
574
|
-
|
|
575
|
-
|
|
733
|
+
this._fullTotalRows--;
|
|
734
|
+
this.totalRows--;
|
|
735
|
+
if (!this.searchActive) this.rows = this._fullRows;
|
|
736
|
+
else this._applySearch();
|
|
576
737
|
}
|
|
577
738
|
return;
|
|
578
739
|
}
|
|
579
740
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
this.
|
|
741
|
+
search(query) {
|
|
742
|
+
const q = query.trim().toLowerCase();
|
|
743
|
+
if (!q) {
|
|
744
|
+
this.exitSearch();
|
|
745
|
+
return;
|
|
584
746
|
}
|
|
585
747
|
this.searchActive = true;
|
|
586
|
-
this.
|
|
587
|
-
this.
|
|
588
|
-
this.rows = [];
|
|
748
|
+
this._query = q;
|
|
749
|
+
this._applySearch();
|
|
589
750
|
}
|
|
590
751
|
exitSearch() {
|
|
591
752
|
if (!this.searchActive) return;
|
|
592
753
|
this.rows = this._fullRows;
|
|
593
754
|
this.totalRows = this._fullTotalRows;
|
|
594
|
-
this._fullRows = [];
|
|
595
|
-
this._fullTotalRows = 0;
|
|
596
755
|
this.searchActive = false;
|
|
756
|
+
this._query = "";
|
|
597
757
|
}
|
|
598
758
|
getRowCount() {
|
|
599
759
|
return this.rows.length;
|
|
@@ -613,6 +773,12 @@ var BulkDataStore = class {
|
|
|
613
773
|
}
|
|
614
774
|
return -1;
|
|
615
775
|
}
|
|
776
|
+
_applySearch() {
|
|
777
|
+
this.rows = this._fullRows.filter(
|
|
778
|
+
(row) => row.some((value) => value.toLowerCase().includes(this._query))
|
|
779
|
+
);
|
|
780
|
+
this.totalRows = this.rows.length;
|
|
781
|
+
}
|
|
616
782
|
};
|
|
617
783
|
|
|
618
784
|
// src/components/devtools/data-browser/bulk-decoder.ts
|
|
@@ -702,8 +868,8 @@ function parseDataChunk(buf, columns) {
|
|
|
702
868
|
let offset = 0;
|
|
703
869
|
const frameType = view.getUint8(offset);
|
|
704
870
|
offset += 1;
|
|
705
|
-
if (frameType !== FRAME_DATA
|
|
706
|
-
throw new Error(`Expected DATA
|
|
871
|
+
if (frameType !== FRAME_DATA) {
|
|
872
|
+
throw new Error(`Expected DATA frame, got 0x${frameType.toString(16)}`);
|
|
707
873
|
}
|
|
708
874
|
offset += SID_BYTES;
|
|
709
875
|
const rowCount = view.getUint16(offset, true);
|
|
@@ -749,18 +915,9 @@ function parseError(buf) {
|
|
|
749
915
|
const bytes = new Uint8Array(buf, HEAD + 2, len);
|
|
750
916
|
return textDecoder.decode(bytes);
|
|
751
917
|
}
|
|
752
|
-
function encodeSchemaCmd(sid, cart, entity) {
|
|
753
|
-
return JSON.stringify({ cmd: "schema", sid, cart, entity });
|
|
754
|
-
}
|
|
755
918
|
function encodeStreamCmd(sid, cart, entity, opts) {
|
|
756
919
|
return JSON.stringify({ cmd: "sub", sid, cart, entity, ...opts });
|
|
757
920
|
}
|
|
758
|
-
function encodeSearchCmd(sid, query) {
|
|
759
|
-
return JSON.stringify({ cmd: "search", sid, q: query });
|
|
760
|
-
}
|
|
761
|
-
function encodeClearSearchCmd(sid) {
|
|
762
|
-
return JSON.stringify({ cmd: "clear_search", sid });
|
|
763
|
-
}
|
|
764
921
|
|
|
765
922
|
// src/hooks/use-bulk-stream.ts
|
|
766
923
|
var BulkStreamContext = createContext(null);
|
|
@@ -777,7 +934,7 @@ var _wsTokenCache = null;
|
|
|
777
934
|
var WS_TOKEN_TTL_MS = 6e4;
|
|
778
935
|
async function fetchWsToken(signal, apiBaseUrl) {
|
|
779
936
|
const now = Date.now();
|
|
780
|
-
if (_wsTokenCache && now - _wsTokenCache.at < WS_TOKEN_TTL_MS) {
|
|
937
|
+
if (_wsTokenCache?.apiBaseUrl === apiBaseUrl && now - _wsTokenCache.at < WS_TOKEN_TTL_MS) {
|
|
781
938
|
return _wsTokenCache.token;
|
|
782
939
|
}
|
|
783
940
|
const r = await fetch(`${apiBaseUrl}/api/auth/session/current`, {
|
|
@@ -790,7 +947,7 @@ async function fetchWsToken(signal, apiBaseUrl) {
|
|
|
790
947
|
const j = await r.json();
|
|
791
948
|
const token = j.session?.token;
|
|
792
949
|
if (!token) return null;
|
|
793
|
-
_wsTokenCache = { token, at: now };
|
|
950
|
+
_wsTokenCache = { apiBaseUrl, token, at: now };
|
|
794
951
|
return token;
|
|
795
952
|
}
|
|
796
953
|
function invalidateWsToken() {
|
|
@@ -838,7 +995,6 @@ function BulkStreamProvider({ registry, children }) {
|
|
|
838
995
|
streaming: false,
|
|
839
996
|
error: null,
|
|
840
997
|
streamRequested: false,
|
|
841
|
-
searchStreaming: false,
|
|
842
998
|
onRender: null,
|
|
843
999
|
listeners: /* @__PURE__ */ new Set()
|
|
844
1000
|
};
|
|
@@ -861,8 +1017,7 @@ function BulkStreamProvider({ registry, children }) {
|
|
|
861
1017
|
switch (ft) {
|
|
862
1018
|
case FRAME_SCHEMA: {
|
|
863
1019
|
const schema = parseSchema(buf);
|
|
864
|
-
|
|
865
|
-
else store.applySchema(schema);
|
|
1020
|
+
store.applySchema(schema);
|
|
866
1021
|
view.columns = schema.columns;
|
|
867
1022
|
view.totalRows = schema.totalRows;
|
|
868
1023
|
view.loadedRows = 0;
|
|
@@ -870,8 +1025,7 @@ function BulkStreamProvider({ registry, children }) {
|
|
|
870
1025
|
notify(view);
|
|
871
1026
|
break;
|
|
872
1027
|
}
|
|
873
|
-
case FRAME_DATA:
|
|
874
|
-
case FRAME_SEARCH_RESULT: {
|
|
1028
|
+
case FRAME_DATA: {
|
|
875
1029
|
const rows = parseDataChunk(buf, store.columns);
|
|
876
1030
|
store.appendChunk(rows);
|
|
877
1031
|
view.loadedRows = store.getRowCount();
|
|
@@ -882,11 +1036,6 @@ function BulkStreamProvider({ registry, children }) {
|
|
|
882
1036
|
view.streaming = false;
|
|
883
1037
|
notify(view);
|
|
884
1038
|
break;
|
|
885
|
-
case FRAME_SEARCH_END:
|
|
886
|
-
view.searchStreaming = false;
|
|
887
|
-
view.streaming = false;
|
|
888
|
-
notify(view);
|
|
889
|
-
break;
|
|
890
1039
|
case FRAME_DELTA_INS:
|
|
891
1040
|
case FRAME_DELTA_UPD:
|
|
892
1041
|
case FRAME_DELTA_DEL: {
|
|
@@ -910,6 +1059,12 @@ function BulkStreamProvider({ registry, children }) {
|
|
|
910
1059
|
let cancelled = false;
|
|
911
1060
|
let opened = false;
|
|
912
1061
|
let ws = null;
|
|
1062
|
+
sendQueueRef.current = [];
|
|
1063
|
+
sidCounterRef.current = 1;
|
|
1064
|
+
viewsBySidRef.current.clear();
|
|
1065
|
+
viewsByKeyRef.current.clear();
|
|
1066
|
+
preloadedRef.current = false;
|
|
1067
|
+
setConnected(false);
|
|
913
1068
|
setError(null);
|
|
914
1069
|
(async () => {
|
|
915
1070
|
let token = null;
|
|
@@ -959,7 +1114,7 @@ function BulkStreamProvider({ registry, children }) {
|
|
|
959
1114
|
if (wsRef.current === ws) wsRef.current = null;
|
|
960
1115
|
}
|
|
961
1116
|
};
|
|
962
|
-
}, []);
|
|
1117
|
+
}, [apiBaseUrl]);
|
|
963
1118
|
useEffect3(() => {
|
|
964
1119
|
if (!connected) return;
|
|
965
1120
|
if (preloadedRef.current) return;
|
|
@@ -969,28 +1124,21 @@ function BulkStreamProvider({ registry, children }) {
|
|
|
969
1124
|
for (const cart of keys) {
|
|
970
1125
|
for (const ent of registry[cart] ?? []) {
|
|
971
1126
|
const view = getView(cart, ent.name);
|
|
972
|
-
|
|
1127
|
+
ensureStreamed(view);
|
|
973
1128
|
}
|
|
974
1129
|
}
|
|
975
1130
|
}, [connected, registry]);
|
|
976
1131
|
const search = (view, query) => {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
notify(view);
|
|
983
|
-
view.onRender?.();
|
|
984
|
-
return;
|
|
985
|
-
}
|
|
986
|
-
view.searchStreaming = true;
|
|
987
|
-
sendCmd(encodeSearchCmd(view.sid, query));
|
|
1132
|
+
view.store.search(query);
|
|
1133
|
+
view.totalRows = view.store.totalRows;
|
|
1134
|
+
view.loadedRows = view.store.getRowCount();
|
|
1135
|
+
notify(view);
|
|
1136
|
+
view.onRender?.();
|
|
988
1137
|
};
|
|
989
1138
|
const clearSearch = (view) => {
|
|
990
1139
|
view.store.exitSearch();
|
|
991
1140
|
view.totalRows = view.store.totalRows;
|
|
992
1141
|
view.loadedRows = view.store.getRowCount();
|
|
993
|
-
sendCmd(encodeClearSearchCmd(view.sid));
|
|
994
1142
|
notify(view);
|
|
995
1143
|
view.onRender?.();
|
|
996
1144
|
};
|
|
@@ -1057,11 +1205,210 @@ function useBulkSubscription(cart, entity) {
|
|
|
1057
1205
|
}
|
|
1058
1206
|
|
|
1059
1207
|
// src/components/devtools/data-browser/data-table.tsx
|
|
1208
|
+
import React6 from "react";
|
|
1209
|
+
|
|
1210
|
+
// src/components/devtools/data-browser/value-popover.tsx
|
|
1060
1211
|
import React5 from "react";
|
|
1061
|
-
import {
|
|
1212
|
+
import { createPortal } from "react-dom";
|
|
1213
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1214
|
+
var WIDTH = 420;
|
|
1215
|
+
var INT_TYPES = /* @__PURE__ */ new Set([
|
|
1216
|
+
TYPE_U8,
|
|
1217
|
+
TYPE_U16,
|
|
1218
|
+
TYPE_U32,
|
|
1219
|
+
TYPE_U64,
|
|
1220
|
+
TYPE_S8,
|
|
1221
|
+
TYPE_S16,
|
|
1222
|
+
TYPE_S32,
|
|
1223
|
+
TYPE_S64
|
|
1224
|
+
]);
|
|
1225
|
+
var FLOAT_TYPES = /* @__PURE__ */ new Set([TYPE_FLOAT32, TYPE_FLOAT64]);
|
|
1226
|
+
function position(rect) {
|
|
1227
|
+
const margin = 12;
|
|
1228
|
+
const left = Math.max(
|
|
1229
|
+
margin,
|
|
1230
|
+
Math.min(rect.left, window.innerWidth - WIDTH - margin)
|
|
1231
|
+
);
|
|
1232
|
+
const roomBelow = window.innerHeight - rect.bottom;
|
|
1233
|
+
const top = roomBelow >= 180 ? rect.bottom + 6 : Math.max(margin, rect.top - 188);
|
|
1234
|
+
return { left, top, width: WIDTH };
|
|
1235
|
+
}
|
|
1236
|
+
function coerce(draft, colType) {
|
|
1237
|
+
if (colType === TYPE_BOOL) return { value: draft === "true" };
|
|
1238
|
+
if (INT_TYPES.has(colType)) {
|
|
1239
|
+
const n = Number(draft);
|
|
1240
|
+
if (!Number.isInteger(n)) return { error: "Expected an integer" };
|
|
1241
|
+
return { value: n };
|
|
1242
|
+
}
|
|
1243
|
+
if (FLOAT_TYPES.has(colType)) {
|
|
1244
|
+
const n = Number(draft);
|
|
1245
|
+
if (!Number.isFinite(n)) return { error: "Expected a number" };
|
|
1246
|
+
return { value: n };
|
|
1247
|
+
}
|
|
1248
|
+
return { value: draft };
|
|
1249
|
+
}
|
|
1250
|
+
var ValuePopover = ({
|
|
1251
|
+
data,
|
|
1252
|
+
onClose
|
|
1253
|
+
}) => {
|
|
1254
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
1255
|
+
const [copied, setCopied] = React5.useState(false);
|
|
1256
|
+
const [draft, setDraft] = React5.useState(data.value);
|
|
1257
|
+
const [saving, setSaving] = React5.useState(false);
|
|
1258
|
+
const [error, setError] = React5.useState(null);
|
|
1259
|
+
React5.useEffect(() => {
|
|
1260
|
+
setDraft(data.value);
|
|
1261
|
+
setError(null);
|
|
1262
|
+
}, [data.value, data.r, data.c]);
|
|
1263
|
+
React5.useEffect(() => {
|
|
1264
|
+
const onKey = (e) => {
|
|
1265
|
+
if (e.key === "Escape") onClose();
|
|
1266
|
+
};
|
|
1267
|
+
document.addEventListener("keydown", onKey);
|
|
1268
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
1269
|
+
}, [onClose]);
|
|
1270
|
+
const copy = async () => {
|
|
1271
|
+
try {
|
|
1272
|
+
await navigator.clipboard.writeText(data.value);
|
|
1273
|
+
setCopied(true);
|
|
1274
|
+
} catch {
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
const save = async () => {
|
|
1278
|
+
const result = coerce(draft, data.colType);
|
|
1279
|
+
if ("error" in result) {
|
|
1280
|
+
setError(result.error);
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
setSaving(true);
|
|
1284
|
+
setError(null);
|
|
1285
|
+
try {
|
|
1286
|
+
const r = await fetch(
|
|
1287
|
+
`${apiBaseUrl}/api/${data.cart}/${data.entity.toLowerCase()}/${encodeURIComponent(data.rowId)}`,
|
|
1288
|
+
{
|
|
1289
|
+
method: "PUT",
|
|
1290
|
+
credentials: "include",
|
|
1291
|
+
headers: { "content-type": "application/json" },
|
|
1292
|
+
body: JSON.stringify({ [data.colName]: result.value })
|
|
1293
|
+
}
|
|
1294
|
+
);
|
|
1295
|
+
if (!r.ok) {
|
|
1296
|
+
setError(await r.text() || `HTTP ${r.status}`);
|
|
1297
|
+
setSaving(false);
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
onClose();
|
|
1301
|
+
} catch (e) {
|
|
1302
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1303
|
+
setSaving(false);
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
const isBool = data.colType === TYPE_BOOL;
|
|
1307
|
+
const isNumeric = INT_TYPES.has(data.colType) || FLOAT_TYPES.has(data.colType);
|
|
1308
|
+
return createPortal(
|
|
1309
|
+
/* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1310
|
+
/* @__PURE__ */ jsx5("div", { className: "fixed inset-0 z-[60]", onClick: onClose }),
|
|
1311
|
+
/* @__PURE__ */ jsxs2(
|
|
1312
|
+
"div",
|
|
1313
|
+
{
|
|
1314
|
+
className: cn(
|
|
1315
|
+
"nimbit-devtools dark fixed z-[61] rounded-md border border-border bg-popover p-3",
|
|
1316
|
+
"text-[12px] text-popover-foreground shadow-lg"
|
|
1317
|
+
),
|
|
1318
|
+
style: position(data.rect),
|
|
1319
|
+
onClick: (e) => e.stopPropagation(),
|
|
1320
|
+
children: [
|
|
1321
|
+
/* @__PURE__ */ jsxs2("div", { className: "mb-2 flex items-center justify-between gap-3", children: [
|
|
1322
|
+
/* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
|
|
1323
|
+
/* @__PURE__ */ jsx5("div", { className: "truncate font-medium text-foreground", children: data.colName }),
|
|
1324
|
+
/* @__PURE__ */ jsxs2("div", { className: "text-[11px] text-muted-foreground", children: [
|
|
1325
|
+
"Row ",
|
|
1326
|
+
data.r + 1
|
|
1327
|
+
] })
|
|
1328
|
+
] }),
|
|
1329
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex shrink-0 items-center gap-1.5", children: [
|
|
1330
|
+
/* @__PURE__ */ jsx5(
|
|
1331
|
+
"button",
|
|
1332
|
+
{
|
|
1333
|
+
type: "button",
|
|
1334
|
+
onClick: copy,
|
|
1335
|
+
className: cn(
|
|
1336
|
+
"rounded border border-border px-2 py-1 text-[11px] leading-none",
|
|
1337
|
+
"hover:bg-accent hover:text-accent-foreground"
|
|
1338
|
+
),
|
|
1339
|
+
children: copied ? "Copied" : "Copy"
|
|
1340
|
+
}
|
|
1341
|
+
),
|
|
1342
|
+
data.editable ? /* @__PURE__ */ jsx5(
|
|
1343
|
+
"button",
|
|
1344
|
+
{
|
|
1345
|
+
type: "button",
|
|
1346
|
+
onClick: save,
|
|
1347
|
+
disabled: saving,
|
|
1348
|
+
className: cn(
|
|
1349
|
+
"rounded border border-border bg-primary/90 px-2 py-1 text-[11px] leading-none text-primary-foreground",
|
|
1350
|
+
"hover:bg-primary disabled:opacity-50"
|
|
1351
|
+
),
|
|
1352
|
+
children: saving ? "Saving\u2026" : "Save"
|
|
1353
|
+
}
|
|
1354
|
+
) : null
|
|
1355
|
+
] })
|
|
1356
|
+
] }),
|
|
1357
|
+
data.editable ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1358
|
+
isBool ? /* @__PURE__ */ jsxs2(
|
|
1359
|
+
"select",
|
|
1360
|
+
{
|
|
1361
|
+
value: draft,
|
|
1362
|
+
disabled: saving,
|
|
1363
|
+
onChange: (e) => setDraft(e.target.value),
|
|
1364
|
+
className: "w-full rounded-sm border border-border bg-background px-2 py-1 text-[12px] outline-none focus:border-accent-foreground/30",
|
|
1365
|
+
children: [
|
|
1366
|
+
/* @__PURE__ */ jsx5("option", { value: "true", children: "true" }),
|
|
1367
|
+
/* @__PURE__ */ jsx5("option", { value: "false", children: "false" })
|
|
1368
|
+
]
|
|
1369
|
+
}
|
|
1370
|
+
) : isNumeric ? /* @__PURE__ */ jsx5(
|
|
1371
|
+
"input",
|
|
1372
|
+
{
|
|
1373
|
+
type: "number",
|
|
1374
|
+
value: draft,
|
|
1375
|
+
disabled: saving,
|
|
1376
|
+
autoFocus: true,
|
|
1377
|
+
onChange: (e) => setDraft(e.target.value),
|
|
1378
|
+
onKeyDown: (e) => {
|
|
1379
|
+
if (e.key === "Enter") void save();
|
|
1380
|
+
},
|
|
1381
|
+
className: "w-full rounded-sm border border-border bg-background px-2 py-1 font-mono text-[12px] outline-none focus:border-accent-foreground/30"
|
|
1382
|
+
}
|
|
1383
|
+
) : /* @__PURE__ */ jsx5(
|
|
1384
|
+
"textarea",
|
|
1385
|
+
{
|
|
1386
|
+
value: draft,
|
|
1387
|
+
disabled: saving,
|
|
1388
|
+
autoFocus: true,
|
|
1389
|
+
onChange: (e) => setDraft(e.target.value),
|
|
1390
|
+
className: "max-h-48 min-h-[3rem] w-full resize-y overflow-auto rounded-sm border border-border bg-background p-2 font-mono text-[11px] leading-5 outline-none focus:border-accent-foreground/30"
|
|
1391
|
+
}
|
|
1392
|
+
),
|
|
1393
|
+
error ? /* @__PURE__ */ jsx5("div", { className: "mt-2 break-words text-[11px] text-red-400", children: error }) : null
|
|
1394
|
+
] }) : /* @__PURE__ */ jsx5("div", { className: "max-h-48 overflow-auto rounded-sm bg-muted/40 p-2 font-mono text-[11px] leading-5 whitespace-pre-wrap break-words", children: data.value.length > 0 ? data.value : /* @__PURE__ */ jsx5("span", { className: "font-sans italic text-muted-foreground", children: "(empty)" }) })
|
|
1395
|
+
]
|
|
1396
|
+
}
|
|
1397
|
+
)
|
|
1398
|
+
] }),
|
|
1399
|
+
document.body
|
|
1400
|
+
);
|
|
1401
|
+
};
|
|
1402
|
+
var value_popover_default = ValuePopover;
|
|
1403
|
+
|
|
1404
|
+
// src/components/devtools/data-browser/data-table.tsx
|
|
1405
|
+
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1062
1406
|
var ROW_H = 22;
|
|
1063
1407
|
var OVERSCAN = 6;
|
|
1064
1408
|
var MIN_COL_W = 80;
|
|
1409
|
+
var GUTTER_W = 32;
|
|
1410
|
+
var READONLY_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt"]);
|
|
1411
|
+
var READONLY_TYPES = /* @__PURE__ */ new Set([TYPE_DATETIME, TYPE_DOCUMENT]);
|
|
1065
1412
|
function colWidth(name) {
|
|
1066
1413
|
if (name === "id") return 220;
|
|
1067
1414
|
if (name === "createdAt" || name === "updatedAt") return 180;
|
|
@@ -1071,16 +1418,18 @@ function colWidth(name) {
|
|
|
1071
1418
|
}
|
|
1072
1419
|
var DataTable = ({ cart, entity, searchFields, onSelectRow }) => {
|
|
1073
1420
|
const stream = useBulkSubscription(cart, entity);
|
|
1074
|
-
const scrollerRef =
|
|
1075
|
-
const [scrollTop, setScrollTop] =
|
|
1076
|
-
const [viewportH, setViewportH] =
|
|
1077
|
-
const [query, setQuery] =
|
|
1078
|
-
const [
|
|
1079
|
-
|
|
1421
|
+
const scrollerRef = React6.useRef(null);
|
|
1422
|
+
const [scrollTop, setScrollTop] = React6.useState(0);
|
|
1423
|
+
const [viewportH, setViewportH] = React6.useState(0);
|
|
1424
|
+
const [query, setQuery] = React6.useState("");
|
|
1425
|
+
const [selected, setSelected] = React6.useState(null);
|
|
1426
|
+
const [popover, setPopover] = React6.useState(null);
|
|
1427
|
+
const [tick, setTick] = React6.useState(0);
|
|
1428
|
+
React6.useEffect(() => {
|
|
1080
1429
|
stream.setOnRender(() => setTick((n) => n + 1));
|
|
1081
1430
|
return () => stream.setOnRender(null);
|
|
1082
1431
|
}, [stream]);
|
|
1083
|
-
|
|
1432
|
+
React6.useEffect(() => {
|
|
1084
1433
|
const el = scrollerRef.current;
|
|
1085
1434
|
if (!el) return;
|
|
1086
1435
|
const ro = new ResizeObserver(() => setViewportH(el.clientHeight));
|
|
@@ -1110,10 +1459,42 @@ var DataTable = ({ cart, entity, searchFields, onSelectRow }) => {
|
|
|
1110
1459
|
onSelectRow(out);
|
|
1111
1460
|
};
|
|
1112
1461
|
const searchDisabled = searchFields.length === 0;
|
|
1113
|
-
const totalColW = columns.reduce((s, c) => s + colWidth(c.name), 0);
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1462
|
+
const totalColW = GUTTER_W + columns.reduce((s, c) => s + colWidth(c.name), 0);
|
|
1463
|
+
const idColIndex = columns.findIndex((c) => c.name === "id");
|
|
1464
|
+
const scrollRowIntoView = (r) => {
|
|
1465
|
+
const el = scrollerRef.current;
|
|
1466
|
+
if (!el) return;
|
|
1467
|
+
const top = r * ROW_H;
|
|
1468
|
+
if (top < el.scrollTop) el.scrollTop = top;
|
|
1469
|
+
else if (top + ROW_H > el.scrollTop + el.clientHeight)
|
|
1470
|
+
el.scrollTop = top + ROW_H - el.clientHeight;
|
|
1471
|
+
};
|
|
1472
|
+
const onKeyDown = (e) => {
|
|
1473
|
+
if (rows.length === 0 || columns.length === 0) return;
|
|
1474
|
+
if ((e.metaKey || e.ctrlKey) && (e.key === "c" || e.key === "C")) {
|
|
1475
|
+
if (!selected) return;
|
|
1476
|
+
const v = rows[selected.r]?.[selected.c] ?? "";
|
|
1477
|
+
void navigator.clipboard?.writeText(v);
|
|
1478
|
+
e.preventDefault();
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
const cur = selected ?? { r: start, c: 0 };
|
|
1482
|
+
let { r, c } = cur;
|
|
1483
|
+
if (e.key === "ArrowUp") r--;
|
|
1484
|
+
else if (e.key === "ArrowDown") r++;
|
|
1485
|
+
else if (e.key === "ArrowLeft") c--;
|
|
1486
|
+
else if (e.key === "ArrowRight") c++;
|
|
1487
|
+
else return;
|
|
1488
|
+
e.preventDefault();
|
|
1489
|
+
r = Math.max(0, Math.min(rows.length - 1, r));
|
|
1490
|
+
c = Math.max(0, Math.min(columns.length - 1, c));
|
|
1491
|
+
setSelected({ r, c });
|
|
1492
|
+
setPopover(null);
|
|
1493
|
+
scrollRowIntoView(r);
|
|
1494
|
+
};
|
|
1495
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex h-full flex-col bg-background", children: [
|
|
1496
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex h-7 shrink-0 items-center gap-2 border-b border-border px-2", children: [
|
|
1497
|
+
/* @__PURE__ */ jsx6(
|
|
1117
1498
|
"input",
|
|
1118
1499
|
{
|
|
1119
1500
|
type: "text",
|
|
@@ -1129,7 +1510,7 @@ var DataTable = ({ cart, entity, searchFields, onSelectRow }) => {
|
|
|
1129
1510
|
)
|
|
1130
1511
|
}
|
|
1131
1512
|
),
|
|
1132
|
-
/* @__PURE__ */
|
|
1513
|
+
/* @__PURE__ */ jsx6("div", { className: "shrink-0 text-[11px] text-muted-foreground tabular-nums", children: stream.error ? /* @__PURE__ */ jsx6("span", { className: "text-red-400", children: stream.error }) : !stream.connected ? /* @__PURE__ */ jsx6("span", { children: "connecting\u2026" }) : /* @__PURE__ */ jsxs3("span", { children: [
|
|
1133
1514
|
stream.loadedRows.toLocaleString(),
|
|
1134
1515
|
" / ",
|
|
1135
1516
|
stream.totalRows.toLocaleString(),
|
|
@@ -1137,75 +1518,129 @@ var DataTable = ({ cart, entity, searchFields, onSelectRow }) => {
|
|
|
1137
1518
|
stream.streaming ? " \xB7 streaming" : ""
|
|
1138
1519
|
] }) })
|
|
1139
1520
|
] }),
|
|
1140
|
-
columns.length === 0 ? /* @__PURE__ */
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1521
|
+
columns.length === 0 ? /* @__PURE__ */ jsx6("div", { className: "flex flex-1 items-center justify-center text-[12px] text-muted-foreground", children: stream.error ? stream.error : "Waiting for schema\u2026" }) : /* @__PURE__ */ jsx6(
|
|
1522
|
+
"div",
|
|
1523
|
+
{
|
|
1524
|
+
ref: scrollerRef,
|
|
1525
|
+
tabIndex: 0,
|
|
1526
|
+
onKeyDown,
|
|
1527
|
+
onScroll: (e) => {
|
|
1528
|
+
setScrollTop(e.target.scrollTop);
|
|
1529
|
+
if (popover) setPopover(null);
|
|
1530
|
+
},
|
|
1531
|
+
className: "group/table relative min-h-0 flex-1 overflow-auto font-mono outline-none",
|
|
1532
|
+
children: /* @__PURE__ */ jsxs3("div", { style: { width: totalColW }, children: [
|
|
1533
|
+
/* @__PURE__ */ jsxs3(
|
|
1150
1534
|
"div",
|
|
1151
1535
|
{
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
children:
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1536
|
+
className: "sticky top-0 z-10 flex h-6 select-none border-b border-border bg-background text-[11px] uppercase tracking-wider text-muted-foreground",
|
|
1537
|
+
style: { width: totalColW },
|
|
1538
|
+
children: [
|
|
1539
|
+
/* @__PURE__ */ jsx6(
|
|
1540
|
+
"div",
|
|
1541
|
+
{
|
|
1542
|
+
style: { width: GUTTER_W },
|
|
1543
|
+
className: "shrink-0 border-r border-border"
|
|
1544
|
+
}
|
|
1545
|
+
),
|
|
1546
|
+
columns.map((c) => /* @__PURE__ */ jsx6(
|
|
1547
|
+
"div",
|
|
1548
|
+
{
|
|
1549
|
+
style: { width: colWidth(c.name) },
|
|
1550
|
+
className: "flex items-center overflow-hidden border-r border-border px-2",
|
|
1551
|
+
children: /* @__PURE__ */ jsx6("span", { className: "truncate", children: c.name })
|
|
1552
|
+
},
|
|
1553
|
+
c.name
|
|
1554
|
+
))
|
|
1555
|
+
]
|
|
1556
|
+
}
|
|
1557
|
+
),
|
|
1558
|
+
/* @__PURE__ */ jsx6("div", { style: { height: totalH, width: totalColW, position: "relative" }, children: rows.slice(start, end).map((row, i) => {
|
|
1170
1559
|
const rowIdx = start + i;
|
|
1171
|
-
return /* @__PURE__ */
|
|
1560
|
+
return /* @__PURE__ */ jsxs3(
|
|
1172
1561
|
"div",
|
|
1173
1562
|
{
|
|
1174
|
-
onClick: () => handleRowClick(rowIdx),
|
|
1175
1563
|
style: {
|
|
1176
1564
|
top: rowIdx * ROW_H,
|
|
1177
1565
|
height: ROW_H,
|
|
1178
1566
|
width: totalColW
|
|
1179
1567
|
},
|
|
1180
1568
|
className: cn(
|
|
1181
|
-
"absolute left-0 flex
|
|
1182
|
-
rowIdx % 2 === 0 ? "bg-background" : "bg-muted/20"
|
|
1183
|
-
"hover:bg-accent hover:text-accent-foreground"
|
|
1569
|
+
"absolute left-0 flex text-[12px] leading-none",
|
|
1570
|
+
rowIdx % 2 === 0 ? "bg-background" : "bg-muted/20"
|
|
1184
1571
|
),
|
|
1185
|
-
children:
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1572
|
+
children: [
|
|
1573
|
+
/* @__PURE__ */ jsx6(
|
|
1574
|
+
"button",
|
|
1575
|
+
{
|
|
1576
|
+
type: "button",
|
|
1577
|
+
title: "Open row detail",
|
|
1578
|
+
onClick: () => handleRowClick(rowIdx),
|
|
1579
|
+
style: { width: GUTTER_W },
|
|
1580
|
+
className: cn(
|
|
1581
|
+
"flex shrink-0 items-center justify-center border-r border-border/60",
|
|
1582
|
+
"cursor-pointer text-[10px] tabular-nums text-muted-foreground/60",
|
|
1583
|
+
"hover:bg-accent hover:text-accent-foreground"
|
|
1584
|
+
),
|
|
1585
|
+
children: rowIdx + 1
|
|
1586
|
+
}
|
|
1587
|
+
),
|
|
1588
|
+
columns.map((c, ci) => {
|
|
1589
|
+
const isSel = selected?.r === rowIdx && selected?.c === ci;
|
|
1590
|
+
return /* @__PURE__ */ jsx6(
|
|
1591
|
+
"div",
|
|
1592
|
+
{
|
|
1593
|
+
"data-selected": isSel,
|
|
1594
|
+
onClick: (e) => {
|
|
1595
|
+
e.stopPropagation();
|
|
1596
|
+
setSelected({ r: rowIdx, c: ci });
|
|
1597
|
+
setPopover(null);
|
|
1598
|
+
},
|
|
1599
|
+
onDoubleClick: (e) => {
|
|
1600
|
+
e.stopPropagation();
|
|
1601
|
+
setSelected({ r: rowIdx, c: ci });
|
|
1602
|
+
const editable = idColIndex >= 0 && !READONLY_FIELDS.has(c.name) && !READONLY_TYPES.has(c.type);
|
|
1603
|
+
setPopover({
|
|
1604
|
+
r: rowIdx,
|
|
1605
|
+
c: ci,
|
|
1606
|
+
colName: c.name,
|
|
1607
|
+
value: row[ci] ?? "",
|
|
1608
|
+
rect: e.currentTarget.getBoundingClientRect(),
|
|
1609
|
+
cart,
|
|
1610
|
+
entity,
|
|
1611
|
+
rowId: idColIndex >= 0 ? row[idColIndex] ?? "" : "",
|
|
1612
|
+
colType: c.type,
|
|
1613
|
+
editable
|
|
1614
|
+
});
|
|
1615
|
+
},
|
|
1616
|
+
style: { width: colWidth(c.name) },
|
|
1617
|
+
className: cn(
|
|
1618
|
+
"flex cursor-pointer items-center overflow-hidden border-r border-border/60 px-2",
|
|
1619
|
+
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground",
|
|
1620
|
+
"data-[selected=true]:[box-shadow:inset_0_0_0_1px_#60a5fa]"
|
|
1621
|
+
),
|
|
1622
|
+
title: row[ci],
|
|
1623
|
+
children: /* @__PURE__ */ jsx6("span", { className: "truncate", children: row[ci] })
|
|
1624
|
+
},
|
|
1625
|
+
c.name
|
|
1626
|
+
);
|
|
1627
|
+
})
|
|
1628
|
+
]
|
|
1195
1629
|
},
|
|
1196
1630
|
rowIdx
|
|
1197
1631
|
);
|
|
1198
1632
|
}) })
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1633
|
+
] })
|
|
1634
|
+
}
|
|
1635
|
+
),
|
|
1636
|
+
popover ? /* @__PURE__ */ jsx6(value_popover_default, { data: popover, onClose: () => setPopover(null) }) : null,
|
|
1637
|
+
tick < 0 && /* @__PURE__ */ jsx6("span", {})
|
|
1203
1638
|
] });
|
|
1204
1639
|
};
|
|
1205
1640
|
var data_table_default = DataTable;
|
|
1206
1641
|
|
|
1207
1642
|
// src/components/devtools/entity-graph/diagram-tab.tsx
|
|
1208
|
-
import
|
|
1643
|
+
import React7 from "react";
|
|
1209
1644
|
import {
|
|
1210
1645
|
Background,
|
|
1211
1646
|
BackgroundVariant,
|
|
@@ -1216,18 +1651,17 @@ import {
|
|
|
1216
1651
|
useNodesInitialized,
|
|
1217
1652
|
useReactFlow
|
|
1218
1653
|
} from "@xyflow/react";
|
|
1219
|
-
import { Database as Database2, Eye, EyeOff, GitBranch, Rows3 } from "lucide-react";
|
|
1220
1654
|
|
|
1221
1655
|
// src/components/devtools/entity-graph/entity-node.tsx
|
|
1222
1656
|
import { Handle, Position } from "@xyflow/react";
|
|
1223
1657
|
import { Database, FileText, KeyRound, Link2 } from "lucide-react";
|
|
1224
|
-
import { Fragment, jsx as
|
|
1658
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1225
1659
|
function EntityNode({ data, selected }) {
|
|
1226
1660
|
const node = data;
|
|
1227
1661
|
const highlight = node.highlight;
|
|
1228
1662
|
const highlightedFields = new Set(highlight?.fields ?? []);
|
|
1229
1663
|
const hiddenHandleClass = "!h-1 !w-1 !border-0 !bg-transparent !opacity-0";
|
|
1230
|
-
return /* @__PURE__ */
|
|
1664
|
+
return /* @__PURE__ */ jsxs4(
|
|
1231
1665
|
"div",
|
|
1232
1666
|
{
|
|
1233
1667
|
className: [
|
|
@@ -1237,21 +1671,20 @@ function EntityNode({ data, selected }) {
|
|
|
1237
1671
|
selected && !highlight?.focused ? "ring-2 ring-zinc-100/10" : ""
|
|
1238
1672
|
].join(" "),
|
|
1239
1673
|
children: [
|
|
1240
|
-
/* @__PURE__ */
|
|
1241
|
-
/* @__PURE__ */
|
|
1242
|
-
/* @__PURE__ */
|
|
1243
|
-
/* @__PURE__ */
|
|
1244
|
-
/* @__PURE__ */ jsx6("span", { className: "shrink-0 rounded bg-zinc-700 px-1.5 py-0.5 text-[10px] font-medium tabular-nums text-zinc-200", children: node.fieldCount })
|
|
1674
|
+
/* @__PURE__ */ jsxs4("div", { className: "rounded-t-xl border-b border-zinc-700 bg-zinc-800 px-3 py-2", children: [
|
|
1675
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1676
|
+
/* @__PURE__ */ jsx7(Database, { className: "h-3.5 w-3.5 shrink-0 text-zinc-400" }),
|
|
1677
|
+
/* @__PURE__ */ jsx7("span", { className: "min-w-0 flex-1 truncate text-[13px] font-semibold leading-none text-zinc-50", children: node.entity })
|
|
1245
1678
|
] }),
|
|
1246
|
-
/* @__PURE__ */
|
|
1679
|
+
/* @__PURE__ */ jsx7("div", { className: "mt-1 truncate text-[10px] text-zinc-500", children: node.cartridge })
|
|
1247
1680
|
] }),
|
|
1248
|
-
/* @__PURE__ */
|
|
1681
|
+
/* @__PURE__ */ jsx7("div", { className: "bg-zinc-900 py-1", children: node.fields.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "px-3 py-2 text-[10px] text-zinc-500", children: "No fields" }) : node.fields.map((field) => {
|
|
1249
1682
|
const type = `${field.type}${field.array ? "[]" : ""}${field.optional ? "?" : ""}`;
|
|
1250
1683
|
const isId = field.displayName.toLowerCase() === "id";
|
|
1251
1684
|
const isRelation = field.kind === "relation";
|
|
1252
1685
|
const isDocument = field.kind === "document";
|
|
1253
1686
|
const fieldHighlighted = highlightedFields.has(field.displayName);
|
|
1254
|
-
return /* @__PURE__ */
|
|
1687
|
+
return /* @__PURE__ */ jsxs4(
|
|
1255
1688
|
"div",
|
|
1256
1689
|
{
|
|
1257
1690
|
className: [
|
|
@@ -1259,8 +1692,8 @@ function EntityNode({ data, selected }) {
|
|
|
1259
1692
|
fieldHighlighted ? "bg-blue-500/15 text-blue-200" : ""
|
|
1260
1693
|
].join(" "),
|
|
1261
1694
|
children: [
|
|
1262
|
-
isId ? /* @__PURE__ */
|
|
1263
|
-
/* @__PURE__ */
|
|
1695
|
+
isId ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1696
|
+
/* @__PURE__ */ jsx7(
|
|
1264
1697
|
Handle,
|
|
1265
1698
|
{
|
|
1266
1699
|
id: `target-${field.displayName}-left`,
|
|
@@ -1270,7 +1703,7 @@ function EntityNode({ data, selected }) {
|
|
|
1270
1703
|
style: { top: "50%" }
|
|
1271
1704
|
}
|
|
1272
1705
|
),
|
|
1273
|
-
/* @__PURE__ */
|
|
1706
|
+
/* @__PURE__ */ jsx7(
|
|
1274
1707
|
Handle,
|
|
1275
1708
|
{
|
|
1276
1709
|
id: `target-${field.displayName}-right`,
|
|
@@ -1281,8 +1714,8 @@ function EntityNode({ data, selected }) {
|
|
|
1281
1714
|
}
|
|
1282
1715
|
)
|
|
1283
1716
|
] }) : null,
|
|
1284
|
-
isRelation ? /* @__PURE__ */
|
|
1285
|
-
/* @__PURE__ */
|
|
1717
|
+
isRelation ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
1718
|
+
/* @__PURE__ */ jsx7(
|
|
1286
1719
|
Handle,
|
|
1287
1720
|
{
|
|
1288
1721
|
id: `source-${field.displayName}-left`,
|
|
@@ -1292,7 +1725,7 @@ function EntityNode({ data, selected }) {
|
|
|
1292
1725
|
style: { top: "50%" }
|
|
1293
1726
|
}
|
|
1294
1727
|
),
|
|
1295
|
-
/* @__PURE__ */
|
|
1728
|
+
/* @__PURE__ */ jsx7(
|
|
1296
1729
|
Handle,
|
|
1297
1730
|
{
|
|
1298
1731
|
id: `source-${field.displayName}-right`,
|
|
@@ -1303,24 +1736,24 @@ function EntityNode({ data, selected }) {
|
|
|
1303
1736
|
}
|
|
1304
1737
|
)
|
|
1305
1738
|
] }) : null,
|
|
1306
|
-
/* @__PURE__ */
|
|
1307
|
-
isId ? /* @__PURE__ */
|
|
1308
|
-
isRelation ? /* @__PURE__ */
|
|
1309
|
-
isDocument ? /* @__PURE__ */
|
|
1310
|
-
/* @__PURE__ */
|
|
1739
|
+
/* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-1.5", children: [
|
|
1740
|
+
isId ? /* @__PURE__ */ jsx7(KeyRound, { className: "h-3 w-3 shrink-0 text-zinc-500" }) : null,
|
|
1741
|
+
isRelation ? /* @__PURE__ */ jsx7(Link2, { className: ["h-3 w-3 shrink-0", fieldHighlighted ? "text-blue-300" : "text-blue-400"].join(" ") }) : null,
|
|
1742
|
+
isDocument ? /* @__PURE__ */ jsx7(FileText, { className: "h-3 w-3 shrink-0 text-zinc-500" }) : null,
|
|
1743
|
+
/* @__PURE__ */ jsx7("span", { className: "truncate", children: field.displayName })
|
|
1311
1744
|
] }),
|
|
1312
|
-
/* @__PURE__ */
|
|
1745
|
+
/* @__PURE__ */ jsx7("span", { className: "max-w-[96px] truncate text-right text-zinc-500", children: type })
|
|
1313
1746
|
]
|
|
1314
1747
|
},
|
|
1315
1748
|
`${field.name}:${field.displayName}`
|
|
1316
1749
|
);
|
|
1317
1750
|
}) }),
|
|
1318
|
-
/* @__PURE__ */
|
|
1319
|
-
/* @__PURE__ */
|
|
1751
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-2 rounded-b-xl border-t border-zinc-700 bg-zinc-800 px-3 py-1.5 text-[10px] text-zinc-500", children: [
|
|
1752
|
+
/* @__PURE__ */ jsxs4("span", { className: "tabular-nums", children: [
|
|
1320
1753
|
node.rowCount.toLocaleString(),
|
|
1321
1754
|
" rows"
|
|
1322
1755
|
] }),
|
|
1323
|
-
/* @__PURE__ */
|
|
1756
|
+
/* @__PURE__ */ jsxs4("span", { className: "tabular-nums", children: [
|
|
1324
1757
|
node.relationCount,
|
|
1325
1758
|
" rel \xB7 ",
|
|
1326
1759
|
node.scalarCount,
|
|
@@ -1469,7 +1902,7 @@ function layoutNodes(nodes, edges) {
|
|
|
1469
1902
|
let maxY = 0;
|
|
1470
1903
|
if (connected.length > 0) {
|
|
1471
1904
|
const g = new dagre.graphlib.Graph();
|
|
1472
|
-
g.setGraph({ rankdir: "LR", ranksep:
|
|
1905
|
+
g.setGraph({ rankdir: "LR", ranksep: 160, nodesep: 48, marginx: 40, marginy: 40 });
|
|
1473
1906
|
g.setDefaultEdgeLabel(() => ({}));
|
|
1474
1907
|
for (const node of connected) {
|
|
1475
1908
|
g.setNode(node.id, { width: NODE_WIDTH, height: nodeHeight(node) });
|
|
@@ -1581,7 +2014,6 @@ function buildEntityGraphModel(cartridges) {
|
|
|
1581
2014
|
}
|
|
1582
2015
|
}
|
|
1583
2016
|
recomputeNodeCounts(nodes);
|
|
1584
|
-
layoutNodes(nodes, edges);
|
|
1585
2017
|
return {
|
|
1586
2018
|
nodes,
|
|
1587
2019
|
edges,
|
|
@@ -1589,9 +2021,10 @@ function buildEntityGraphModel(cartridges) {
|
|
|
1589
2021
|
};
|
|
1590
2022
|
}
|
|
1591
2023
|
function filterEntityGraphModel(model, visibleIds) {
|
|
1592
|
-
const nodes = model.nodes.filter((node) => visibleIds.has(node.id));
|
|
2024
|
+
const nodes = model.nodes.filter((node) => visibleIds.has(node.id)).map((node) => ({ ...node, position: { ...node.position } }));
|
|
1593
2025
|
const ids = new Set(nodes.map((node) => node.id));
|
|
1594
2026
|
const edges = model.edges.filter((edge) => ids.has(edge.source) && ids.has(edge.target));
|
|
2027
|
+
layoutNodes(nodes, edges);
|
|
1595
2028
|
return {
|
|
1596
2029
|
nodes,
|
|
1597
2030
|
edges,
|
|
@@ -1600,28 +2033,27 @@ function filterEntityGraphModel(model, visibleIds) {
|
|
|
1600
2033
|
}
|
|
1601
2034
|
|
|
1602
2035
|
// src/components/devtools/entity-graph/diagram-tab.tsx
|
|
1603
|
-
import { jsx as
|
|
1604
|
-
var API_BASE = typeof NIMBIT_API_ENDPOINT !== "undefined" && NIMBIT_API_ENDPOINT || "";
|
|
1605
|
-
var STORAGE_KEY = "nimbit:inspector:entity-graph:hidden-carts";
|
|
2036
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1606
2037
|
var nodeTypes = { entity: EntityNode };
|
|
1607
2038
|
var FitOnReady = ({ fitKey }) => {
|
|
1608
2039
|
const initialized = useNodesInitialized();
|
|
1609
2040
|
const { fitView } = useReactFlow();
|
|
1610
|
-
|
|
2041
|
+
React7.useEffect(() => {
|
|
1611
2042
|
if (initialized) fitView({ padding: 0.25, duration: 200 });
|
|
1612
2043
|
}, [initialized, fitKey, fitView]);
|
|
1613
2044
|
return null;
|
|
1614
2045
|
};
|
|
1615
2046
|
function useContracts() {
|
|
1616
|
-
const
|
|
1617
|
-
const [
|
|
1618
|
-
const [
|
|
1619
|
-
|
|
2047
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
2048
|
+
const [contracts, setContracts] = React7.useState([]);
|
|
2049
|
+
const [loading, setLoading] = React7.useState(true);
|
|
2050
|
+
const [error, setError] = React7.useState(null);
|
|
2051
|
+
React7.useEffect(() => {
|
|
1620
2052
|
const ac = new AbortController();
|
|
1621
2053
|
let cancelled = false;
|
|
1622
2054
|
(async () => {
|
|
1623
2055
|
try {
|
|
1624
|
-
const r = await fetch(`${
|
|
2056
|
+
const r = await fetch(`${apiBaseUrl}/_console/contracts`, {
|
|
1625
2057
|
signal: ac.signal,
|
|
1626
2058
|
credentials: "include"
|
|
1627
2059
|
});
|
|
@@ -1640,7 +2072,7 @@ function useContracts() {
|
|
|
1640
2072
|
cancelled = true;
|
|
1641
2073
|
ac.abort();
|
|
1642
2074
|
};
|
|
1643
|
-
}, []);
|
|
2075
|
+
}, [apiBaseUrl]);
|
|
1644
2076
|
return { contracts, loading, error };
|
|
1645
2077
|
}
|
|
1646
2078
|
function registryFromContracts(contracts) {
|
|
@@ -1656,58 +2088,40 @@ function registryFromContracts(contracts) {
|
|
|
1656
2088
|
}
|
|
1657
2089
|
return reg;
|
|
1658
2090
|
}
|
|
1659
|
-
var DiagramView = () => {
|
|
2091
|
+
var DiagramView = ({ visibleIds, onSelectNode }) => {
|
|
1660
2092
|
const { contracts, loading, error } = useContracts();
|
|
1661
|
-
const graph =
|
|
2093
|
+
const graph = React7.useMemo(
|
|
1662
2094
|
() => buildEntityGraphModel(cartsFromContracts(contracts)),
|
|
1663
2095
|
[contracts]
|
|
1664
2096
|
);
|
|
1665
|
-
const registry =
|
|
2097
|
+
const registry = React7.useMemo(() => registryFromContracts(contracts), [contracts]);
|
|
1666
2098
|
if (graph.nodes.length === 0) {
|
|
1667
2099
|
let msg = "No installed cartridges with entities.";
|
|
1668
2100
|
if (loading) msg = "Loading entity graph\u2026";
|
|
1669
2101
|
else if (error) msg = `Failed to load contracts: ${error}`;
|
|
1670
|
-
return /* @__PURE__ */
|
|
2102
|
+
return /* @__PURE__ */ jsx8("div", { className: "p-3 text-[12px] text-muted-foreground", children: msg });
|
|
1671
2103
|
}
|
|
1672
|
-
return /* @__PURE__ */
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
const [hiddenCarts, setHiddenCarts] = React6.useState(() => {
|
|
1680
|
-
if (typeof window === "undefined") return /* @__PURE__ */ new Set();
|
|
1681
|
-
try {
|
|
1682
|
-
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
1683
|
-
const saved = raw ? JSON.parse(raw) : [];
|
|
1684
|
-
return new Set(Array.isArray(saved) ? saved.filter((n) => typeof n === "string") : []);
|
|
1685
|
-
} catch {
|
|
1686
|
-
return /* @__PURE__ */ new Set();
|
|
2104
|
+
return /* @__PURE__ */ jsx8(
|
|
2105
|
+
DiagramInner,
|
|
2106
|
+
{
|
|
2107
|
+
graph,
|
|
2108
|
+
registry,
|
|
2109
|
+
visibleIds,
|
|
2110
|
+
onSelectNode
|
|
1687
2111
|
}
|
|
1688
|
-
});
|
|
1689
|
-
const [focusedNodeId, setFocusedNodeId] = React6.useState(null);
|
|
1690
|
-
React6.useEffect(() => {
|
|
1691
|
-
if (typeof window === "undefined") return;
|
|
1692
|
-
window.localStorage.setItem(STORAGE_KEY, JSON.stringify([...hiddenCarts]));
|
|
1693
|
-
}, [hiddenCarts]);
|
|
1694
|
-
const cartGroups = React6.useMemo(() => {
|
|
1695
|
-
const counts = /* @__PURE__ */ new Map();
|
|
1696
|
-
for (const node of graph.nodes) counts.set(node.cartridge, (counts.get(node.cartridge) ?? 0) + 1);
|
|
1697
|
-
return [...counts.entries()].map(([name, total]) => ({ name, total })).sort((a, b) => a.name.localeCompare(b.name));
|
|
1698
|
-
}, [graph.nodes]);
|
|
1699
|
-
const visibleIds = React6.useMemo(
|
|
1700
|
-
() => new Set(graph.nodes.filter((n) => !hiddenCarts.has(n.cartridge)).map((n) => n.id)),
|
|
1701
|
-
[graph.nodes, hiddenCarts]
|
|
1702
2112
|
);
|
|
1703
|
-
|
|
2113
|
+
};
|
|
2114
|
+
var DiagramInner = ({ graph, registry, visibleIds, onSelectNode }) => {
|
|
2115
|
+
const rowCounts = useBulkRowCounts(registry);
|
|
2116
|
+
const [focusedNodeId, setFocusedNodeId] = React7.useState(null);
|
|
2117
|
+
const visibleGraph = React7.useMemo(
|
|
1704
2118
|
() => filterEntityGraphModel(graph, visibleIds),
|
|
1705
2119
|
[graph, visibleIds]
|
|
1706
2120
|
);
|
|
1707
|
-
|
|
2121
|
+
React7.useEffect(() => {
|
|
1708
2122
|
if (focusedNodeId && !visibleIds.has(focusedNodeId)) setFocusedNodeId(null);
|
|
1709
2123
|
}, [focusedNodeId, visibleIds]);
|
|
1710
|
-
const focusedConnections =
|
|
2124
|
+
const focusedConnections = React7.useMemo(() => {
|
|
1711
2125
|
if (!focusedNodeId) {
|
|
1712
2126
|
return {
|
|
1713
2127
|
connectedIds: /* @__PURE__ */ new Set(),
|
|
@@ -1733,11 +2147,7 @@ var DiagramInner = ({
|
|
|
1733
2147
|
}
|
|
1734
2148
|
return { connectedIds, edgeIds, fieldsByNode };
|
|
1735
2149
|
}, [focusedNodeId, visibleGraph.edges]);
|
|
1736
|
-
const
|
|
1737
|
-
() => visibleGraph.nodes.reduce((sum, n) => sum + (rowCounts[n.id] ?? 0), 0),
|
|
1738
|
-
[visibleGraph.nodes, rowCounts]
|
|
1739
|
-
);
|
|
1740
|
-
const nodes = React6.useMemo(
|
|
2150
|
+
const nodes = React7.useMemo(
|
|
1741
2151
|
() => visibleGraph.nodes.map((node) => ({
|
|
1742
2152
|
id: node.id,
|
|
1743
2153
|
type: "entity",
|
|
@@ -1755,7 +2165,7 @@ var DiagramInner = ({
|
|
|
1755
2165
|
})),
|
|
1756
2166
|
[focusedConnections, focusedNodeId, visibleGraph.nodes, rowCounts]
|
|
1757
2167
|
);
|
|
1758
|
-
const edges =
|
|
2168
|
+
const edges = React7.useMemo(() => {
|
|
1759
2169
|
const nodeById = new Map(visibleGraph.nodes.map((node) => [node.id, node]));
|
|
1760
2170
|
const laneCounts = /* @__PURE__ */ new Map();
|
|
1761
2171
|
return visibleGraph.edges.map((edge) => {
|
|
@@ -1787,95 +2197,54 @@ var DiagramInner = ({
|
|
|
1787
2197
|
};
|
|
1788
2198
|
});
|
|
1789
2199
|
}, [focusedConnections.edgeIds, focusedNodeId, visibleGraph.edges, visibleGraph.nodes]);
|
|
1790
|
-
const
|
|
1791
|
-
|
|
1792
|
-
const
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
title: hidden ? `Show ${g.name}` : `Hide ${g.name}`,
|
|
1839
|
-
children: [
|
|
1840
|
-
hidden ? /* @__PURE__ */ jsx7(EyeOff, { className: "h-3.5 w-3.5 shrink-0" }) : /* @__PURE__ */ jsx7(Eye, { className: "h-3.5 w-3.5 shrink-0 text-orange-400" }),
|
|
1841
|
-
/* @__PURE__ */ jsx7("span", { className: "min-w-0 flex-1 truncate", children: g.name }),
|
|
1842
|
-
/* @__PURE__ */ jsx7("span", { className: "shrink-0 text-[10px] tabular-nums text-zinc-500", children: g.total })
|
|
1843
|
-
]
|
|
1844
|
-
},
|
|
1845
|
-
g.name
|
|
1846
|
-
);
|
|
1847
|
-
}) }) }) : null,
|
|
1848
|
-
nodes.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "flex h-full items-center justify-center text-[12px] text-zinc-500", children: "No entities are visible." }) : /* @__PURE__ */ jsx7(ReactFlowProvider, { children: /* @__PURE__ */ jsxs4(
|
|
1849
|
-
ReactFlow,
|
|
1850
|
-
{
|
|
1851
|
-
colorMode: "dark",
|
|
1852
|
-
nodes,
|
|
1853
|
-
edges,
|
|
1854
|
-
nodeTypes,
|
|
1855
|
-
fitView: true,
|
|
1856
|
-
fitViewOptions: { padding: 0.2 },
|
|
1857
|
-
minZoom: 0.1,
|
|
1858
|
-
maxZoom: 1.6,
|
|
1859
|
-
nodesDraggable: false,
|
|
1860
|
-
nodesConnectable: false,
|
|
1861
|
-
elementsSelectable: true,
|
|
1862
|
-
panOnDrag: true,
|
|
1863
|
-
panOnScroll: true,
|
|
1864
|
-
zoomOnScroll: false,
|
|
1865
|
-
zoomOnPinch: true,
|
|
1866
|
-
selectionOnDrag: false,
|
|
1867
|
-
onNodeClick,
|
|
1868
|
-
onPaneClick: clearFocusedNode,
|
|
1869
|
-
proOptions: { hideAttribution: true },
|
|
1870
|
-
children: [
|
|
1871
|
-
/* @__PURE__ */ jsx7(FitOnReady, { fitKey: [...visibleIds].sort().join("|") }),
|
|
1872
|
-
/* @__PURE__ */ jsx7(Background, { variant: BackgroundVariant.Dots, gap: 16, size: 1, color: "#3f3f46" }),
|
|
1873
|
-
/* @__PURE__ */ jsx7(Controls, { showInteractive: false })
|
|
1874
|
-
]
|
|
1875
|
-
}
|
|
1876
|
-
) })
|
|
1877
|
-
] })
|
|
1878
|
-
] });
|
|
2200
|
+
const onNodeClick = React7.useCallback(
|
|
2201
|
+
(_, node) => {
|
|
2202
|
+
const data = node.data;
|
|
2203
|
+
const nodeId = entityGraphId(data.cartridge, data.entity);
|
|
2204
|
+
setFocusedNodeId((prev) => prev === nodeId ? null : nodeId);
|
|
2205
|
+
onSelectNode?.(data.cartridge, data.entity);
|
|
2206
|
+
},
|
|
2207
|
+
[onSelectNode]
|
|
2208
|
+
);
|
|
2209
|
+
const onNodeContextMenu = React7.useCallback(
|
|
2210
|
+
(e, node) => {
|
|
2211
|
+
e.preventDefault();
|
|
2212
|
+
const data = node.data;
|
|
2213
|
+
setFocusedNodeId(entityGraphId(data.cartridge, data.entity));
|
|
2214
|
+
onSelectNode?.(data.cartridge, data.entity);
|
|
2215
|
+
},
|
|
2216
|
+
[onSelectNode]
|
|
2217
|
+
);
|
|
2218
|
+
const clearFocusedNode = React7.useCallback(() => setFocusedNodeId(null), []);
|
|
2219
|
+
return /* @__PURE__ */ jsx8("div", { className: "flex h-full min-h-0 w-full flex-col", children: /* @__PURE__ */ jsx8("div", { className: "relative min-h-0 flex-1 bg-zinc-950", children: nodes.length === 0 ? /* @__PURE__ */ jsx8("div", { className: "flex h-full items-center justify-center text-[12px] text-zinc-500", children: "No entities are visible." }) : /* @__PURE__ */ jsx8(ReactFlowProvider, { children: /* @__PURE__ */ jsxs5(
|
|
2220
|
+
ReactFlow,
|
|
2221
|
+
{
|
|
2222
|
+
colorMode: "dark",
|
|
2223
|
+
nodes,
|
|
2224
|
+
edges,
|
|
2225
|
+
nodeTypes,
|
|
2226
|
+
fitView: true,
|
|
2227
|
+
fitViewOptions: { padding: 0.2 },
|
|
2228
|
+
minZoom: 0.1,
|
|
2229
|
+
maxZoom: 1.6,
|
|
2230
|
+
nodesDraggable: false,
|
|
2231
|
+
nodesConnectable: false,
|
|
2232
|
+
elementsSelectable: true,
|
|
2233
|
+
panOnDrag: true,
|
|
2234
|
+
zoomOnScroll: true,
|
|
2235
|
+
zoomOnPinch: true,
|
|
2236
|
+
selectionOnDrag: false,
|
|
2237
|
+
onNodeClick,
|
|
2238
|
+
onNodeContextMenu,
|
|
2239
|
+
onPaneClick: clearFocusedNode,
|
|
2240
|
+
proOptions: { hideAttribution: true },
|
|
2241
|
+
children: [
|
|
2242
|
+
/* @__PURE__ */ jsx8(FitOnReady, { fitKey: [...visibleIds].sort().join("|") }),
|
|
2243
|
+
/* @__PURE__ */ jsx8(Background, { variant: BackgroundVariant.Dots, gap: 16, size: 1, color: "#3f3f46" }),
|
|
2244
|
+
/* @__PURE__ */ jsx8(Controls, { showInteractive: false })
|
|
2245
|
+
]
|
|
2246
|
+
}
|
|
2247
|
+
) }) }) });
|
|
1879
2248
|
};
|
|
1880
2249
|
|
|
1881
2250
|
// src/components/ui/sheet.tsx
|
|
@@ -1884,7 +2253,7 @@ import { Dialog as SheetPrimitive } from "radix-ui";
|
|
|
1884
2253
|
// src/components/ui/button.tsx
|
|
1885
2254
|
import { cva } from "class-variance-authority";
|
|
1886
2255
|
import { Slot } from "radix-ui";
|
|
1887
|
-
import { jsx as
|
|
2256
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1888
2257
|
var buttonVariants = cva(
|
|
1889
2258
|
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
1890
2259
|
{
|
|
@@ -1922,7 +2291,7 @@ function Button({
|
|
|
1922
2291
|
...props
|
|
1923
2292
|
}) {
|
|
1924
2293
|
const Comp = asChild ? Slot.Root : "button";
|
|
1925
|
-
return /* @__PURE__ */
|
|
2294
|
+
return /* @__PURE__ */ jsx9(
|
|
1926
2295
|
Comp,
|
|
1927
2296
|
{
|
|
1928
2297
|
"data-slot": "button",
|
|
@@ -1936,20 +2305,20 @@ function Button({
|
|
|
1936
2305
|
|
|
1937
2306
|
// src/components/ui/sheet.tsx
|
|
1938
2307
|
import { XIcon } from "lucide-react";
|
|
1939
|
-
import { jsx as
|
|
2308
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1940
2309
|
function Sheet({ ...props }) {
|
|
1941
|
-
return /* @__PURE__ */
|
|
2310
|
+
return /* @__PURE__ */ jsx10(SheetPrimitive.Root, { "data-slot": "sheet", ...props });
|
|
1942
2311
|
}
|
|
1943
2312
|
function SheetPortal({
|
|
1944
2313
|
...props
|
|
1945
2314
|
}) {
|
|
1946
|
-
return /* @__PURE__ */
|
|
2315
|
+
return /* @__PURE__ */ jsx10(SheetPrimitive.Portal, { "data-slot": "sheet-portal", ...props });
|
|
1947
2316
|
}
|
|
1948
2317
|
function SheetOverlay({
|
|
1949
2318
|
className,
|
|
1950
2319
|
...props
|
|
1951
2320
|
}) {
|
|
1952
|
-
return /* @__PURE__ */
|
|
2321
|
+
return /* @__PURE__ */ jsx10(
|
|
1953
2322
|
SheetPrimitive.Overlay,
|
|
1954
2323
|
{
|
|
1955
2324
|
"data-slot": "sheet-overlay",
|
|
@@ -1968,9 +2337,9 @@ function SheetContent({
|
|
|
1968
2337
|
showCloseButton = true,
|
|
1969
2338
|
...props
|
|
1970
2339
|
}) {
|
|
1971
|
-
return /* @__PURE__ */
|
|
1972
|
-
/* @__PURE__ */
|
|
1973
|
-
/* @__PURE__ */
|
|
2340
|
+
return /* @__PURE__ */ jsxs6(SheetPortal, { children: [
|
|
2341
|
+
/* @__PURE__ */ jsx10(SheetOverlay, {}),
|
|
2342
|
+
/* @__PURE__ */ jsxs6(
|
|
1974
2343
|
SheetPrimitive.Content,
|
|
1975
2344
|
{
|
|
1976
2345
|
"data-slot": "sheet-content",
|
|
@@ -1982,18 +2351,18 @@ function SheetContent({
|
|
|
1982
2351
|
...props,
|
|
1983
2352
|
children: [
|
|
1984
2353
|
children,
|
|
1985
|
-
showCloseButton && /* @__PURE__ */
|
|
2354
|
+
showCloseButton && /* @__PURE__ */ jsx10(SheetPrimitive.Close, { "data-slot": "sheet-close", asChild: true, children: /* @__PURE__ */ jsxs6(
|
|
1986
2355
|
Button,
|
|
1987
2356
|
{
|
|
1988
2357
|
variant: "ghost",
|
|
1989
2358
|
className: "absolute top-3 right-3",
|
|
1990
2359
|
size: "icon-sm",
|
|
1991
2360
|
children: [
|
|
1992
|
-
/* @__PURE__ */
|
|
2361
|
+
/* @__PURE__ */ jsx10(
|
|
1993
2362
|
XIcon,
|
|
1994
2363
|
{}
|
|
1995
2364
|
),
|
|
1996
|
-
/* @__PURE__ */
|
|
2365
|
+
/* @__PURE__ */ jsx10("span", { className: "sr-only", children: "Close" })
|
|
1997
2366
|
]
|
|
1998
2367
|
}
|
|
1999
2368
|
) })
|
|
@@ -2003,7 +2372,7 @@ function SheetContent({
|
|
|
2003
2372
|
] });
|
|
2004
2373
|
}
|
|
2005
2374
|
function SheetHeader({ className, ...props }) {
|
|
2006
|
-
return /* @__PURE__ */
|
|
2375
|
+
return /* @__PURE__ */ jsx10(
|
|
2007
2376
|
"div",
|
|
2008
2377
|
{
|
|
2009
2378
|
"data-slot": "sheet-header",
|
|
@@ -2016,7 +2385,7 @@ function SheetTitle({
|
|
|
2016
2385
|
className,
|
|
2017
2386
|
...props
|
|
2018
2387
|
}) {
|
|
2019
|
-
return /* @__PURE__ */
|
|
2388
|
+
return /* @__PURE__ */ jsx10(
|
|
2020
2389
|
SheetPrimitive.Title,
|
|
2021
2390
|
{
|
|
2022
2391
|
"data-slot": "sheet-title",
|
|
@@ -2032,7 +2401,7 @@ function SheetDescription({
|
|
|
2032
2401
|
className,
|
|
2033
2402
|
...props
|
|
2034
2403
|
}) {
|
|
2035
|
-
return /* @__PURE__ */
|
|
2404
|
+
return /* @__PURE__ */ jsx10(
|
|
2036
2405
|
SheetPrimitive.Description,
|
|
2037
2406
|
{
|
|
2038
2407
|
"data-slot": "sheet-description",
|
|
@@ -2043,16 +2412,63 @@ function SheetDescription({
|
|
|
2043
2412
|
}
|
|
2044
2413
|
|
|
2045
2414
|
// src/components/devtools/data-tab.tsx
|
|
2046
|
-
import {
|
|
2415
|
+
import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2047
2416
|
var DataTab = () => {
|
|
2048
|
-
const {
|
|
2049
|
-
|
|
2050
|
-
|
|
2417
|
+
const {
|
|
2418
|
+
dataCart,
|
|
2419
|
+
setDataCart,
|
|
2420
|
+
dataEntity,
|
|
2421
|
+
setDataEntity,
|
|
2422
|
+
view,
|
|
2423
|
+
setView,
|
|
2424
|
+
graphCarts,
|
|
2425
|
+
setGraphCarts,
|
|
2426
|
+
hiddenEntities,
|
|
2427
|
+
setHiddenEntities,
|
|
2428
|
+
graphLeftW,
|
|
2429
|
+
setGraphLeftW,
|
|
2430
|
+
graphRightW,
|
|
2431
|
+
setGraphRightW
|
|
2432
|
+
} = useDevTools();
|
|
2433
|
+
const leftRef = React8.useRef(null);
|
|
2434
|
+
const rightRef = React8.useRef(null);
|
|
2435
|
+
const [detail, setDetail] = React8.useState(null);
|
|
2436
|
+
const [graphSel, setGraphSel] = React8.useState(null);
|
|
2051
2437
|
const { registry, carts, loading, error } = useLiveBulkRegistry();
|
|
2438
|
+
const selectedCarts = graphCarts ?? new Set(carts);
|
|
2439
|
+
const toggleGraphCart = (cart) => {
|
|
2440
|
+
setGraphCarts((prev) => {
|
|
2441
|
+
const next = new Set(prev ?? carts);
|
|
2442
|
+
if (next.has(cart)) next.delete(cart);
|
|
2443
|
+
else next.add(cart);
|
|
2444
|
+
return next;
|
|
2445
|
+
});
|
|
2446
|
+
};
|
|
2447
|
+
const toggleEntity = (id) => {
|
|
2448
|
+
setHiddenEntities((prev) => {
|
|
2449
|
+
const next = new Set(prev);
|
|
2450
|
+
if (next.has(id)) next.delete(id);
|
|
2451
|
+
else next.add(id);
|
|
2452
|
+
return next;
|
|
2453
|
+
});
|
|
2454
|
+
};
|
|
2455
|
+
const visibleIds = React8.useMemo(() => {
|
|
2456
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2457
|
+
for (const cart of carts) {
|
|
2458
|
+
if (!selectedCarts.has(cart)) continue;
|
|
2459
|
+
for (const e of registry[cart] ?? [])
|
|
2460
|
+
if (!hiddenEntities.has(`${cart}:${e.name}`)) ids.add(`${cart}:${e.name}`);
|
|
2461
|
+
}
|
|
2462
|
+
return ids;
|
|
2463
|
+
}, [carts, registry, graphCarts, hiddenEntities]);
|
|
2464
|
+
React8.useEffect(() => {
|
|
2465
|
+
if (graphSel && !visibleIds.has(`${graphSel.cart}:${graphSel.entity}`))
|
|
2466
|
+
setGraphSel(null);
|
|
2467
|
+
}, [graphSel, visibleIds]);
|
|
2052
2468
|
const activeCart = dataCart && registry[dataCart] ? dataCart : carts[0] ?? null;
|
|
2053
2469
|
const entities = activeCart ? registry[activeCart] ?? [] : [];
|
|
2054
2470
|
const activeEntity = entities.find((e) => e.name === dataEntity) ?? entities[0] ?? null;
|
|
2055
|
-
|
|
2471
|
+
React8.useEffect(() => {
|
|
2056
2472
|
if (activeCart && activeCart !== dataCart) setDataCart(activeCart);
|
|
2057
2473
|
if (activeEntity && activeEntity.name !== dataEntity) setDataEntity(activeEntity.name);
|
|
2058
2474
|
}, [activeCart, activeEntity, dataCart, dataEntity, setDataCart, setDataEntity]);
|
|
@@ -2060,60 +2476,100 @@ var DataTab = () => {
|
|
|
2060
2476
|
let msg = "No installed cartridges with entities.";
|
|
2061
2477
|
if (loading) msg = "Loading cartridges\u2026";
|
|
2062
2478
|
else if (error) msg = `Failed to load cartridges: ${error}`;
|
|
2063
|
-
return /* @__PURE__ */
|
|
2479
|
+
return /* @__PURE__ */ jsx11("div", { className: "p-3 text-[12px] text-muted-foreground", children: msg });
|
|
2064
2480
|
}
|
|
2065
|
-
return /* @__PURE__ */
|
|
2066
|
-
/* @__PURE__ */
|
|
2067
|
-
/* @__PURE__ */
|
|
2068
|
-
/* @__PURE__ */
|
|
2069
|
-
/* @__PURE__ */
|
|
2481
|
+
return /* @__PURE__ */ jsx11(BulkStreamProvider, { registry, children: /* @__PURE__ */ jsxs7("div", { className: "flex h-full flex-col", children: [
|
|
2482
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex h-7 shrink-0 items-center gap-1 overflow-x-auto border-b border-border bg-background px-1 select-none", children: [
|
|
2483
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex shrink-0 items-center gap-0.5 pr-1", children: [
|
|
2484
|
+
/* @__PURE__ */ jsx11(ViewBtn, { active: view === "table", onClick: () => setView("table"), label: "Table", children: /* @__PURE__ */ jsx11(Table2, { className: "size-3" }) }),
|
|
2485
|
+
/* @__PURE__ */ jsx11(ViewBtn, { active: view === "diagram", onClick: () => setView("diagram"), label: "Graph", children: /* @__PURE__ */ jsx11(Network, { className: "size-3" }) })
|
|
2070
2486
|
] }),
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
return /* @__PURE__ */ jsx10(
|
|
2076
|
-
"button",
|
|
2077
|
-
{
|
|
2078
|
-
type: "button",
|
|
2079
|
-
"data-active": isActive,
|
|
2080
|
-
onClick: () => {
|
|
2081
|
-
setDataCart(cart);
|
|
2082
|
-
const first = registry[cart]?.[0];
|
|
2083
|
-
setDataEntity(first ? first.name : null);
|
|
2084
|
-
},
|
|
2085
|
-
className: cn(
|
|
2086
|
-
"h-5 shrink-0 rounded-full px-2.5 text-[11px] leading-none outline-none transition-colors",
|
|
2087
|
-
"border border-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
2088
|
-
"data-[active=true]:border-border data-[active=true]:bg-accent data-[active=true]:text-accent-foreground"
|
|
2089
|
-
),
|
|
2090
|
-
children: cart
|
|
2091
|
-
},
|
|
2092
|
-
cart
|
|
2093
|
-
);
|
|
2094
|
-
})
|
|
2095
|
-
] }) : null
|
|
2096
|
-
] }),
|
|
2097
|
-
view === "diagram" ? /* @__PURE__ */ jsx10("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx10(DiagramView, {}) }) : /* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 flex-1", children: [
|
|
2098
|
-
/* @__PURE__ */ jsx10("div", { className: "flex w-44 shrink-0 flex-col overflow-y-auto border-r border-border bg-muted/10 py-1", children: entities.length === 0 ? /* @__PURE__ */ jsx10("div", { className: "px-2 py-1 text-[11px] text-muted-foreground", children: "No entities" }) : entities.map((e) => {
|
|
2099
|
-
const isActive = e.name === activeEntity?.name;
|
|
2100
|
-
return /* @__PURE__ */ jsx10(
|
|
2487
|
+
/* @__PURE__ */ jsx11("div", { className: "h-4 w-px shrink-0 bg-border" }),
|
|
2488
|
+
carts.map((cart) => {
|
|
2489
|
+
const isActive = view === "table" ? cart === activeCart : selectedCarts.has(cart);
|
|
2490
|
+
return /* @__PURE__ */ jsx11(
|
|
2101
2491
|
"button",
|
|
2102
2492
|
{
|
|
2103
2493
|
type: "button",
|
|
2104
2494
|
"data-active": isActive,
|
|
2105
|
-
onClick: () =>
|
|
2495
|
+
onClick: () => {
|
|
2496
|
+
if (view === "table") {
|
|
2497
|
+
setDataCart(cart);
|
|
2498
|
+
const first = registry[cart]?.[0];
|
|
2499
|
+
setDataEntity(first ? first.name : null);
|
|
2500
|
+
} else {
|
|
2501
|
+
toggleGraphCart(cart);
|
|
2502
|
+
}
|
|
2503
|
+
},
|
|
2106
2504
|
className: cn(
|
|
2107
|
-
"h-
|
|
2108
|
-
"text-foreground hover:bg-accent hover:text-accent-foreground",
|
|
2109
|
-
"data-[active=true]:bg-accent data-[active=true]:text-accent-foreground"
|
|
2505
|
+
"h-5 shrink-0 rounded-full px-2.5 text-[11px] leading-none outline-none transition-colors",
|
|
2506
|
+
"border border-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
2507
|
+
"data-[active=true]:border-border data-[active=true]:bg-accent data-[active=true]:text-accent-foreground"
|
|
2110
2508
|
),
|
|
2111
|
-
children:
|
|
2509
|
+
children: cart
|
|
2112
2510
|
},
|
|
2113
|
-
|
|
2511
|
+
cart
|
|
2114
2512
|
);
|
|
2115
|
-
})
|
|
2116
|
-
|
|
2513
|
+
})
|
|
2514
|
+
] }),
|
|
2515
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex min-h-0 min-w-0 flex-1", children: [
|
|
2516
|
+
/* @__PURE__ */ jsxs7(
|
|
2517
|
+
"div",
|
|
2518
|
+
{
|
|
2519
|
+
ref: leftRef,
|
|
2520
|
+
style: { width: graphLeftW },
|
|
2521
|
+
className: "relative shrink-0 border-r border-border bg-muted/10",
|
|
2522
|
+
children: [
|
|
2523
|
+
/* @__PURE__ */ jsx11(ResizeHandle, { edge: "right", targetRef: leftRef, min: 120, max: 480, onCommit: setGraphLeftW }),
|
|
2524
|
+
/* @__PURE__ */ jsx11("div", { className: "flex h-full flex-col overflow-y-auto py-1", children: view === "table" ? entities.length === 0 ? /* @__PURE__ */ jsx11("div", { className: "px-2 py-1 text-[11px] text-muted-foreground", children: "No entities" }) : entities.map((e) => {
|
|
2525
|
+
const isActive = e.name === activeEntity?.name;
|
|
2526
|
+
return /* @__PURE__ */ jsx11(
|
|
2527
|
+
"button",
|
|
2528
|
+
{
|
|
2529
|
+
type: "button",
|
|
2530
|
+
"data-active": isActive,
|
|
2531
|
+
onClick: () => setDataEntity(e.name),
|
|
2532
|
+
className: cn(
|
|
2533
|
+
"h-6 shrink-0 text-left px-2 text-[12px] leading-none outline-none transition-colors",
|
|
2534
|
+
"text-foreground hover:bg-accent hover:text-accent-foreground",
|
|
2535
|
+
"data-[active=true]:bg-accent data-[active=true]:text-accent-foreground"
|
|
2536
|
+
),
|
|
2537
|
+
children: e.name
|
|
2538
|
+
},
|
|
2539
|
+
e.name
|
|
2540
|
+
);
|
|
2541
|
+
}) : carts.filter((c) => selectedCarts.has(c)).length === 0 ? /* @__PURE__ */ jsx11("div", { className: "px-2 py-1 text-[11px] text-muted-foreground", children: "No cartridges selected" }) : carts.filter((c) => selectedCarts.has(c)).map((cart) => /* @__PURE__ */ jsxs7("div", { className: "mb-1", children: [
|
|
2542
|
+
/* @__PURE__ */ jsx11("div", { className: "px-2 py-0.5 text-[10px] uppercase tracking-wider text-muted-foreground/60", children: cart }),
|
|
2543
|
+
(registry[cart] ?? []).map((e) => {
|
|
2544
|
+
const id = `${cart}:${e.name}`;
|
|
2545
|
+
const on = !hiddenEntities.has(id);
|
|
2546
|
+
return /* @__PURE__ */ jsx11(
|
|
2547
|
+
"button",
|
|
2548
|
+
{
|
|
2549
|
+
type: "button",
|
|
2550
|
+
"data-active": on,
|
|
2551
|
+
onClick: () => toggleEntity(id),
|
|
2552
|
+
title: on ? `Hide ${e.name}` : `Show ${e.name}`,
|
|
2553
|
+
className: cn(
|
|
2554
|
+
"h-6 w-full shrink-0 text-left px-2 text-[12px] leading-none outline-none transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
2555
|
+
on ? "text-foreground" : "text-muted-foreground/40 line-through"
|
|
2556
|
+
),
|
|
2557
|
+
children: e.name
|
|
2558
|
+
},
|
|
2559
|
+
id
|
|
2560
|
+
);
|
|
2561
|
+
})
|
|
2562
|
+
] }, cart)) })
|
|
2563
|
+
]
|
|
2564
|
+
}
|
|
2565
|
+
),
|
|
2566
|
+
/* @__PURE__ */ jsx11("div", { className: "min-h-0 min-w-0 flex-1", children: view === "diagram" ? /* @__PURE__ */ jsx11(
|
|
2567
|
+
DiagramView,
|
|
2568
|
+
{
|
|
2569
|
+
visibleIds,
|
|
2570
|
+
onSelectNode: (cart, entity) => setGraphSel({ cart, entity })
|
|
2571
|
+
}
|
|
2572
|
+
) : activeEntity ? /* @__PURE__ */ jsx11(
|
|
2117
2573
|
data_table_default,
|
|
2118
2574
|
{
|
|
2119
2575
|
cart: activeCart,
|
|
@@ -2122,26 +2578,125 @@ var DataTab = () => {
|
|
|
2122
2578
|
onSelectRow: (row) => setDetail(row)
|
|
2123
2579
|
},
|
|
2124
2580
|
`${activeCart}:${activeEntity.name}`
|
|
2125
|
-
) : /* @__PURE__ */
|
|
2581
|
+
) : /* @__PURE__ */ jsx11("div", { className: "p-3 text-[12px] text-muted-foreground", children: "Pick an entity." }) }),
|
|
2582
|
+
view === "diagram" && graphSel ? /* @__PURE__ */ jsxs7(
|
|
2583
|
+
"div",
|
|
2584
|
+
{
|
|
2585
|
+
ref: rightRef,
|
|
2586
|
+
style: { width: graphRightW },
|
|
2587
|
+
className: "relative flex shrink-0 flex-col border-l border-border bg-background",
|
|
2588
|
+
children: [
|
|
2589
|
+
/* @__PURE__ */ jsx11(ResizeHandle, { edge: "left", targetRef: rightRef, min: 280, max: 900, onCommit: setGraphRightW }),
|
|
2590
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex h-7 shrink-0 items-center gap-1 border-b border-border bg-muted/10 px-2 select-none", children: [
|
|
2591
|
+
/* @__PURE__ */ jsxs7("span", { className: "min-w-0 flex-1 truncate text-[12px]", children: [
|
|
2592
|
+
/* @__PURE__ */ jsxs7("span", { className: "text-muted-foreground", children: [
|
|
2593
|
+
graphSel.cart,
|
|
2594
|
+
"."
|
|
2595
|
+
] }),
|
|
2596
|
+
graphSel.entity
|
|
2597
|
+
] }),
|
|
2598
|
+
/* @__PURE__ */ jsx11(
|
|
2599
|
+
"button",
|
|
2600
|
+
{
|
|
2601
|
+
type: "button",
|
|
2602
|
+
"aria-label": "Close",
|
|
2603
|
+
title: "Close",
|
|
2604
|
+
onClick: () => setGraphSel(null),
|
|
2605
|
+
className: "grid size-5 shrink-0 place-items-center rounded-md text-foreground outline-none hover:bg-accent hover:text-accent-foreground",
|
|
2606
|
+
children: /* @__PURE__ */ jsx11(X, { className: "size-3.5" })
|
|
2607
|
+
}
|
|
2608
|
+
)
|
|
2609
|
+
] }),
|
|
2610
|
+
/* @__PURE__ */ jsx11("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx11(
|
|
2611
|
+
data_table_default,
|
|
2612
|
+
{
|
|
2613
|
+
cart: graphSel.cart,
|
|
2614
|
+
entity: graphSel.entity,
|
|
2615
|
+
searchFields: (registry[graphSel.cart] ?? []).find((e) => e.name === graphSel.entity)?.searchFields ?? [],
|
|
2616
|
+
onSelectRow: (row) => setDetail(row)
|
|
2617
|
+
},
|
|
2618
|
+
`${graphSel.cart}:${graphSel.entity}`
|
|
2619
|
+
) })
|
|
2620
|
+
]
|
|
2621
|
+
}
|
|
2622
|
+
) : null
|
|
2126
2623
|
] }),
|
|
2127
|
-
/* @__PURE__ */
|
|
2128
|
-
/* @__PURE__ */
|
|
2129
|
-
/* @__PURE__ */
|
|
2624
|
+
/* @__PURE__ */ jsx11(Sheet, { open: !!detail, onOpenChange: (o) => !o && setDetail(null), children: /* @__PURE__ */ jsxs7(SheetContent, { side: "right", className: "w-[480px] sm:max-w-[480px]", children: [
|
|
2625
|
+
/* @__PURE__ */ jsxs7(SheetHeader, { children: [
|
|
2626
|
+
/* @__PURE__ */ jsxs7(SheetTitle, { className: "text-[13px]", children: [
|
|
2130
2627
|
activeCart,
|
|
2131
2628
|
".",
|
|
2132
2629
|
activeEntity?.name,
|
|
2133
|
-
detail?.id ? /* @__PURE__ */
|
|
2630
|
+
detail?.id ? /* @__PURE__ */ jsx11("span", { className: "ml-2 font-mono text-[11px] text-muted-foreground", children: detail.id }) : null
|
|
2134
2631
|
] }),
|
|
2135
|
-
/* @__PURE__ */
|
|
2632
|
+
/* @__PURE__ */ jsx11(SheetDescription, { className: "text-[11px]", children: "Row detail" })
|
|
2136
2633
|
] }),
|
|
2137
|
-
/* @__PURE__ */
|
|
2138
|
-
/* @__PURE__ */
|
|
2139
|
-
/* @__PURE__ */
|
|
2634
|
+
/* @__PURE__ */ jsx11("div", { className: "min-h-0 flex-1 overflow-auto px-4 pb-4", children: detail ? /* @__PURE__ */ jsx11("table", { className: "w-full text-[12px]", children: /* @__PURE__ */ jsx11("tbody", { children: Object.entries(detail).map(([k, v]) => /* @__PURE__ */ jsxs7("tr", { className: "border-b border-border/40 align-top", children: [
|
|
2635
|
+
/* @__PURE__ */ jsx11("td", { className: "w-32 py-1 pr-2 font-mono text-[11px] text-muted-foreground", children: k }),
|
|
2636
|
+
/* @__PURE__ */ jsx11("td", { className: "break-all py-1 font-mono", children: v || /* @__PURE__ */ jsx11("span", { className: "text-muted-foreground/60", children: "\u2205" }) })
|
|
2140
2637
|
] }, k)) }) }) : null })
|
|
2141
2638
|
] }) })
|
|
2142
2639
|
] }) });
|
|
2143
2640
|
};
|
|
2144
|
-
var
|
|
2641
|
+
var ResizeHandle = ({ edge, targetRef, min, max, onCommit }) => {
|
|
2642
|
+
const onPointerDown = (e) => {
|
|
2643
|
+
e.preventDefault();
|
|
2644
|
+
const el = targetRef.current;
|
|
2645
|
+
if (!el) return;
|
|
2646
|
+
const handle = e.currentTarget;
|
|
2647
|
+
handle.setPointerCapture(e.pointerId);
|
|
2648
|
+
const startX = e.clientX;
|
|
2649
|
+
const startW = el.offsetWidth;
|
|
2650
|
+
let last = startW;
|
|
2651
|
+
let raf = 0;
|
|
2652
|
+
let pending = null;
|
|
2653
|
+
const flush = () => {
|
|
2654
|
+
raf = 0;
|
|
2655
|
+
if (pending == null) return;
|
|
2656
|
+
last = pending;
|
|
2657
|
+
el.style.width = `${pending}px`;
|
|
2658
|
+
pending = null;
|
|
2659
|
+
};
|
|
2660
|
+
const onMove = (ev) => {
|
|
2661
|
+
ev.preventDefault();
|
|
2662
|
+
const dx = ev.clientX - startX;
|
|
2663
|
+
const delta = edge === "left" ? -dx : dx;
|
|
2664
|
+
pending = Math.max(min, Math.min(max, startW + delta));
|
|
2665
|
+
if (!raf) raf = requestAnimationFrame(flush);
|
|
2666
|
+
};
|
|
2667
|
+
const onUp = () => {
|
|
2668
|
+
if (raf) cancelAnimationFrame(raf);
|
|
2669
|
+
flush();
|
|
2670
|
+
onCommit(last);
|
|
2671
|
+
handle.removeEventListener("pointermove", onMove);
|
|
2672
|
+
handle.removeEventListener("pointerup", onUp);
|
|
2673
|
+
handle.removeEventListener("pointercancel", onUp);
|
|
2674
|
+
try {
|
|
2675
|
+
handle.releasePointerCapture(e.pointerId);
|
|
2676
|
+
} catch {
|
|
2677
|
+
}
|
|
2678
|
+
document.body.style.userSelect = "";
|
|
2679
|
+
document.body.style.cursor = "";
|
|
2680
|
+
};
|
|
2681
|
+
document.body.style.userSelect = "none";
|
|
2682
|
+
document.body.style.cursor = "ew-resize";
|
|
2683
|
+
handle.addEventListener("pointermove", onMove);
|
|
2684
|
+
handle.addEventListener("pointerup", onUp);
|
|
2685
|
+
handle.addEventListener("pointercancel", onUp);
|
|
2686
|
+
};
|
|
2687
|
+
return /* @__PURE__ */ jsx11(
|
|
2688
|
+
"div",
|
|
2689
|
+
{
|
|
2690
|
+
onPointerDown,
|
|
2691
|
+
style: { touchAction: "none" },
|
|
2692
|
+
className: cn(
|
|
2693
|
+
"absolute top-0 bottom-0 z-20 w-1.5 cursor-ew-resize bg-transparent hover:bg-accent/40",
|
|
2694
|
+
edge === "left" ? "-left-px" : "-right-px"
|
|
2695
|
+
)
|
|
2696
|
+
}
|
|
2697
|
+
);
|
|
2698
|
+
};
|
|
2699
|
+
var ViewBtn = ({ active, onClick, label, children }) => /* @__PURE__ */ jsxs7(
|
|
2145
2700
|
"button",
|
|
2146
2701
|
{
|
|
2147
2702
|
type: "button",
|
|
@@ -2162,22 +2717,1112 @@ var ViewBtn = ({ active, onClick, label, children }) => /* @__PURE__ */ jsxs6(
|
|
|
2162
2717
|
var data_tab_default = DataTab;
|
|
2163
2718
|
|
|
2164
2719
|
// src/components/devtools/settings-tab.tsx
|
|
2165
|
-
import { Fragment as Fragment3, jsx as
|
|
2720
|
+
import { Fragment as Fragment3, jsx as jsx12 } from "react/jsx-runtime";
|
|
2166
2721
|
var SettingsTab = () => {
|
|
2167
|
-
return /* @__PURE__ */
|
|
2722
|
+
return /* @__PURE__ */ jsx12(Fragment3, {});
|
|
2168
2723
|
};
|
|
2169
2724
|
var settings_tab_default = SettingsTab;
|
|
2170
2725
|
|
|
2726
|
+
// src/components/devtools/sources/sources-tab.tsx
|
|
2727
|
+
import React11 from "react";
|
|
2728
|
+
import { Play as Play2, Save } from "lucide-react";
|
|
2729
|
+
|
|
2730
|
+
// src/components/devtools/sources/use-dev-files.ts
|
|
2731
|
+
import { useCallback, useEffect as useEffect4, useState as useState3 } from "react";
|
|
2732
|
+
function useDevMode() {
|
|
2733
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
2734
|
+
const [status, setStatus] = useState3(null);
|
|
2735
|
+
useEffect4(() => {
|
|
2736
|
+
let cancelled = false;
|
|
2737
|
+
fetch(`${apiBaseUrl}/_console/dev/status`, { credentials: "include" }).then((r) => r.ok ? r.json() : null).then((s) => {
|
|
2738
|
+
if (!cancelled) setStatus(s && s.dev ? s : null);
|
|
2739
|
+
}).catch(() => {
|
|
2740
|
+
if (!cancelled) setStatus(null);
|
|
2741
|
+
});
|
|
2742
|
+
return () => {
|
|
2743
|
+
cancelled = true;
|
|
2744
|
+
};
|
|
2745
|
+
}, [apiBaseUrl]);
|
|
2746
|
+
return status;
|
|
2747
|
+
}
|
|
2748
|
+
async function jsonOrThrow(r) {
|
|
2749
|
+
const body = await r.json().catch(() => ({}));
|
|
2750
|
+
if (!r.ok) throw new Error(body?.error ?? `HTTP ${r.status}`);
|
|
2751
|
+
return body;
|
|
2752
|
+
}
|
|
2753
|
+
function useDevFilesApi() {
|
|
2754
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
2755
|
+
const listCarts = useCallback(
|
|
2756
|
+
async () => (await jsonOrThrow(await fetch(`${apiBaseUrl}/_console/dev/carts`, { credentials: "include" }))).carts ?? [],
|
|
2757
|
+
[apiBaseUrl]
|
|
2758
|
+
);
|
|
2759
|
+
const readFile = useCallback(
|
|
2760
|
+
async (cart, path) => jsonOrThrow(
|
|
2761
|
+
await fetch(
|
|
2762
|
+
`${apiBaseUrl}/_console/dev/file?cart=${encodeURIComponent(cart)}&path=${encodeURIComponent(path)}`,
|
|
2763
|
+
{ credentials: "include" }
|
|
2764
|
+
)
|
|
2765
|
+
),
|
|
2766
|
+
[apiBaseUrl]
|
|
2767
|
+
);
|
|
2768
|
+
const writeFile = useCallback(
|
|
2769
|
+
async (cart, path, content) => jsonOrThrow(
|
|
2770
|
+
await fetch(`${apiBaseUrl}/_console/dev/file`, {
|
|
2771
|
+
method: "POST",
|
|
2772
|
+
credentials: "include",
|
|
2773
|
+
headers: { "content-type": "application/json" },
|
|
2774
|
+
body: JSON.stringify({ cart, path, content })
|
|
2775
|
+
})
|
|
2776
|
+
),
|
|
2777
|
+
[apiBaseUrl]
|
|
2778
|
+
);
|
|
2779
|
+
const applyCart = useCallback(
|
|
2780
|
+
async (cart) => jsonOrThrow(
|
|
2781
|
+
await fetch(`${apiBaseUrl}/_console/dev/apply`, {
|
|
2782
|
+
method: "POST",
|
|
2783
|
+
credentials: "include",
|
|
2784
|
+
headers: { "content-type": "application/json" },
|
|
2785
|
+
body: JSON.stringify({ cart })
|
|
2786
|
+
})
|
|
2787
|
+
),
|
|
2788
|
+
[apiBaseUrl]
|
|
2789
|
+
);
|
|
2790
|
+
const scaffoldCart = useCallback(
|
|
2791
|
+
async (name) => jsonOrThrow(
|
|
2792
|
+
await fetch(`${apiBaseUrl}/_console/dev/scaffold`, {
|
|
2793
|
+
method: "POST",
|
|
2794
|
+
credentials: "include",
|
|
2795
|
+
headers: { "content-type": "application/json" },
|
|
2796
|
+
body: JSON.stringify({ name })
|
|
2797
|
+
})
|
|
2798
|
+
),
|
|
2799
|
+
[apiBaseUrl]
|
|
2800
|
+
);
|
|
2801
|
+
return { listCarts, readFile, writeFile, applyCart, scaffoldCart };
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
// src/components/devtools/sources/file-tree.tsx
|
|
2805
|
+
import React9 from "react";
|
|
2806
|
+
import { Lock, Plus, FilePlus2 } from "lucide-react";
|
|
2807
|
+
import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2808
|
+
var FileTree = ({
|
|
2809
|
+
carts,
|
|
2810
|
+
selected,
|
|
2811
|
+
dirtyKeys,
|
|
2812
|
+
onSelect,
|
|
2813
|
+
onNewCart,
|
|
2814
|
+
onNewMigration
|
|
2815
|
+
}) => {
|
|
2816
|
+
const [creating, setCreating] = React9.useState(false);
|
|
2817
|
+
const [newName, setNewName] = React9.useState("");
|
|
2818
|
+
const submitNew = () => {
|
|
2819
|
+
const name = newName.trim();
|
|
2820
|
+
if (/^[a-z][a-z0-9_]*$/.test(name)) {
|
|
2821
|
+
onNewCart(name);
|
|
2822
|
+
setNewName("");
|
|
2823
|
+
setCreating(false);
|
|
2824
|
+
}
|
|
2825
|
+
};
|
|
2826
|
+
return /* @__PURE__ */ jsxs8("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
2827
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex h-6 shrink-0 items-center justify-between border-b border-border px-2", children: [
|
|
2828
|
+
/* @__PURE__ */ jsx13("span", { className: "text-[11px] font-medium text-muted-foreground", children: "Cartridges" }),
|
|
2829
|
+
/* @__PURE__ */ jsx13(
|
|
2830
|
+
"button",
|
|
2831
|
+
{
|
|
2832
|
+
type: "button",
|
|
2833
|
+
title: "New cartridge",
|
|
2834
|
+
onClick: () => setCreating((v) => !v),
|
|
2835
|
+
className: "rounded p-0.5 hover:bg-accent",
|
|
2836
|
+
children: /* @__PURE__ */ jsx13(Plus, { className: "size-3.5" })
|
|
2837
|
+
}
|
|
2838
|
+
)
|
|
2839
|
+
] }),
|
|
2840
|
+
creating && /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1 border-b border-border p-1", children: [
|
|
2841
|
+
/* @__PURE__ */ jsx13(
|
|
2842
|
+
"input",
|
|
2843
|
+
{
|
|
2844
|
+
autoFocus: true,
|
|
2845
|
+
value: newName,
|
|
2846
|
+
onChange: (e) => setNewName(e.target.value),
|
|
2847
|
+
onKeyDown: (e) => {
|
|
2848
|
+
if (e.key === "Enter") submitNew();
|
|
2849
|
+
if (e.key === "Escape") setCreating(false);
|
|
2850
|
+
},
|
|
2851
|
+
placeholder: "cart_name",
|
|
2852
|
+
className: "h-5 w-full rounded border border-border bg-background px-1 text-[11px] outline-none"
|
|
2853
|
+
}
|
|
2854
|
+
),
|
|
2855
|
+
/* @__PURE__ */ jsx13(
|
|
2856
|
+
"button",
|
|
2857
|
+
{
|
|
2858
|
+
type: "button",
|
|
2859
|
+
onClick: submitNew,
|
|
2860
|
+
disabled: !/^[a-z][a-z0-9_]*$/.test(newName.trim()),
|
|
2861
|
+
className: "rounded px-1 text-[11px] hover:bg-accent disabled:opacity-40",
|
|
2862
|
+
children: "Create"
|
|
2863
|
+
}
|
|
2864
|
+
)
|
|
2865
|
+
] }),
|
|
2866
|
+
/* @__PURE__ */ jsx13("div", { className: "min-h-0 flex-1 overflow-y-auto py-1", children: carts.map((cart) => /* @__PURE__ */ jsxs8("div", { className: "mb-1", children: [
|
|
2867
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between px-2 py-0.5", children: [
|
|
2868
|
+
/* @__PURE__ */ jsxs8("span", { className: "flex items-center gap-1 text-[11px] font-medium", children: [
|
|
2869
|
+
cart.name,
|
|
2870
|
+
!cart.writable && /* @__PURE__ */ jsx13(
|
|
2871
|
+
Lock,
|
|
2872
|
+
{
|
|
2873
|
+
className: "size-3 text-muted-foreground",
|
|
2874
|
+
"aria-label": cart.reason ?? "read-only"
|
|
2875
|
+
}
|
|
2876
|
+
)
|
|
2877
|
+
] }),
|
|
2878
|
+
cart.writable && /* @__PURE__ */ jsx13(
|
|
2879
|
+
"button",
|
|
2880
|
+
{
|
|
2881
|
+
type: "button",
|
|
2882
|
+
title: "New migration",
|
|
2883
|
+
onClick: () => onNewMigration(cart.name),
|
|
2884
|
+
className: "rounded p-0.5 text-muted-foreground hover:bg-accent",
|
|
2885
|
+
children: /* @__PURE__ */ jsx13(FilePlus2, { className: "size-3" })
|
|
2886
|
+
}
|
|
2887
|
+
)
|
|
2888
|
+
] }),
|
|
2889
|
+
cart.files.map((path) => {
|
|
2890
|
+
const key = `${cart.name}:${path}`;
|
|
2891
|
+
const isSel = selected?.cart === cart.name && selected?.path === path;
|
|
2892
|
+
return /* @__PURE__ */ jsxs8(
|
|
2893
|
+
"button",
|
|
2894
|
+
{
|
|
2895
|
+
type: "button",
|
|
2896
|
+
onClick: () => onSelect({ cart: cart.name, path }),
|
|
2897
|
+
"data-active": isSel,
|
|
2898
|
+
className: cn(
|
|
2899
|
+
"flex w-full items-center gap-1 truncate py-0.5 pl-4 pr-2 text-left text-[11px]",
|
|
2900
|
+
"hover:bg-accent data-[active=true]:bg-accent"
|
|
2901
|
+
),
|
|
2902
|
+
children: [
|
|
2903
|
+
/* @__PURE__ */ jsx13("span", { className: "truncate", children: path }),
|
|
2904
|
+
dirtyKeys.has(key) && /* @__PURE__ */ jsx13("span", { className: "size-1.5 shrink-0 rounded-full bg-amber-400", title: "unsaved" })
|
|
2905
|
+
]
|
|
2906
|
+
},
|
|
2907
|
+
path
|
|
2908
|
+
);
|
|
2909
|
+
})
|
|
2910
|
+
] }, cart.name)) })
|
|
2911
|
+
] });
|
|
2912
|
+
};
|
|
2913
|
+
|
|
2914
|
+
// src/components/devtools/sources/nbt-editor.tsx
|
|
2915
|
+
import React10 from "react";
|
|
2916
|
+
import { EditorState } from "@codemirror/state";
|
|
2917
|
+
import {
|
|
2918
|
+
EditorView as EditorView2,
|
|
2919
|
+
keymap as keymap2,
|
|
2920
|
+
lineNumbers,
|
|
2921
|
+
drawSelection,
|
|
2922
|
+
highlightActiveLine
|
|
2923
|
+
} from "@codemirror/view";
|
|
2924
|
+
import {
|
|
2925
|
+
defaultKeymap,
|
|
2926
|
+
history,
|
|
2927
|
+
historyKeymap,
|
|
2928
|
+
indentWithTab
|
|
2929
|
+
} from "@codemirror/commands";
|
|
2930
|
+
import {
|
|
2931
|
+
syntaxHighlighting,
|
|
2932
|
+
defaultHighlightStyle,
|
|
2933
|
+
bracketMatching
|
|
2934
|
+
} from "@codemirror/language";
|
|
2935
|
+
import { lintGutter } from "@codemirror/lint";
|
|
2936
|
+
import { searchKeymap } from "@codemirror/search";
|
|
2937
|
+
import { completionKeymap, closeBrackets } from "@codemirror/autocomplete";
|
|
2938
|
+
|
|
2939
|
+
// src/components/devtools/sources/nbt-language.ts
|
|
2940
|
+
import {
|
|
2941
|
+
StreamLanguage,
|
|
2942
|
+
LanguageSupport,
|
|
2943
|
+
indentUnit
|
|
2944
|
+
} from "@codemirror/language";
|
|
2945
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
2946
|
+
"entity",
|
|
2947
|
+
"enum",
|
|
2948
|
+
"struct",
|
|
2949
|
+
"const",
|
|
2950
|
+
"export",
|
|
2951
|
+
"extends",
|
|
2952
|
+
"fn",
|
|
2953
|
+
"on",
|
|
2954
|
+
"action",
|
|
2955
|
+
"activity",
|
|
2956
|
+
"task",
|
|
2957
|
+
"variant",
|
|
2958
|
+
"workflow",
|
|
2959
|
+
"command",
|
|
2960
|
+
"middleware",
|
|
2961
|
+
"schedule",
|
|
2962
|
+
"every",
|
|
2963
|
+
"jai",
|
|
2964
|
+
"migration",
|
|
2965
|
+
"test",
|
|
2966
|
+
"mock",
|
|
2967
|
+
"assert",
|
|
2968
|
+
"cartridge",
|
|
2969
|
+
"service",
|
|
2970
|
+
"component",
|
|
2971
|
+
"app",
|
|
2972
|
+
"route",
|
|
2973
|
+
"layout",
|
|
2974
|
+
"import",
|
|
2975
|
+
"from",
|
|
2976
|
+
"if",
|
|
2977
|
+
"elif",
|
|
2978
|
+
"else",
|
|
2979
|
+
"while",
|
|
2980
|
+
"for",
|
|
2981
|
+
"in",
|
|
2982
|
+
"return",
|
|
2983
|
+
"break",
|
|
2984
|
+
"continue",
|
|
2985
|
+
"delete",
|
|
2986
|
+
"defer",
|
|
2987
|
+
"print",
|
|
2988
|
+
"sleep",
|
|
2989
|
+
"fail"
|
|
2990
|
+
]);
|
|
2991
|
+
var TYPES = /* @__PURE__ */ new Set([
|
|
2992
|
+
"string",
|
|
2993
|
+
"bool",
|
|
2994
|
+
"ulid",
|
|
2995
|
+
"document",
|
|
2996
|
+
"dict",
|
|
2997
|
+
"blob",
|
|
2998
|
+
"DateTime",
|
|
2999
|
+
"u8",
|
|
3000
|
+
"u16",
|
|
3001
|
+
"u32",
|
|
3002
|
+
"u64",
|
|
3003
|
+
"s8",
|
|
3004
|
+
"s16",
|
|
3005
|
+
"s32",
|
|
3006
|
+
"s64",
|
|
3007
|
+
"int",
|
|
3008
|
+
"integer",
|
|
3009
|
+
"float",
|
|
3010
|
+
"float64",
|
|
3011
|
+
"f32",
|
|
3012
|
+
"f64",
|
|
3013
|
+
"double",
|
|
3014
|
+
"time",
|
|
3015
|
+
"bytes",
|
|
3016
|
+
"any",
|
|
3017
|
+
"name",
|
|
3018
|
+
"ref"
|
|
3019
|
+
]);
|
|
3020
|
+
function eatSingleLineString(stream, quote) {
|
|
3021
|
+
while (!stream.eol()) {
|
|
3022
|
+
if (stream.next() === quote) break;
|
|
3023
|
+
}
|
|
3024
|
+
return "string";
|
|
3025
|
+
}
|
|
3026
|
+
var nbtParser = {
|
|
3027
|
+
name: "nbt",
|
|
3028
|
+
startState: () => ({ tripleString: false }),
|
|
3029
|
+
token(stream, state) {
|
|
3030
|
+
if (state.tripleString) {
|
|
3031
|
+
while (!stream.eol()) {
|
|
3032
|
+
if (stream.match('"""')) {
|
|
3033
|
+
state.tripleString = false;
|
|
3034
|
+
return "string";
|
|
3035
|
+
}
|
|
3036
|
+
stream.next();
|
|
3037
|
+
}
|
|
3038
|
+
return "string";
|
|
3039
|
+
}
|
|
3040
|
+
if (stream.eatSpace()) return null;
|
|
3041
|
+
const ch = stream.peek();
|
|
3042
|
+
if (ch == null) return null;
|
|
3043
|
+
if (ch === "#") {
|
|
3044
|
+
stream.skipToEnd();
|
|
3045
|
+
return "comment";
|
|
3046
|
+
}
|
|
3047
|
+
if (stream.match('"""')) {
|
|
3048
|
+
state.tripleString = true;
|
|
3049
|
+
while (!stream.eol()) {
|
|
3050
|
+
if (stream.match('"""')) {
|
|
3051
|
+
state.tripleString = false;
|
|
3052
|
+
return "string";
|
|
3053
|
+
}
|
|
3054
|
+
stream.next();
|
|
3055
|
+
}
|
|
3056
|
+
return "string";
|
|
3057
|
+
}
|
|
3058
|
+
if (ch === '"') {
|
|
3059
|
+
stream.next();
|
|
3060
|
+
return eatSingleLineString(stream, '"');
|
|
3061
|
+
}
|
|
3062
|
+
if (ch === "'") {
|
|
3063
|
+
const before = stream.string.charAt(stream.pos - 1);
|
|
3064
|
+
if (!/[A-Za-z0-9_]/.test(before)) {
|
|
3065
|
+
stream.next();
|
|
3066
|
+
return eatSingleLineString(stream, "'");
|
|
3067
|
+
}
|
|
3068
|
+
stream.next();
|
|
3069
|
+
return null;
|
|
3070
|
+
}
|
|
3071
|
+
if (ch === "f" && stream.string.charAt(stream.pos + 1) === '"') {
|
|
3072
|
+
stream.next();
|
|
3073
|
+
stream.next();
|
|
3074
|
+
return eatSingleLineString(stream, '"');
|
|
3075
|
+
}
|
|
3076
|
+
if (ch === "@") {
|
|
3077
|
+
stream.next();
|
|
3078
|
+
stream.eat("@");
|
|
3079
|
+
stream.eatWhile(/[A-Za-z0-9_]/);
|
|
3080
|
+
return "meta";
|
|
3081
|
+
}
|
|
3082
|
+
if (/\d/.test(ch)) {
|
|
3083
|
+
stream.match(/^\d+(\.\d+)?/);
|
|
3084
|
+
return "number";
|
|
3085
|
+
}
|
|
3086
|
+
if (/[A-Za-z_]/.test(ch)) {
|
|
3087
|
+
stream.eatWhile(/[A-Za-z0-9_]/);
|
|
3088
|
+
const word = stream.current();
|
|
3089
|
+
if (word === "true" || word === "false") return "atom";
|
|
3090
|
+
if (KEYWORDS.has(word)) return "keyword";
|
|
3091
|
+
if (TYPES.has(word)) return "typeName";
|
|
3092
|
+
if (/^[A-Z]/.test(word)) return "typeName";
|
|
3093
|
+
return "variableName";
|
|
3094
|
+
}
|
|
3095
|
+
if (/[+\-*/=<>!&|?.,:;]/.test(ch)) {
|
|
3096
|
+
stream.next();
|
|
3097
|
+
stream.eatWhile(/[+\-*/=<>!&|.]/);
|
|
3098
|
+
return "operator";
|
|
3099
|
+
}
|
|
3100
|
+
stream.next();
|
|
3101
|
+
return null;
|
|
3102
|
+
},
|
|
3103
|
+
languageData: {
|
|
3104
|
+
commentTokens: { line: "#" }
|
|
3105
|
+
}
|
|
3106
|
+
};
|
|
3107
|
+
var nbtLanguage = StreamLanguage.define(nbtParser);
|
|
3108
|
+
function nbtLanguageSupport() {
|
|
3109
|
+
return new LanguageSupport(nbtLanguage, [indentUnit.of(" ")]);
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
// src/components/devtools/sources/lsp-extensions.ts
|
|
3113
|
+
import {
|
|
3114
|
+
ViewPlugin,
|
|
3115
|
+
hoverTooltip,
|
|
3116
|
+
keymap
|
|
3117
|
+
} from "@codemirror/view";
|
|
3118
|
+
import {
|
|
3119
|
+
autocompletion
|
|
3120
|
+
} from "@codemirror/autocomplete";
|
|
3121
|
+
import { setDiagnostics } from "@codemirror/lint";
|
|
3122
|
+
function toLspPos(doc, offset) {
|
|
3123
|
+
const line = doc.lineAt(offset);
|
|
3124
|
+
return { line: line.number - 1, character: offset - line.from };
|
|
3125
|
+
}
|
|
3126
|
+
function fromLspPos(doc, pos) {
|
|
3127
|
+
const lineNo = Math.min(Math.max(pos.line + 1, 1), doc.lines);
|
|
3128
|
+
const line = doc.line(lineNo);
|
|
3129
|
+
return Math.min(line.from + Math.max(pos.character, 0), line.to);
|
|
3130
|
+
}
|
|
3131
|
+
function toCmDiagnostics(doc, diags) {
|
|
3132
|
+
return diags.map((d) => {
|
|
3133
|
+
const from = fromLspPos(doc, d.range.start);
|
|
3134
|
+
let to = fromLspPos(doc, d.range.end);
|
|
3135
|
+
if (to <= from) to = Math.min(from + 1, doc.length);
|
|
3136
|
+
const severity = d.severity === 2 ? "warning" : d.severity === 3 || d.severity === 4 ? "info" : "error";
|
|
3137
|
+
return { from, to, severity, message: d.message, source: d.source ?? "nbt" };
|
|
3138
|
+
});
|
|
3139
|
+
}
|
|
3140
|
+
function completionType(kind) {
|
|
3141
|
+
switch (kind) {
|
|
3142
|
+
case 3:
|
|
3143
|
+
return "function";
|
|
3144
|
+
case 5:
|
|
3145
|
+
return "property";
|
|
3146
|
+
case 13:
|
|
3147
|
+
return "enum";
|
|
3148
|
+
case 14:
|
|
3149
|
+
return "keyword";
|
|
3150
|
+
case 22:
|
|
3151
|
+
return "class";
|
|
3152
|
+
default:
|
|
3153
|
+
return "variable";
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
function lspExtensions(client, uri, onGotoDefinition) {
|
|
3157
|
+
const syncPlugin = ViewPlugin.fromClass(
|
|
3158
|
+
class {
|
|
3159
|
+
constructor(view) {
|
|
3160
|
+
this.view = view;
|
|
3161
|
+
this.timer = null;
|
|
3162
|
+
client.didOpen(uri, view.state.doc.toString());
|
|
3163
|
+
this.unsubscribe = client.onDiagnostics(uri, (diags) => {
|
|
3164
|
+
const cm = toCmDiagnostics(this.view.state.doc, diags);
|
|
3165
|
+
this.view.dispatch(setDiagnostics(this.view.state, cm));
|
|
3166
|
+
});
|
|
3167
|
+
}
|
|
3168
|
+
update(u) {
|
|
3169
|
+
if (!u.docChanged) return;
|
|
3170
|
+
if (this.timer) clearTimeout(this.timer);
|
|
3171
|
+
this.timer = setTimeout(() => {
|
|
3172
|
+
this.timer = null;
|
|
3173
|
+
client.didChange(uri, this.view.state.doc.toString());
|
|
3174
|
+
}, 200);
|
|
3175
|
+
}
|
|
3176
|
+
destroy() {
|
|
3177
|
+
if (this.timer) {
|
|
3178
|
+
clearTimeout(this.timer);
|
|
3179
|
+
client.didChange(uri, this.view.state.doc.toString());
|
|
3180
|
+
}
|
|
3181
|
+
this.unsubscribe();
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
);
|
|
3185
|
+
const completionSource = async (ctx) => {
|
|
3186
|
+
const word = ctx.matchBefore(/\w*/);
|
|
3187
|
+
if (!word) return null;
|
|
3188
|
+
if (word.from === word.to && !ctx.explicit) {
|
|
3189
|
+
const before = ctx.state.doc.sliceString(Math.max(0, ctx.pos - 1), ctx.pos);
|
|
3190
|
+
if (before !== "." && before !== ":" && before !== '"') return null;
|
|
3191
|
+
}
|
|
3192
|
+
client.didChange(uri, ctx.state.doc.toString());
|
|
3193
|
+
let result;
|
|
3194
|
+
try {
|
|
3195
|
+
result = await client.completion(uri, toLspPos(ctx.state.doc, ctx.pos));
|
|
3196
|
+
} catch {
|
|
3197
|
+
return null;
|
|
3198
|
+
}
|
|
3199
|
+
const items = Array.isArray(result) ? result : result?.items ?? [];
|
|
3200
|
+
if (items.length === 0) return null;
|
|
3201
|
+
return {
|
|
3202
|
+
from: word.from,
|
|
3203
|
+
options: items.map((it) => ({
|
|
3204
|
+
label: it.label,
|
|
3205
|
+
type: completionType(it.kind),
|
|
3206
|
+
detail: it.detail
|
|
3207
|
+
}))
|
|
3208
|
+
};
|
|
3209
|
+
};
|
|
3210
|
+
const hover = hoverTooltip(async (view, pos) => {
|
|
3211
|
+
let result;
|
|
3212
|
+
try {
|
|
3213
|
+
result = await client.hover(uri, toLspPos(view.state.doc, pos));
|
|
3214
|
+
} catch {
|
|
3215
|
+
return null;
|
|
3216
|
+
}
|
|
3217
|
+
const value = result?.contents?.value;
|
|
3218
|
+
if (!value) return null;
|
|
3219
|
+
return {
|
|
3220
|
+
pos,
|
|
3221
|
+
create: () => {
|
|
3222
|
+
const dom = document.createElement("pre");
|
|
3223
|
+
dom.className = "nbt-hover-tooltip";
|
|
3224
|
+
dom.style.cssText = "margin:0;padding:6px 8px;max-width:480px;white-space:pre-wrap;font-size:11px;";
|
|
3225
|
+
dom.textContent = value.replace(/```\w*\n?/g, "");
|
|
3226
|
+
return { dom };
|
|
3227
|
+
}
|
|
3228
|
+
};
|
|
3229
|
+
});
|
|
3230
|
+
const gotoDef = keymap.of([
|
|
3231
|
+
{
|
|
3232
|
+
key: "F12",
|
|
3233
|
+
run: (view) => {
|
|
3234
|
+
const pos = toLspPos(view.state.doc, view.state.selection.main.head);
|
|
3235
|
+
client.definition(uri, pos).then((result) => {
|
|
3236
|
+
const loc = Array.isArray(result) ? result[0] ?? null : result;
|
|
3237
|
+
if (!loc?.uri) return;
|
|
3238
|
+
if (loc.uri === uri) {
|
|
3239
|
+
const off = fromLspPos(view.state.doc, loc.range.start);
|
|
3240
|
+
view.dispatch({
|
|
3241
|
+
selection: { anchor: off },
|
|
3242
|
+
scrollIntoView: true
|
|
3243
|
+
});
|
|
3244
|
+
} else {
|
|
3245
|
+
onGotoDefinition?.(loc.uri, loc.range.start.line);
|
|
3246
|
+
}
|
|
3247
|
+
}).catch(() => {
|
|
3248
|
+
});
|
|
3249
|
+
return true;
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
]);
|
|
3253
|
+
return [
|
|
3254
|
+
syncPlugin,
|
|
3255
|
+
autocompletion({ override: [completionSource] }),
|
|
3256
|
+
hover,
|
|
3257
|
+
gotoDef
|
|
3258
|
+
];
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
// src/components/devtools/sources/nbt-editor.tsx
|
|
3262
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
3263
|
+
var nbtTheme = EditorView2.theme(
|
|
3264
|
+
{
|
|
3265
|
+
"&": {
|
|
3266
|
+
height: "100%",
|
|
3267
|
+
fontSize: "12px",
|
|
3268
|
+
backgroundColor: "var(--background)",
|
|
3269
|
+
color: "var(--foreground)"
|
|
3270
|
+
},
|
|
3271
|
+
".cm-content": { fontFamily: "ui-monospace, monospace" },
|
|
3272
|
+
".cm-gutters": {
|
|
3273
|
+
backgroundColor: "var(--background)",
|
|
3274
|
+
color: "var(--muted-foreground)",
|
|
3275
|
+
borderRight: "1px solid var(--border)"
|
|
3276
|
+
},
|
|
3277
|
+
".cm-activeLine": { backgroundColor: "color-mix(in srgb, var(--accent) 35%, transparent)" },
|
|
3278
|
+
"&.cm-focused": { outline: "none" },
|
|
3279
|
+
".cm-tooltip": {
|
|
3280
|
+
backgroundColor: "var(--popover)",
|
|
3281
|
+
color: "var(--popover-foreground)",
|
|
3282
|
+
border: "1px solid var(--border)"
|
|
3283
|
+
}
|
|
3284
|
+
},
|
|
3285
|
+
{ dark: true }
|
|
3286
|
+
);
|
|
3287
|
+
var NbtEditor = ({
|
|
3288
|
+
uri,
|
|
3289
|
+
value,
|
|
3290
|
+
readOnly = false,
|
|
3291
|
+
lsp,
|
|
3292
|
+
onChange,
|
|
3293
|
+
onSave,
|
|
3294
|
+
onGotoDefinition
|
|
3295
|
+
}) => {
|
|
3296
|
+
const hostRef = React10.useRef(null);
|
|
3297
|
+
const viewRef = React10.useRef(null);
|
|
3298
|
+
const onChangeRef = React10.useRef(onChange);
|
|
3299
|
+
onChangeRef.current = onChange;
|
|
3300
|
+
const onSaveRef = React10.useRef(onSave);
|
|
3301
|
+
onSaveRef.current = onSave;
|
|
3302
|
+
const onGotoRef = React10.useRef(onGotoDefinition);
|
|
3303
|
+
onGotoRef.current = onGotoDefinition;
|
|
3304
|
+
React10.useEffect(() => {
|
|
3305
|
+
const host = hostRef.current;
|
|
3306
|
+
if (!host) return;
|
|
3307
|
+
const extensions = [
|
|
3308
|
+
lineNumbers(),
|
|
3309
|
+
history(),
|
|
3310
|
+
drawSelection(),
|
|
3311
|
+
highlightActiveLine(),
|
|
3312
|
+
bracketMatching(),
|
|
3313
|
+
closeBrackets(),
|
|
3314
|
+
lintGutter(),
|
|
3315
|
+
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
3316
|
+
nbtLanguageSupport(),
|
|
3317
|
+
nbtTheme,
|
|
3318
|
+
keymap2.of([
|
|
3319
|
+
{
|
|
3320
|
+
key: "Mod-s",
|
|
3321
|
+
run: (view2) => {
|
|
3322
|
+
onSaveRef.current?.(view2.state.doc.toString());
|
|
3323
|
+
return true;
|
|
3324
|
+
}
|
|
3325
|
+
},
|
|
3326
|
+
...defaultKeymap,
|
|
3327
|
+
...historyKeymap,
|
|
3328
|
+
...searchKeymap,
|
|
3329
|
+
...completionKeymap,
|
|
3330
|
+
indentWithTab
|
|
3331
|
+
]),
|
|
3332
|
+
EditorView2.updateListener.of((u) => {
|
|
3333
|
+
if (u.docChanged) onChangeRef.current?.(u.state.doc.toString());
|
|
3334
|
+
}),
|
|
3335
|
+
EditorState.readOnly.of(readOnly)
|
|
3336
|
+
];
|
|
3337
|
+
if (lsp && !readOnly) {
|
|
3338
|
+
extensions.push(
|
|
3339
|
+
lspExtensions(lsp, uri, (defUri, line) => onGotoRef.current?.(defUri, line))
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
3342
|
+
const view = new EditorView2({
|
|
3343
|
+
state: EditorState.create({ doc: value, extensions }),
|
|
3344
|
+
parent: host
|
|
3345
|
+
});
|
|
3346
|
+
viewRef.current = view;
|
|
3347
|
+
return () => {
|
|
3348
|
+
view.destroy();
|
|
3349
|
+
viewRef.current = null;
|
|
3350
|
+
};
|
|
3351
|
+
}, [uri, readOnly, lsp]);
|
|
3352
|
+
return /* @__PURE__ */ jsx14("div", { ref: hostRef, className: "h-full min-h-0 overflow-hidden" });
|
|
3353
|
+
};
|
|
3354
|
+
|
|
3355
|
+
// src/components/devtools/sources/lsp-client.ts
|
|
3356
|
+
var NbtLspClient = class {
|
|
3357
|
+
constructor(url) {
|
|
3358
|
+
this.rootUri = null;
|
|
3359
|
+
this.ws = null;
|
|
3360
|
+
this.nextId = 1;
|
|
3361
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
3362
|
+
this.diagHandlers = /* @__PURE__ */ new Map();
|
|
3363
|
+
// Open buffers, kept for replay across reconnects.
|
|
3364
|
+
this.docs = /* @__PURE__ */ new Map();
|
|
3365
|
+
this.ready = false;
|
|
3366
|
+
this.queue = [];
|
|
3367
|
+
this.closed = false;
|
|
3368
|
+
this.retryMs = 500;
|
|
3369
|
+
this.url = url;
|
|
3370
|
+
this.connect();
|
|
3371
|
+
}
|
|
3372
|
+
dispose() {
|
|
3373
|
+
this.closed = true;
|
|
3374
|
+
this.ws?.close();
|
|
3375
|
+
this.pending.forEach((p) => p.reject(new Error("lsp client disposed")));
|
|
3376
|
+
this.pending.clear();
|
|
3377
|
+
}
|
|
3378
|
+
connect() {
|
|
3379
|
+
if (this.closed) return;
|
|
3380
|
+
const ws = new WebSocket(this.url);
|
|
3381
|
+
this.ws = ws;
|
|
3382
|
+
ws.onopen = () => {
|
|
3383
|
+
this.retryMs = 500;
|
|
3384
|
+
if (this.rootUri) this.sendInitialize(this.rootUri);
|
|
3385
|
+
};
|
|
3386
|
+
ws.onmessage = (ev) => {
|
|
3387
|
+
if (typeof ev.data !== "string") return;
|
|
3388
|
+
this.onMessage(ev.data);
|
|
3389
|
+
};
|
|
3390
|
+
ws.onclose = () => {
|
|
3391
|
+
this.ready = false;
|
|
3392
|
+
this.ws = null;
|
|
3393
|
+
for (const p of this.pending.values())
|
|
3394
|
+
p.reject(new Error("lsp connection closed"));
|
|
3395
|
+
this.pending.clear();
|
|
3396
|
+
if (!this.closed) {
|
|
3397
|
+
setTimeout(() => this.connect(), this.retryMs);
|
|
3398
|
+
this.retryMs = Math.min(this.retryMs * 2, 1e4);
|
|
3399
|
+
}
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
// Called once by the host with file://<projectRoot>; also re-sent after every
|
|
3403
|
+
// reconnect, followed by didOpen replays for tracked buffers.
|
|
3404
|
+
initialize(rootUri) {
|
|
3405
|
+
this.rootUri = rootUri;
|
|
3406
|
+
if (this.ws?.readyState === WebSocket.OPEN) this.sendInitialize(rootUri);
|
|
3407
|
+
}
|
|
3408
|
+
sendInitialize(rootUri) {
|
|
3409
|
+
const id = this.nextId++;
|
|
3410
|
+
this.pending.set(id, {
|
|
3411
|
+
resolve: () => {
|
|
3412
|
+
this.sendRaw({ jsonrpc: "2.0", method: "initialized", params: {} });
|
|
3413
|
+
this.ready = true;
|
|
3414
|
+
for (const [uri, d] of this.docs) {
|
|
3415
|
+
this.sendRaw({
|
|
3416
|
+
jsonrpc: "2.0",
|
|
3417
|
+
method: "textDocument/didOpen",
|
|
3418
|
+
params: {
|
|
3419
|
+
textDocument: {
|
|
3420
|
+
uri,
|
|
3421
|
+
languageId: "nbt",
|
|
3422
|
+
version: d.version,
|
|
3423
|
+
text: d.text
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
});
|
|
3427
|
+
}
|
|
3428
|
+
const q = this.queue;
|
|
3429
|
+
this.queue = [];
|
|
3430
|
+
for (const m of q) this.ws?.send(m);
|
|
3431
|
+
},
|
|
3432
|
+
reject: () => {
|
|
3433
|
+
}
|
|
3434
|
+
});
|
|
3435
|
+
this.ws?.send(
|
|
3436
|
+
JSON.stringify({
|
|
3437
|
+
jsonrpc: "2.0",
|
|
3438
|
+
id,
|
|
3439
|
+
method: "initialize",
|
|
3440
|
+
params: { rootUri }
|
|
3441
|
+
})
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
sendRaw(msg) {
|
|
3445
|
+
const s = JSON.stringify(msg);
|
|
3446
|
+
if (this.ready && this.ws?.readyState === WebSocket.OPEN) this.ws.send(s);
|
|
3447
|
+
else this.queue.push(s);
|
|
3448
|
+
}
|
|
3449
|
+
onMessage(data) {
|
|
3450
|
+
let msg;
|
|
3451
|
+
try {
|
|
3452
|
+
msg = JSON.parse(data);
|
|
3453
|
+
} catch {
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
if (msg.id != null && (msg.result !== void 0 || msg.error)) {
|
|
3457
|
+
const p = this.pending.get(msg.id);
|
|
3458
|
+
if (!p) return;
|
|
3459
|
+
this.pending.delete(msg.id);
|
|
3460
|
+
if (msg.error) p.reject(new Error(msg.error.message ?? "lsp error"));
|
|
3461
|
+
else p.resolve(msg.result);
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3464
|
+
if (msg.method === "textDocument/publishDiagnostics") {
|
|
3465
|
+
const uri = msg.params?.uri;
|
|
3466
|
+
const handler = this.diagHandlers.get(uri);
|
|
3467
|
+
if (handler) handler(msg.params?.diagnostics ?? []);
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
request(method, params) {
|
|
3471
|
+
const id = this.nextId++;
|
|
3472
|
+
const p = new Promise((resolve, reject) => {
|
|
3473
|
+
this.pending.set(id, { resolve, reject });
|
|
3474
|
+
});
|
|
3475
|
+
this.sendRaw({ jsonrpc: "2.0", id, method, params });
|
|
3476
|
+
return p;
|
|
3477
|
+
}
|
|
3478
|
+
onDiagnostics(uri, handler) {
|
|
3479
|
+
this.diagHandlers.set(uri, handler);
|
|
3480
|
+
return () => {
|
|
3481
|
+
if (this.diagHandlers.get(uri) === handler) this.diagHandlers.delete(uri);
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
didOpen(uri, text) {
|
|
3485
|
+
const existing = this.docs.get(uri);
|
|
3486
|
+
if (existing) {
|
|
3487
|
+
this.didChange(uri, text);
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
this.docs.set(uri, { text, version: 1 });
|
|
3491
|
+
this.sendRaw({
|
|
3492
|
+
jsonrpc: "2.0",
|
|
3493
|
+
method: "textDocument/didOpen",
|
|
3494
|
+
params: { textDocument: { uri, languageId: "nbt", version: 1, text } }
|
|
3495
|
+
});
|
|
3496
|
+
}
|
|
3497
|
+
didChange(uri, text) {
|
|
3498
|
+
const d = this.docs.get(uri);
|
|
3499
|
+
if (!d) return this.didOpen(uri, text);
|
|
3500
|
+
if (d.text === text) return;
|
|
3501
|
+
d.text = text;
|
|
3502
|
+
d.version += 1;
|
|
3503
|
+
this.sendRaw({
|
|
3504
|
+
jsonrpc: "2.0",
|
|
3505
|
+
method: "textDocument/didChange",
|
|
3506
|
+
params: {
|
|
3507
|
+
textDocument: { uri, version: d.version },
|
|
3508
|
+
contentChanges: [{ text }]
|
|
3509
|
+
}
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
didClose(uri) {
|
|
3513
|
+
if (!this.docs.delete(uri)) return;
|
|
3514
|
+
this.sendRaw({
|
|
3515
|
+
jsonrpc: "2.0",
|
|
3516
|
+
method: "textDocument/didClose",
|
|
3517
|
+
params: { textDocument: { uri } }
|
|
3518
|
+
});
|
|
3519
|
+
}
|
|
3520
|
+
completion(uri, pos) {
|
|
3521
|
+
return this.request("textDocument/completion", {
|
|
3522
|
+
textDocument: { uri },
|
|
3523
|
+
position: pos
|
|
3524
|
+
});
|
|
3525
|
+
}
|
|
3526
|
+
hover(uri, pos) {
|
|
3527
|
+
return this.request("textDocument/hover", {
|
|
3528
|
+
textDocument: { uri },
|
|
3529
|
+
position: pos
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
definition(uri, pos) {
|
|
3533
|
+
return this.request("textDocument/definition", {
|
|
3534
|
+
textDocument: { uri },
|
|
3535
|
+
position: pos
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
};
|
|
3539
|
+
|
|
3540
|
+
// src/components/devtools/sources/sources-tab.tsx
|
|
3541
|
+
import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3542
|
+
var bufKey = (s) => `${s.cart}:${s.path}`;
|
|
3543
|
+
function migrationStamp() {
|
|
3544
|
+
const d = /* @__PURE__ */ new Date();
|
|
3545
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
3546
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
3547
|
+
}
|
|
3548
|
+
var SourcesTab = () => {
|
|
3549
|
+
const { apiBaseUrl } = useDevToolsConfig();
|
|
3550
|
+
const status = useDevMode();
|
|
3551
|
+
const api = useDevFilesApi();
|
|
3552
|
+
const { sourcesCart, setSourcesCart, sourcesFile, setSourcesFile, sourcesTreeW, setSourcesTreeW } = useDevTools();
|
|
3553
|
+
const [carts, setCarts] = React11.useState([]);
|
|
3554
|
+
const [buffers, setBuffers] = React11.useState(/* @__PURE__ */ new Map());
|
|
3555
|
+
const [statusMsg, setStatusMsg] = React11.useState("");
|
|
3556
|
+
const [diags, setDiags] = React11.useState([]);
|
|
3557
|
+
const [applying, setApplying] = React11.useState(false);
|
|
3558
|
+
const selected = sourcesCart && sourcesFile ? { cart: sourcesCart, path: sourcesFile } : null;
|
|
3559
|
+
const lspRef = React11.useRef(null);
|
|
3560
|
+
React11.useEffect(() => {
|
|
3561
|
+
if (!status?.dev) return;
|
|
3562
|
+
const client = new NbtLspClient(`${wsBaseFrom(apiBaseUrl)}/_console/dev/lsp`);
|
|
3563
|
+
client.initialize(`file://${status.projectRoot}`);
|
|
3564
|
+
lspRef.current = client;
|
|
3565
|
+
return () => {
|
|
3566
|
+
client.dispose();
|
|
3567
|
+
lspRef.current = null;
|
|
3568
|
+
};
|
|
3569
|
+
}, [status?.dev, status?.projectRoot, apiBaseUrl]);
|
|
3570
|
+
const refreshCarts = React11.useCallback(() => {
|
|
3571
|
+
api.listCarts().then(setCarts).catch((e) => setStatusMsg(String(e.message ?? e)));
|
|
3572
|
+
}, [api]);
|
|
3573
|
+
React11.useEffect(() => {
|
|
3574
|
+
if (status?.dev) refreshCarts();
|
|
3575
|
+
}, [status?.dev, refreshCarts]);
|
|
3576
|
+
const cartOf = React11.useCallback(
|
|
3577
|
+
(name) => carts.find((c) => c.name === name),
|
|
3578
|
+
[carts]
|
|
3579
|
+
);
|
|
3580
|
+
const openFile = React11.useCallback(
|
|
3581
|
+
(sel) => {
|
|
3582
|
+
setSourcesCart(sel.cart);
|
|
3583
|
+
setSourcesFile(sel.path);
|
|
3584
|
+
setDiags([]);
|
|
3585
|
+
setStatusMsg("");
|
|
3586
|
+
const key = bufKey(sel);
|
|
3587
|
+
if (buffers.has(key)) return;
|
|
3588
|
+
api.readFile(sel.cart, sel.path).then(({ content, writable }) => {
|
|
3589
|
+
setBuffers((prev) => {
|
|
3590
|
+
if (prev.has(key)) return prev;
|
|
3591
|
+
const next = new Map(prev);
|
|
3592
|
+
next.set(key, { saved: content, current: content, writable });
|
|
3593
|
+
return next;
|
|
3594
|
+
});
|
|
3595
|
+
}).catch((e) => setStatusMsg(String(e.message ?? e)));
|
|
3596
|
+
},
|
|
3597
|
+
[api, buffers, setSourcesCart, setSourcesFile]
|
|
3598
|
+
);
|
|
3599
|
+
React11.useEffect(() => {
|
|
3600
|
+
if (selected && carts.length > 0 && !buffers.has(bufKey(selected))) openFile(selected);
|
|
3601
|
+
}, [carts]);
|
|
3602
|
+
const buf = selected ? buffers.get(bufKey(selected)) : void 0;
|
|
3603
|
+
const dirtyKeys = React11.useMemo(() => {
|
|
3604
|
+
const s = /* @__PURE__ */ new Set();
|
|
3605
|
+
for (const [k, b] of buffers) if (b.current !== b.saved) s.add(k);
|
|
3606
|
+
return s;
|
|
3607
|
+
}, [buffers]);
|
|
3608
|
+
React11.useEffect(() => {
|
|
3609
|
+
if (dirtyKeys.size === 0) return;
|
|
3610
|
+
const onBeforeUnload = (e) => e.preventDefault();
|
|
3611
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
3612
|
+
return () => window.removeEventListener("beforeunload", onBeforeUnload);
|
|
3613
|
+
}, [dirtyKeys.size]);
|
|
3614
|
+
const onChange = React11.useCallback(
|
|
3615
|
+
(text) => {
|
|
3616
|
+
if (!selected) return;
|
|
3617
|
+
const key = bufKey(selected);
|
|
3618
|
+
setBuffers((prev) => {
|
|
3619
|
+
const b = prev.get(key);
|
|
3620
|
+
if (!b || b.current === text) return prev;
|
|
3621
|
+
const next = new Map(prev);
|
|
3622
|
+
next.set(key, { ...b, current: text });
|
|
3623
|
+
return next;
|
|
3624
|
+
});
|
|
3625
|
+
},
|
|
3626
|
+
[selected]
|
|
3627
|
+
);
|
|
3628
|
+
const onSave = React11.useCallback(
|
|
3629
|
+
(text) => {
|
|
3630
|
+
if (!selected) return;
|
|
3631
|
+
const key = bufKey(selected);
|
|
3632
|
+
api.writeFile(selected.cart, selected.path, text).then((r) => {
|
|
3633
|
+
setDiags(r.diagnostics);
|
|
3634
|
+
setStatusMsg(
|
|
3635
|
+
r.ok ? `saved ${selected.path}` : `saved ${selected.path} \u2014 ${r.diagnostics.length} problem(s)`
|
|
3636
|
+
);
|
|
3637
|
+
setBuffers((prev) => {
|
|
3638
|
+
const b = prev.get(key);
|
|
3639
|
+
if (!b) return prev;
|
|
3640
|
+
const next = new Map(prev);
|
|
3641
|
+
next.set(key, { ...b, saved: text, current: text });
|
|
3642
|
+
return next;
|
|
3643
|
+
});
|
|
3644
|
+
refreshCarts();
|
|
3645
|
+
}).catch((e) => setStatusMsg(`save failed: ${e.message ?? e}`));
|
|
3646
|
+
},
|
|
3647
|
+
[api, selected, refreshCarts]
|
|
3648
|
+
);
|
|
3649
|
+
const onApply = React11.useCallback(() => {
|
|
3650
|
+
if (!selected) return;
|
|
3651
|
+
setApplying(true);
|
|
3652
|
+
setStatusMsg(`applying ${selected.cart}\u2026`);
|
|
3653
|
+
api.applyCart(selected.cart).then(() => setStatusMsg(`applied ${selected.cart} \u2014 pending migrations ran, cart re-registered`)).catch((e) => setStatusMsg(`apply failed: ${e.message ?? e}`)).finally(() => setApplying(false));
|
|
3654
|
+
}, [api, selected]);
|
|
3655
|
+
const onNewCart = React11.useCallback(
|
|
3656
|
+
(name) => {
|
|
3657
|
+
setStatusMsg(`creating ${name}\u2026`);
|
|
3658
|
+
api.scaffoldCart(name).then(() => {
|
|
3659
|
+
setStatusMsg(`created ${name}`);
|
|
3660
|
+
refreshCarts();
|
|
3661
|
+
openFile({ cart: name, path: "schema.nbt" });
|
|
3662
|
+
}).catch((e) => setStatusMsg(`scaffold failed: ${e.message ?? e}`));
|
|
3663
|
+
},
|
|
3664
|
+
[api, refreshCarts, openFile]
|
|
3665
|
+
);
|
|
3666
|
+
const onNewMigration = React11.useCallback(
|
|
3667
|
+
(cart2) => {
|
|
3668
|
+
const path = `migrations/${migrationStamp()}_change/migration.nbt`;
|
|
3669
|
+
const key = `${cart2}:${path}`;
|
|
3670
|
+
setBuffers((prev) => {
|
|
3671
|
+
const next = new Map(prev);
|
|
3672
|
+
next.set(key, {
|
|
3673
|
+
saved: "",
|
|
3674
|
+
current: "migration change {\n \n}\n",
|
|
3675
|
+
writable: true
|
|
3676
|
+
});
|
|
3677
|
+
return next;
|
|
3678
|
+
});
|
|
3679
|
+
setSourcesCart(cart2);
|
|
3680
|
+
setSourcesFile(path);
|
|
3681
|
+
},
|
|
3682
|
+
[setSourcesCart, setSourcesFile]
|
|
3683
|
+
);
|
|
3684
|
+
const onGotoDefinition = React11.useCallback(
|
|
3685
|
+
(uri, _line) => {
|
|
3686
|
+
const path = uri.replace(/^file:\/\//, "");
|
|
3687
|
+
let best = null;
|
|
3688
|
+
for (const c of carts) {
|
|
3689
|
+
if (!c.sourceDir) continue;
|
|
3690
|
+
const prefix = `${c.sourceDir}/`;
|
|
3691
|
+
if (path.startsWith(prefix) && (!best || prefix.length > best.cart.length)) {
|
|
3692
|
+
best = { cart: c.name, rel: path.slice(prefix.length) };
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3695
|
+
if (best) openFile({ cart: best.cart, path: best.rel });
|
|
3696
|
+
},
|
|
3697
|
+
[carts, openFile]
|
|
3698
|
+
);
|
|
3699
|
+
const startTreeResize = React11.useCallback(
|
|
3700
|
+
(e) => {
|
|
3701
|
+
e.preventDefault();
|
|
3702
|
+
const startX = e.clientX;
|
|
3703
|
+
const startW = sourcesTreeW;
|
|
3704
|
+
const handle = e.currentTarget;
|
|
3705
|
+
handle.setPointerCapture(e.pointerId);
|
|
3706
|
+
const onMove = (ev) => setSourcesTreeW(Math.max(120, Math.min(480, startW + (ev.clientX - startX))));
|
|
3707
|
+
const onUp = () => {
|
|
3708
|
+
handle.removeEventListener("pointermove", onMove);
|
|
3709
|
+
handle.removeEventListener("pointerup", onUp);
|
|
3710
|
+
};
|
|
3711
|
+
handle.addEventListener("pointermove", onMove);
|
|
3712
|
+
handle.addEventListener("pointerup", onUp);
|
|
3713
|
+
},
|
|
3714
|
+
[sourcesTreeW, setSourcesTreeW]
|
|
3715
|
+
);
|
|
3716
|
+
if (!status?.dev) {
|
|
3717
|
+
return /* @__PURE__ */ jsx15("div", { className: "flex h-full items-center justify-center text-[12px] text-muted-foreground", children: "Sources requires the console to run in dev mode (`console up --dev`)." });
|
|
3718
|
+
}
|
|
3719
|
+
const cart = selected ? cartOf(selected.cart) : void 0;
|
|
3720
|
+
const editorUri = selected && cart?.sourceDir ? `file://${cart.sourceDir}/${selected.path}` : selected ? `file://${status.projectRoot}/.readonly/${selected.cart}/${selected.path}` : "";
|
|
3721
|
+
const isDirty = selected ? dirtyKeys.has(bufKey(selected)) : false;
|
|
3722
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
3723
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex min-h-0 flex-1", children: [
|
|
3724
|
+
/* @__PURE__ */ jsx15("div", { style: { width: sourcesTreeW }, className: "shrink-0 border-r border-border", children: /* @__PURE__ */ jsx15(
|
|
3725
|
+
FileTree,
|
|
3726
|
+
{
|
|
3727
|
+
carts,
|
|
3728
|
+
selected,
|
|
3729
|
+
dirtyKeys,
|
|
3730
|
+
onSelect: openFile,
|
|
3731
|
+
onNewCart,
|
|
3732
|
+
onNewMigration
|
|
3733
|
+
}
|
|
3734
|
+
) }),
|
|
3735
|
+
/* @__PURE__ */ jsx15(
|
|
3736
|
+
"div",
|
|
3737
|
+
{
|
|
3738
|
+
onPointerDown: startTreeResize,
|
|
3739
|
+
style: { touchAction: "none" },
|
|
3740
|
+
className: "w-1 shrink-0 cursor-ew-resize bg-transparent hover:bg-accent/40"
|
|
3741
|
+
}
|
|
3742
|
+
),
|
|
3743
|
+
/* @__PURE__ */ jsx15("div", { className: "min-h-0 min-w-0 flex-1", children: selected && buf ? /* @__PURE__ */ jsx15(
|
|
3744
|
+
NbtEditor,
|
|
3745
|
+
{
|
|
3746
|
+
uri: editorUri,
|
|
3747
|
+
value: buf.current,
|
|
3748
|
+
readOnly: !buf.writable,
|
|
3749
|
+
lsp: lspRef.current,
|
|
3750
|
+
onChange,
|
|
3751
|
+
onSave,
|
|
3752
|
+
onGotoDefinition
|
|
3753
|
+
},
|
|
3754
|
+
bufKey(selected)
|
|
3755
|
+
) : /* @__PURE__ */ jsx15("div", { className: "flex h-full items-center justify-center text-[12px] text-muted-foreground", children: "Select a file \u2014 or create a cartridge with the + button." }) })
|
|
3756
|
+
] }),
|
|
3757
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex h-6 shrink-0 items-center gap-2 border-t border-border px-2 text-[11px]", children: [
|
|
3758
|
+
selected && buf?.writable && /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
3759
|
+
/* @__PURE__ */ jsxs9(
|
|
3760
|
+
"button",
|
|
3761
|
+
{
|
|
3762
|
+
type: "button",
|
|
3763
|
+
onClick: () => buf && onSave(buf.current),
|
|
3764
|
+
className: cn(
|
|
3765
|
+
"flex items-center gap-1 rounded px-1.5 py-0.5 hover:bg-accent",
|
|
3766
|
+
isDirty && "text-amber-400"
|
|
3767
|
+
),
|
|
3768
|
+
title: "Save (Ctrl+S) \u2014 mirrors to your local project folder",
|
|
3769
|
+
children: [
|
|
3770
|
+
/* @__PURE__ */ jsx15(Save, { className: "size-3" }),
|
|
3771
|
+
" Save"
|
|
3772
|
+
]
|
|
3773
|
+
}
|
|
3774
|
+
),
|
|
3775
|
+
/* @__PURE__ */ jsxs9(
|
|
3776
|
+
"button",
|
|
3777
|
+
{
|
|
3778
|
+
type: "button",
|
|
3779
|
+
onClick: onApply,
|
|
3780
|
+
disabled: applying || isDirty,
|
|
3781
|
+
className: "flex items-center gap-1 rounded px-1.5 py-0.5 hover:bg-accent disabled:opacity-40",
|
|
3782
|
+
title: isDirty ? "Save first" : "Re-install this cart from disk (runs your pending migrations)",
|
|
3783
|
+
children: [
|
|
3784
|
+
/* @__PURE__ */ jsx15(Play2, { className: "size-3" }),
|
|
3785
|
+
" Apply"
|
|
3786
|
+
]
|
|
3787
|
+
}
|
|
3788
|
+
)
|
|
3789
|
+
] }),
|
|
3790
|
+
selected && !buf?.writable && /* @__PURE__ */ jsxs9("span", { className: "text-muted-foreground", children: [
|
|
3791
|
+
"read-only (",
|
|
3792
|
+
cart?.reason ?? "no source mapping",
|
|
3793
|
+
")"
|
|
3794
|
+
] }),
|
|
3795
|
+
/* @__PURE__ */ jsx15("span", { className: "min-w-0 flex-1 truncate text-muted-foreground", children: statusMsg }),
|
|
3796
|
+
diags.length > 0 && /* @__PURE__ */ jsxs9("span", { className: "shrink-0 text-red-400", title: diags.map((d) => `${d.line}:${d.col} ${d.message}`).join("\n"), children: [
|
|
3797
|
+
diags.length,
|
|
3798
|
+
" problem",
|
|
3799
|
+
diags.length > 1 ? "s" : ""
|
|
3800
|
+
] })
|
|
3801
|
+
] })
|
|
3802
|
+
] });
|
|
3803
|
+
};
|
|
3804
|
+
var sources_tab_default = SourcesTab;
|
|
3805
|
+
|
|
2171
3806
|
// src/components/devtools/dev-tools.tsx
|
|
2172
|
-
import { jsx as
|
|
3807
|
+
import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2173
3808
|
var MIN_H = 120;
|
|
2174
3809
|
var MIN_W = 240;
|
|
2175
|
-
var
|
|
2176
|
-
|
|
2177
|
-
{ id: "
|
|
2178
|
-
{ id: "
|
|
2179
|
-
{ id: "
|
|
3810
|
+
var EDGE = 8;
|
|
3811
|
+
var BASE_TABS = [
|
|
3812
|
+
{ id: "console", title: "Console", render: () => /* @__PURE__ */ jsx16(console_tab_default, {}) },
|
|
3813
|
+
{ id: "network", title: "Network", render: () => /* @__PURE__ */ jsx16(network_tab_default, {}) },
|
|
3814
|
+
{ id: "data", title: "Data", render: () => /* @__PURE__ */ jsx16(data_tab_default, {}) }
|
|
2180
3815
|
];
|
|
3816
|
+
var SOURCES_TAB = {
|
|
3817
|
+
id: "sources",
|
|
3818
|
+
title: "Sources",
|
|
3819
|
+
render: () => /* @__PURE__ */ jsx16(sources_tab_default, {})
|
|
3820
|
+
};
|
|
3821
|
+
var SETTINGS_TAB = {
|
|
3822
|
+
id: "settings",
|
|
3823
|
+
title: "Settings",
|
|
3824
|
+
render: () => /* @__PURE__ */ jsx16(settings_tab_default, {})
|
|
3825
|
+
};
|
|
2181
3826
|
var DevTools = () => {
|
|
2182
3827
|
const {
|
|
2183
3828
|
open,
|
|
@@ -2191,21 +3836,66 @@ var DevTools = () => {
|
|
|
2191
3836
|
maximized,
|
|
2192
3837
|
setMaximized
|
|
2193
3838
|
} = useDevTools();
|
|
2194
|
-
const panelRef =
|
|
2195
|
-
|
|
3839
|
+
const panelRef = React12.useRef(null);
|
|
3840
|
+
const hasOpenedRef = React12.useRef(false);
|
|
3841
|
+
const devMode = useDevMode();
|
|
3842
|
+
const tabs = React12.useMemo(
|
|
3843
|
+
() => devMode?.dev ? [...BASE_TABS, SOURCES_TAB, SETTINGS_TAB] : [...BASE_TABS, SETTINGS_TAB],
|
|
3844
|
+
[devMode?.dev]
|
|
3845
|
+
);
|
|
3846
|
+
const [vp, setVp] = React12.useState(
|
|
3847
|
+
() => typeof window === "undefined" ? { w: 0, h: 0 } : { w: window.innerWidth, h: window.innerHeight }
|
|
3848
|
+
);
|
|
3849
|
+
React12.useEffect(() => {
|
|
3850
|
+
const onResize = () => setVp({ w: window.innerWidth, h: window.innerHeight });
|
|
3851
|
+
window.addEventListener("resize", onResize);
|
|
3852
|
+
window.visualViewport?.addEventListener("resize", onResize);
|
|
3853
|
+
return () => {
|
|
3854
|
+
window.removeEventListener("resize", onResize);
|
|
3855
|
+
window.visualViewport?.removeEventListener("resize", onResize);
|
|
3856
|
+
};
|
|
3857
|
+
}, []);
|
|
3858
|
+
React12.useEffect(() => {
|
|
2196
3859
|
if (activeTab) return;
|
|
2197
|
-
if (
|
|
2198
|
-
}, [activeTab, setActiveTab]);
|
|
2199
|
-
|
|
3860
|
+
if (tabs.length > 0) setActiveTab(tabs[0].id);
|
|
3861
|
+
}, [activeTab, setActiveTab, tabs]);
|
|
3862
|
+
React12.useEffect(() => {
|
|
3863
|
+
const panel = panelRef.current;
|
|
3864
|
+
if (!panel) return;
|
|
3865
|
+
const onWheel = (e) => {
|
|
3866
|
+
let node = e.target;
|
|
3867
|
+
while (node && node !== panel) {
|
|
3868
|
+
const s = getComputedStyle(node);
|
|
3869
|
+
const vertical = Math.abs(e.deltaY) >= Math.abs(e.deltaX);
|
|
3870
|
+
if (vertical) {
|
|
3871
|
+
const scrollable = (s.overflowY === "auto" || s.overflowY === "scroll") && node.scrollHeight > node.clientHeight;
|
|
3872
|
+
if (scrollable) {
|
|
3873
|
+
const atTop = node.scrollTop <= 0;
|
|
3874
|
+
const atBottom = node.scrollTop + node.clientHeight >= node.scrollHeight - 1;
|
|
3875
|
+
if (!(e.deltaY < 0 && atTop || e.deltaY > 0 && atBottom)) return;
|
|
3876
|
+
}
|
|
3877
|
+
} else {
|
|
3878
|
+
const scrollable = (s.overflowX === "auto" || s.overflowX === "scroll") && node.scrollWidth > node.clientWidth;
|
|
3879
|
+
if (scrollable) {
|
|
3880
|
+
const atLeft = node.scrollLeft <= 0;
|
|
3881
|
+
const atRight = node.scrollLeft + node.clientWidth >= node.scrollWidth - 1;
|
|
3882
|
+
if (!(e.deltaX < 0 && atLeft || e.deltaX > 0 && atRight)) return;
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
node = node.parentElement;
|
|
3886
|
+
}
|
|
3887
|
+
e.preventDefault();
|
|
3888
|
+
};
|
|
3889
|
+
panel.addEventListener("wheel", onWheel, { passive: false });
|
|
3890
|
+
return () => panel.removeEventListener("wheel", onWheel);
|
|
3891
|
+
}, [open]);
|
|
3892
|
+
const startResize = React12.useCallback(
|
|
2200
3893
|
(e) => {
|
|
2201
3894
|
e.preventDefault();
|
|
2202
3895
|
const panel = panelRef.current;
|
|
2203
3896
|
if (!panel) return;
|
|
2204
|
-
const parent = panel.parentElement;
|
|
2205
|
-
if (!parent) return;
|
|
2206
3897
|
const handle = e.currentTarget;
|
|
2207
3898
|
handle.setPointerCapture(e.pointerId);
|
|
2208
|
-
const parentRect = parent.getBoundingClientRect();
|
|
2209
3899
|
const startX = e.clientX;
|
|
2210
3900
|
const startY = e.clientY;
|
|
2211
3901
|
const startH = panel.offsetHeight;
|
|
@@ -2229,13 +3919,13 @@ var DevTools = () => {
|
|
|
2229
3919
|
const dy = startY - ev.clientY;
|
|
2230
3920
|
pendingNext = Math.max(
|
|
2231
3921
|
MIN_H,
|
|
2232
|
-
Math.min(
|
|
3922
|
+
Math.min(window.innerHeight - 24, startH + dy)
|
|
2233
3923
|
);
|
|
2234
3924
|
} else {
|
|
2235
3925
|
const dx = startX - ev.clientX;
|
|
2236
3926
|
pendingNext = Math.max(
|
|
2237
3927
|
MIN_W,
|
|
2238
|
-
Math.min(
|
|
3928
|
+
Math.min(window.innerWidth - 24, startW + dx)
|
|
2239
3929
|
);
|
|
2240
3930
|
}
|
|
2241
3931
|
if (!rafId) rafId = requestAnimationFrame(flush);
|
|
@@ -2264,81 +3954,93 @@ var DevTools = () => {
|
|
|
2264
3954
|
},
|
|
2265
3955
|
[dock, setSize]
|
|
2266
3956
|
);
|
|
2267
|
-
if (
|
|
2268
|
-
|
|
3957
|
+
if (open) hasOpenedRef.current = true;
|
|
3958
|
+
if (!hasOpenedRef.current) return null;
|
|
3959
|
+
const active = tabs.find((t) => t.id === activeTab) ?? tabs[0];
|
|
2269
3960
|
const positionClass = maximized ? "inset-0" : dock === "bottom" ? "inset-x-0 bottom-0" : "inset-y-0 right-0";
|
|
2270
|
-
const sizeStyle = maximized ? {} : dock === "bottom" ? { height: size.h } : { width: size.w };
|
|
2271
|
-
return
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
children: [
|
|
2283
|
-
!maximized && /* @__PURE__ */ jsx12(
|
|
2284
|
-
"div",
|
|
2285
|
-
{
|
|
2286
|
-
onPointerDown: startResize,
|
|
2287
|
-
style: { touchAction: "none" },
|
|
2288
|
-
className: cn(
|
|
2289
|
-
"absolute z-10 bg-transparent hover:bg-accent/40",
|
|
2290
|
-
dock === "bottom" ? "left-0 right-0 -top-px h-1.5 cursor-ns-resize" : "top-0 bottom-0 -left-px w-1.5 cursor-ew-resize"
|
|
2291
|
-
)
|
|
2292
|
-
}
|
|
3961
|
+
const sizeStyle = maximized ? {} : dock === "bottom" ? { height: Math.max(MIN_H, Math.min(size.h, vp.h - EDGE)) } : { width: Math.max(MIN_W, Math.min(size.w, vp.w - EDGE)) };
|
|
3962
|
+
return createPortal2(
|
|
3963
|
+
/* @__PURE__ */ jsxs10(
|
|
3964
|
+
"div",
|
|
3965
|
+
{
|
|
3966
|
+
ref: panelRef,
|
|
3967
|
+
style: sizeStyle,
|
|
3968
|
+
className: cn(
|
|
3969
|
+
"nimbit-devtools dark fixed z-50 flex flex-col border-border bg-background text-foreground text-[12px] shadow-lg",
|
|
3970
|
+
positionClass,
|
|
3971
|
+
dock === "bottom" ? "border-t" : "border-l",
|
|
3972
|
+
!open && "hidden"
|
|
2293
3973
|
),
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
3974
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
3975
|
+
children: [
|
|
3976
|
+
!maximized && /* @__PURE__ */ jsx16(
|
|
3977
|
+
"div",
|
|
3978
|
+
{
|
|
3979
|
+
onPointerDown: startResize,
|
|
3980
|
+
style: { touchAction: "none" },
|
|
3981
|
+
className: cn(
|
|
3982
|
+
"absolute z-10 bg-transparent hover:bg-accent/40",
|
|
3983
|
+
dock === "bottom" ? "left-0 right-0 -top-px h-1.5 cursor-ns-resize" : "top-0 bottom-0 -left-px w-1.5 cursor-ew-resize"
|
|
3984
|
+
)
|
|
3985
|
+
}
|
|
3986
|
+
),
|
|
3987
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex h-7 shrink-0 items-center border-b border-border bg-background pl-1 pr-1 select-none", children: [
|
|
3988
|
+
/* @__PURE__ */ jsx16("div", { className: "flex min-w-0 flex-1 items-center gap-0.5 overflow-x-auto", children: tabs.map((tab) => {
|
|
3989
|
+
const isActive = tab.id === active?.id;
|
|
3990
|
+
return /* @__PURE__ */ jsx16(
|
|
3991
|
+
"button",
|
|
3992
|
+
{
|
|
3993
|
+
type: "button",
|
|
3994
|
+
"data-active": isActive,
|
|
3995
|
+
onClick: () => setActiveTab(tab.id),
|
|
3996
|
+
className: cn(
|
|
3997
|
+
"h-5 shrink-0 rounded-md px-2 text-[12px] leading-none outline-none transition-colors",
|
|
3998
|
+
"text-foreground hover:bg-accent hover:text-accent-foreground",
|
|
3999
|
+
"data-[active=true]:bg-accent data-[active=true]:text-accent-foreground"
|
|
4000
|
+
),
|
|
4001
|
+
children: /* @__PURE__ */ jsxs10("span", { className: "inline-flex items-center gap-1", children: [
|
|
4002
|
+
tab.icon,
|
|
4003
|
+
tab.title
|
|
4004
|
+
] })
|
|
4005
|
+
},
|
|
4006
|
+
tab.id
|
|
4007
|
+
);
|
|
4008
|
+
}) }),
|
|
4009
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex shrink-0 items-center gap-0.5 pl-2", children: [
|
|
4010
|
+
/* @__PURE__ */ jsx16(
|
|
4011
|
+
IconBtn,
|
|
4012
|
+
{
|
|
4013
|
+
label: dock === "bottom" ? "Dock right" : "Dock bottom",
|
|
4014
|
+
onClick: () => setDock(dock === "bottom" ? "right" : "bottom"),
|
|
4015
|
+
children: dock === "bottom" ? /* @__PURE__ */ jsx16(PanelRight, { className: "size-3.5" }) : /* @__PURE__ */ jsx16(PanelBottom, { className: "size-3.5" })
|
|
4016
|
+
}
|
|
4017
|
+
),
|
|
4018
|
+
/* @__PURE__ */ jsx16(
|
|
4019
|
+
IconBtn,
|
|
4020
|
+
{
|
|
4021
|
+
label: maximized ? "Restore" : "Maximize",
|
|
4022
|
+
onClick: () => setMaximized(!maximized),
|
|
4023
|
+
children: maximized ? /* @__PURE__ */ jsx16(Minimize2, { className: "size-3.5" }) : /* @__PURE__ */ jsx16(Maximize2, { className: "size-3.5" })
|
|
4024
|
+
}
|
|
4025
|
+
),
|
|
4026
|
+
/* @__PURE__ */ jsx16(IconBtn, { label: "Close", onClick: () => setOpen(false), children: /* @__PURE__ */ jsx16(X2, { className: "size-3.5" }) })
|
|
4027
|
+
] })
|
|
4028
|
+
] }),
|
|
4029
|
+
/* @__PURE__ */ jsx16("div", { className: "min-h-0 min-w-0 flex-1 overflow-hidden", children: tabs.map((tab) => /* @__PURE__ */ jsx16(
|
|
4030
|
+
"div",
|
|
4031
|
+
{
|
|
4032
|
+
className: cn("h-full w-full", tab.id !== active?.id && "hidden"),
|
|
4033
|
+
children: tab.render()
|
|
4034
|
+
},
|
|
4035
|
+
tab.id
|
|
4036
|
+
)) })
|
|
4037
|
+
]
|
|
4038
|
+
}
|
|
4039
|
+
),
|
|
4040
|
+
document.body
|
|
2339
4041
|
);
|
|
2340
4042
|
};
|
|
2341
|
-
var IconBtn = ({ label, className, children, ...rest }) => /* @__PURE__ */
|
|
4043
|
+
var IconBtn = ({ label, className, children, ...rest }) => /* @__PURE__ */ jsx16(
|
|
2342
4044
|
"button",
|
|
2343
4045
|
{
|
|
2344
4046
|
type: "button",
|
|
@@ -2355,11 +4057,11 @@ var IconBtn = ({ label, className, children, ...rest }) => /* @__PURE__ */ jsx12
|
|
|
2355
4057
|
var dev_tools_default = DevTools;
|
|
2356
4058
|
|
|
2357
4059
|
// src/index.tsx
|
|
2358
|
-
import { jsx as
|
|
4060
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2359
4061
|
var NimbitDevTools = ({
|
|
2360
4062
|
apiBaseUrl = "",
|
|
2361
4063
|
defaultActiveTab
|
|
2362
|
-
}) => /* @__PURE__ */
|
|
4064
|
+
}) => /* @__PURE__ */ jsx17(DevToolsConfigProvider, { apiBaseUrl, children: /* @__PURE__ */ jsx17(DevToolsProvider, { defaultActiveTab, children: /* @__PURE__ */ jsx17(dev_tools_default, {}) }) });
|
|
2363
4065
|
var index_default = NimbitDevTools;
|
|
2364
4066
|
function toggleDevTools() {
|
|
2365
4067
|
window.dispatchEvent(new CustomEvent("devtools-toggle"));
|
|
@@ -2368,8 +4070,10 @@ export {
|
|
|
2368
4070
|
dev_tools_default as DevTools,
|
|
2369
4071
|
DevToolsConfigProvider,
|
|
2370
4072
|
DevToolsProvider,
|
|
4073
|
+
NbtEditor,
|
|
2371
4074
|
NimbitDevTools,
|
|
2372
4075
|
index_default as default,
|
|
4076
|
+
nbtLanguageSupport,
|
|
2373
4077
|
toggleDevTools,
|
|
2374
4078
|
useDevTools,
|
|
2375
4079
|
useDevToolsConfig
|