@party-stack/ontology-devtools 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,334 +1,16 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ArrowPathIcon, CheckIcon, InboxStackIcon, TrashIcon } from "@heroicons/react/24/outline";
3
- import { useLiveQuery } from "@tanstack/react-db";
4
- import { TanStackDevtools } from "@tanstack/react-devtools";
5
- import { useEffect, useId, useRef, useState } from "react";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Dialog } from "@base-ui/react/dialog";
3
+ import { Menu } from "@base-ui/react/menu";
4
+ import { Tabs } from "@base-ui/react/tabs";
5
+ import { Tooltip } from "@base-ui/react/tooltip";
6
+ import { ArrowPathIcon, Bars2Icon, CalendarDaysIcon, CheckIcon, CheckCircleIcon, ChevronDownIcon, ChevronUpIcon, ChevronUpDownIcon, CircleStackIcon, ClockIcon, CodeBracketSquareIcon, HashtagIcon, InboxStackIcon, LinkIcon, ListBulletIcon, MapIcon, MapPinIcon, PaperClipIcon, QuestionMarkCircleIcon, ShareIcon, Squares2X2Icon, TrashIcon, ViewColumnsIcon, } from "@heroicons/react/24/outline";
7
+ import NumberFlow from "@number-flow/react";
8
+ import { createReactPlugin } from "@tanstack/devtools-utils/react";
9
+ import { useLiveInfiniteQuery, useLiveQuery } from "@tanstack/react-db";
10
+ import { createColumnHelper, flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table";
11
+ import { useEffect, useId, useMemo, useRef, useState, } from "react";
6
12
  import { flushSync } from "react-dom";
7
- const styles = `
8
- .ps-outbox-root {
9
- width: 100%;
10
- height: 100%;
11
- min-width: 0;
12
- min-height: 0;
13
- }
14
- .ps-outbox {
15
- box-sizing: border-box;
16
- width: 100%;
17
- height: 100%;
18
- min-width: 0;
19
- min-height: 0;
20
- overflow: hidden auto;
21
- overscroll-behavior: contain;
22
- padding: 20px 22px;
23
- color: #e7e5e4;
24
- background:
25
- radial-gradient(circle at 8% 0%, rgba(232, 59, 50, .12), transparent 30%),
26
- linear-gradient(145deg, #171717 0%, #111113 100%);
27
- font: 13px/1.45 Inter, ui-sans-serif, system-ui, sans-serif;
28
- }
29
- .ps-outbox * { box-sizing: border-box; }
30
- .ps-outbox-header {
31
- display: flex;
32
- align-items: center;
33
- gap: 16px;
34
- margin-bottom: 20px;
35
- }
36
- .ps-outbox-heading {
37
- display: flex;
38
- align-items: center;
39
- gap: 11px;
40
- }
41
- .ps-outbox-mark {
42
- width: 30px;
43
- height: 30px;
44
- padding: 5px;
45
- border: 1px solid rgba(255, 255, 255, .1);
46
- border-radius: 10px;
47
- background: rgba(255, 255, 255, .06);
48
- box-shadow: 0 8px 24px rgba(0, 0, 0, .24);
49
- color: #fb7185;
50
- }
51
- .ps-outbox-mark svg { display: block; width: 100%; height: 100%; }
52
- .ps-outbox-title-row {
53
- display: flex;
54
- align-items: center;
55
- gap: 8px;
56
- }
57
- .ps-outbox-title {
58
- margin: 0;
59
- color: #fafaf9;
60
- font-size: 17px;
61
- font-weight: 700;
62
- letter-spacing: -.015em;
63
- }
64
- .ps-outbox-subtitle { margin: 2px 0 0; color: #a8a29e; font-size: 12px; }
65
- .ps-outbox-count {
66
- display: inline-flex;
67
- align-items: center;
68
- justify-content: center;
69
- min-width: 25px;
70
- padding: 2px 7px;
71
- border: 1px solid #3f3f46;
72
- border-radius: 999px;
73
- color: #d6d3d1;
74
- background: rgba(39, 39, 42, .75);
75
- text-align: center;
76
- font-size: 11px;
77
- font-variant-numeric: tabular-nums;
78
- }
79
- .ps-outbox-track {
80
- display: flex;
81
- align-items: flex-start;
82
- width: 100%;
83
- padding: 4px 2px 16px;
84
- overflow-x: auto;
85
- overscroll-behavior-x: contain;
86
- scrollbar-width: thin;
87
- scrollbar-color: #52525b transparent;
88
- scroll-snap-type: x proximity;
89
- }
90
- .ps-outbox-step {
91
- display: flex;
92
- flex: none;
93
- align-items: flex-start;
94
- }
95
- .ps-outbox-node {
96
- flex: 0 0 clamp(250px, 31vw, 330px);
97
- min-width: 0;
98
- scroll-snap-align: start;
99
- }
100
- .ps-outbox-connector {
101
- position: relative;
102
- flex: 0 0 52px;
103
- align-self: flex-start;
104
- height: 2px;
105
- margin: 30px 8px 0;
106
- border-radius: 999px;
107
- background: linear-gradient(90deg, #3f3f46, #52525b);
108
- }
109
- .ps-outbox-connector::before {
110
- position: absolute;
111
- z-index: 1;
112
- right: 0;
113
- top: -3px;
114
- width: 7px;
115
- height: 7px;
116
- content: "";
117
- border-top: 2px solid #71717a;
118
- border-right: 2px solid #71717a;
119
- transform: rotate(45deg);
120
- }
121
- .ps-outbox-card {
122
- position: relative;
123
- min-height: 190px;
124
- padding: 15px;
125
- overflow: hidden;
126
- border: 1px solid #36363b;
127
- border-radius: 14px;
128
- background: linear-gradient(155deg, rgba(45, 45, 50, .96), rgba(31, 31, 35, .96));
129
- box-shadow: 0 14px 36px rgba(0, 0, 0, .2);
130
- }
131
- .ps-outbox-card::before {
132
- position: absolute;
133
- inset: 0 auto 0 0;
134
- width: 3px;
135
- content: "";
136
- background: #38bdf8;
137
- }
138
- .ps-card-executing::before {
139
- background: #f59e0b;
140
- }
141
- .ps-card-failed::before { background: #ef4444; }
142
- .ps-outbox-card-header {
143
- display: flex;
144
- align-items: center;
145
- justify-content: space-between;
146
- gap: 12px;
147
- }
148
- .ps-outbox-name {
149
- min-width: 0;
150
- overflow: hidden;
151
- color: #fafaf9;
152
- font-size: 14px;
153
- font-weight: 680;
154
- text-overflow: ellipsis;
155
- white-space: nowrap;
156
- }
157
- .ps-outbox-status {
158
- flex: none;
159
- display: inline-flex;
160
- align-items: center;
161
- gap: 5px;
162
- padding: 3px 8px;
163
- border: 1px solid currentColor;
164
- border-radius: 999px;
165
- font-size: 10px;
166
- font-weight: 700;
167
- letter-spacing: .03em;
168
- }
169
- .ps-status-dot { width: 5px; height: 5px; border-radius: 999px; background: currentColor; }
170
- .ps-status-queued { color: #7dd3fc; background: rgba(12, 74, 110, .45); }
171
- .ps-status-executing { color: #fcd34d; background: rgba(120, 53, 15, .42); }
172
- .ps-status-failed { color: #fca5a5; background: rgba(127, 29, 29, .42); }
173
- .ps-outbox-meta {
174
- display: flex;
175
- flex-wrap: wrap;
176
- gap: 6px 13px;
177
- margin-top: 10px;
178
- color: #a8a29e;
179
- font-size: 11px;
180
- }
181
- .ps-outbox-meta strong { color: #d6d3d1; font-weight: 600; }
182
- .ps-outbox-meta-divider {
183
- align-self: center;
184
- width: 3px;
185
- height: 3px;
186
- border-radius: 999px;
187
- background: currentColor;
188
- opacity: .65;
189
- }
190
- .ps-outbox-permanent { color: #fca5a5; }
191
- .ps-outbox-time { cursor: help; font-variant-numeric: tabular-nums; }
192
- .ps-outbox-error {
193
- margin-top: 11px;
194
- padding: 8px 10px;
195
- border: 1px solid rgba(239, 68, 68, .35);
196
- border-radius: 8px;
197
- color: #fecaca;
198
- background: rgba(69, 10, 10, .55);
199
- font-size: 11px;
200
- white-space: pre-wrap;
201
- }
202
- .ps-outbox-details { margin-top: 11px; color: #a8a29e; font-size: 11px; }
203
- .ps-outbox-details summary { cursor: pointer; user-select: none; transition: color 120ms ease; }
204
- .ps-outbox-details summary:hover { color: #e7e5e4; }
205
- .ps-outbox-details pre {
206
- max-height: 150px;
207
- margin: 8px 0 0;
208
- padding: 10px;
209
- overflow: auto;
210
- border: 1px solid #303036;
211
- border-radius: 8px;
212
- color: #d4d4d8;
213
- background: rgba(9, 9, 11, .7);
214
- font: 11px/1.5 ui-monospace, SFMono-Regular, monospace;
215
- }
216
- .ps-outbox-actions { display: flex; gap: 7px; margin-top: 13px; }
217
- .ps-button {
218
- appearance: none;
219
- display: inline-flex;
220
- align-items: center;
221
- gap: 5px;
222
- padding: 5px 10px;
223
- border: 1px solid transparent;
224
- border-radius: 7px;
225
- font: inherit;
226
- font-weight: 600;
227
- cursor: pointer;
228
- transition: background 120ms ease, border-color 120ms ease, opacity 120ms ease;
229
- }
230
- .ps-button svg { width: 13px; height: 13px; }
231
- .ps-button:disabled { cursor: not-allowed; opacity: .4; }
232
- .ps-button-primary { color: #e0f2fe; border-color: #075985; background: rgba(7, 89, 133, .7); }
233
- .ps-button-primary:not(:disabled):hover { background: #0369a1; }
234
- .ps-button-danger { color: #fca5a5; border-color: #7f1d1d; background: rgba(69, 10, 10, .16); }
235
- .ps-button-danger:not(:disabled):hover { background: rgba(127, 29, 29, .7); }
236
- .ps-outbox-action-error { margin-top: 8px; color: #fca5a5; font-size: 12px; }
237
- .ps-outbox-empty {
238
- display: grid;
239
- place-items: center;
240
- min-height: 150px;
241
- padding: 28px 16px;
242
- border: 1px dashed #3f3f46;
243
- border-radius: 14px;
244
- color: #a8a29e;
245
- background: rgba(39, 39, 42, .3);
246
- text-align: center;
247
- }
248
- .ps-outbox-empty-mark {
249
- display: grid;
250
- place-items: center;
251
- width: 34px;
252
- height: 34px;
253
- margin: 0 auto 9px;
254
- border: 1px solid #3f3f46;
255
- border-radius: 999px;
256
- color: #86efac;
257
- background: #1c2920;
258
- }
259
- .ps-outbox-empty-mark svg { width: 17px; height: 17px; }
260
- .ps-outbox-empty strong { display: block; margin-bottom: 2px; color: #e7e5e4; }
261
- html[data-ps-outbox-transition]::view-transition-old(root),
262
- html[data-ps-outbox-transition]::view-transition-new(root) {
263
- animation: none;
264
- }
265
- html[data-ps-outbox-transition]::view-transition-group(*) {
266
- animation-duration: 240ms;
267
- animation-timing-function: cubic-bezier(.22, 1, .36, 1);
268
- }
269
- .ps-theme-light .ps-outbox {
270
- color: #292524;
271
- background:
272
- radial-gradient(circle at 8% 0%, rgba(232, 59, 50, .1), transparent 30%),
273
- linear-gradient(145deg, #fafaf9 0%, #f5f5f4 100%);
274
- }
275
- .ps-theme-light .ps-outbox-mark {
276
- color: #dc2626;
277
- border-color: #e7e5e4;
278
- background: rgba(255, 255, 255, .82);
279
- box-shadow: 0 8px 22px rgba(41, 37, 36, .1);
280
- }
281
- .ps-theme-light .ps-outbox-title,
282
- .ps-theme-light .ps-outbox-name,
283
- .ps-theme-light .ps-outbox-empty strong {
284
- color: #1c1917;
285
- }
286
- .ps-theme-light .ps-outbox-subtitle,
287
- .ps-theme-light .ps-outbox-meta,
288
- .ps-theme-light .ps-outbox-details {
289
- color: #78716c;
290
- }
291
- .ps-theme-light .ps-outbox-meta strong { color: #44403c; }
292
- .ps-theme-light .ps-outbox-count {
293
- color: #57534e;
294
- border-color: #d6d3d1;
295
- background: rgba(255, 255, 255, .8);
296
- }
297
- .ps-theme-light .ps-outbox-card {
298
- border-color: #d6d3d1;
299
- background: linear-gradient(155deg, rgba(255, 255, 255, .98), rgba(245, 245, 244, .98));
300
- box-shadow: 0 14px 34px rgba(41, 37, 36, .1);
301
- }
302
- .ps-theme-light .ps-outbox-connector { background: #d6d3d1; }
303
- .ps-theme-light .ps-outbox-details summary:hover { color: #292524; }
304
- .ps-theme-light .ps-outbox-details pre {
305
- color: #44403c;
306
- border-color: #e7e5e4;
307
- background: rgba(255, 255, 255, .86);
308
- }
309
- .ps-theme-light .ps-outbox-empty {
310
- color: #78716c;
311
- border-color: #d6d3d1;
312
- background: rgba(255, 255, 255, .55);
313
- }
314
- .ps-theme-light .ps-outbox-empty-mark {
315
- color: #15803d;
316
- border-color: #bbf7d0;
317
- background: #f0fdf4;
318
- }
319
- .ps-theme-light .ps-button-primary {
320
- color: #075985;
321
- border-color: #7dd3fc;
322
- background: #e0f2fe;
323
- }
324
- .ps-theme-light .ps-button-primary:not(:disabled):hover { background: #bae6fd; }
325
- .ps-theme-light .ps-button-danger {
326
- color: #b91c1c;
327
- border-color: #fecaca;
328
- background: #fff7f7;
329
- }
330
- .ps-theme-light .ps-button-danger:not(:disabled):hover { background: #fee2e2; }
331
- `;
13
+ import "./styles.css";
332
14
  function PartyStackLogo({ theme }) {
333
15
  const id = useId().replaceAll(":", "");
334
16
  const gradientId = `ps-ball-${id}`;
@@ -349,11 +31,34 @@ function PartyStackLogomark() {
349
31
  width: "100%",
350
32
  }, viewBox: "0 0 512 512", children: [_jsxs("defs", { children: [_jsxs("radialGradient", { cx: "69%", cy: "24%", id: gradientId, r: "82%", children: [_jsx("stop", { offset: "0", stopColor: "#fff" }), _jsx("stop", { offset: ".38", stopColor: "#fff" }), _jsx("stop", { offset: ".62", stopColor: "#f8f7f3" }), _jsx("stop", { offset: ".82", stopColor: "#e1dfda" }), _jsx("stop", { offset: "1", stopColor: "#b7b3ad" })] }), _jsx("filter", { height: "170%", id: shadowId, width: "160%", x: "-30%", y: "-30%", children: _jsx("feDropShadow", { dx: "3", dy: "6", floodColor: "#681611", floodOpacity: ".28", stdDeviation: "5" }) })] }), _jsx("path", { d: "M96 64h184c81 0 136 55 136 136s-55 136-136 136h-56v112H96V64Z", fill: "#e83b32" }), _jsxs("g", { filter: `url(#${shadowId})`, children: [_jsx("circle", { cx: "258", cy: "200", fill: `url(#${gradientId})`, r: "60" }), _jsx("ellipse", { cx: "279", cy: "177", fill: "#fff", opacity: ".92", rx: "8.5", ry: "10.5" })] })] }));
351
33
  }
352
- function PartyStackPluginName({ theme }) {
34
+ export function OntologyDevtoolsPluginName({ theme }) {
353
35
  return (_jsx("div", { style: { height: 30, width: 120 }, children: _jsx(PartyStackLogo, { theme: theme }) }));
354
36
  }
37
+ export function OntologyDevtoolsTrigger({ theme }) {
38
+ const dark = theme === "dark";
39
+ return (_jsx("div", { "aria-label": "Open Ontology devtools", role: "button", style: {
40
+ alignItems: "center",
41
+ background: dark
42
+ ? "linear-gradient(145deg, #2d2d31, #171719)"
43
+ : "linear-gradient(145deg, #fff, #f5f5f4)",
44
+ border: dark ? "1px solid rgba(255,255,255,.16)" : "1px solid rgba(23,23,23,.14)",
45
+ borderRadius: "999px",
46
+ boxShadow: dark
47
+ ? "0 10px 30px rgba(0,0,0,.34), 0 0 0 4px rgba(232,59,50,.11)"
48
+ : "0 10px 28px rgba(28,25,23,.16), 0 0 0 4px rgba(232,59,50,.09)",
49
+ cursor: "pointer",
50
+ display: "flex",
51
+ height: 48,
52
+ justifyContent: "center",
53
+ padding: 8,
54
+ width: 48,
55
+ }, title: "Open Ontology devtools", children: _jsx(PartyStackLogomark, {}) }));
56
+ }
57
+ export function ontologyDevtoolsTrigger(_element, { theme }) {
58
+ return _jsx(OntologyDevtoolsTrigger, { theme: theme });
59
+ }
355
60
  function OutboxPanel({ children, theme }) {
356
- return (_jsx("div", { className: `ps-outbox-root ps-theme-${theme}`, children: _jsxs("div", { className: "ps-outbox", children: [_jsx("style", { children: styles }), children] }) }));
61
+ return (_jsx("div", { className: `ps-outbox-root ps-theme-${theme}`, children: _jsx("div", { className: "ps-outbox", children: children }) }));
357
62
  }
358
63
  function statusClass(entry) {
359
64
  return `ps-outbox-status ps-status-${entry.status}`;
@@ -476,45 +181,496 @@ function OutboxRows({ outbox, theme }) {
476
181
  const transitionScope = `ps-outbox-${useId().replaceAll(":", "")}`;
477
182
  const { data } = useLiveQuery((query) => query.from({ entry: outbox.collection }).orderBy(({ entry }) => entry.sequence, "asc"), [outbox]);
478
183
  const visibleData = useTransitionedEntries(data);
479
- return (_jsxs(OutboxPanel, { theme: theme, children: [_jsx("header", { className: "ps-outbox-header", children: _jsxs("div", { className: "ps-outbox-heading", children: [_jsx("div", { className: "ps-outbox-mark", children: _jsx(InboxStackIcon, { "aria-hidden": "true" }) }), _jsxs("div", { children: [_jsxs("div", { className: "ps-outbox-title-row", children: [_jsx("h2", { className: "ps-outbox-title", children: "Outbox" }), _jsx("span", { className: "ps-outbox-count", children: visibleData.length })] }), _jsx("p", { className: "ps-outbox-subtitle", children: "Actions waiting to sync" })] })] }) }), visibleData.length === 0 ? (_jsx("div", { className: "ps-outbox-empty", style: {
480
- viewTransitionName: `${transitionScope}-empty`,
481
- }, children: _jsxs("div", { children: [_jsx("span", { className: "ps-outbox-empty-mark", children: _jsx(CheckIcon, { "aria-hidden": "true" }) }), _jsx("strong", { children: "Queue is clear" }), "Actions will appear here while they wait to sync."] }) })) : (_jsx("div", { className: "ps-outbox-track", children: visibleData.map((entry, index) => (_jsxs("div", { className: "ps-outbox-step", style: {
482
- viewTransitionName: `${transitionScope}-entry-${entry.id.replaceAll(/[^a-zA-Z0-9_-]/g, "-")}`,
483
- }, children: [index > 0 ? _jsx("span", { "aria-hidden": "true", className: "ps-outbox-connector" }) : null, _jsx("div", { className: "ps-outbox-node", children: _jsx(OutboxEntryCard, { entry: entry, now: now, outbox: outbox }) })] }, entry.id))) }))] }));
184
+ return (_jsx(OutboxPanel, { theme: theme, children: visibleData.length === 0 ? (_jsx("div", { className: "ps-outbox-empty", style: {
185
+ viewTransitionName: `${transitionScope}-empty`,
186
+ }, children: _jsxs("div", { children: [_jsx("span", { className: "ps-outbox-empty-mark", children: _jsx(CheckIcon, { "aria-hidden": "true" }) }), _jsx("strong", { children: "Queue is clear" }), "Actions will appear here while they wait to sync."] }) })) : (_jsx("div", { className: "ps-outbox-track", children: visibleData.map((entry, index) => (_jsxs("div", { className: "ps-outbox-step", style: {
187
+ viewTransitionName: `${transitionScope}-entry-${entry.id.replaceAll(/[^a-zA-Z0-9_-]/g, "-")}`,
188
+ }, children: [index > 0 ? _jsx("span", { "aria-hidden": "true", className: "ps-outbox-connector" }) : null, _jsx("div", { className: "ps-outbox-node", children: _jsx(OutboxEntryCard, { entry: entry, now: now, outbox: outbox }) })] }, entry.id))) })) }));
189
+ }
190
+ export function getOutboxActivity(entries) {
191
+ if (entries.length === 0)
192
+ return "idle";
193
+ const head = entries.reduce((first, entry) => entry.sequence < first.sequence ? entry : first);
194
+ if (head.status === "failed")
195
+ return "paused";
196
+ return "draining";
197
+ }
198
+ function outboxActivityLabel(activity) {
199
+ switch (activity) {
200
+ case "idle":
201
+ return "Idle";
202
+ case "draining":
203
+ return "Draining";
204
+ case "paused":
205
+ return "Paused by failure";
206
+ }
484
207
  }
485
- function OntologyPanel({ ontology, theme }) {
486
- return _jsx(OutboxRows, { outbox: ontology.outbox, theme: theme });
208
+ function outboxBadgeClass(activity) {
209
+ switch (activity) {
210
+ case "idle":
211
+ return "ps:bg-zinc-500/15 ps:text-zinc-400";
212
+ case "draining":
213
+ return "ps:bg-sky-500/20 ps:text-sky-300";
214
+ case "paused":
215
+ return "ps:bg-red-500/20 ps:text-red-300";
216
+ }
487
217
  }
488
- function OntologyTrigger({ theme }) {
489
- const dark = theme === "dark";
490
- return (_jsx("div", { "aria-label": "Open Ontology devtools", role: "button", style: {
491
- alignItems: "center",
492
- background: dark
493
- ? "linear-gradient(145deg, #2d2d31, #171719)"
494
- : "linear-gradient(145deg, #fff, #f5f5f4)",
495
- border: dark ? "1px solid rgba(255,255,255,.16)" : "1px solid rgba(23,23,23,.14)",
496
- borderRadius: "999px",
497
- boxShadow: dark
498
- ? "0 10px 30px rgba(0,0,0,.34), 0 0 0 4px rgba(232,59,50,.11)"
499
- : "0 10px 28px rgba(28,25,23,.16), 0 0 0 4px rgba(232,59,50,.09)",
500
- cursor: "pointer",
501
- display: "flex",
502
- height: 48,
503
- justifyContent: "center",
504
- padding: 8,
505
- width: 48,
506
- }, title: "Open Ontology devtools", children: _jsx(PartyStackLogomark, {}) }));
218
+ const ontologyViews = [
219
+ { id: "outbox", label: "Outbox", icon: InboxStackIcon },
220
+ { id: "schema", label: "Schema", icon: ShareIcon },
221
+ { id: "objects", label: "Objects", icon: CircleStackIcon },
222
+ ];
223
+ function formatObjectValue(value) {
224
+ if (value === undefined || value === null)
225
+ return "—";
226
+ if (typeof value === "string")
227
+ return value;
228
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
229
+ return String(value);
230
+ }
231
+ try {
232
+ return JSON.stringify(value);
233
+ }
234
+ catch {
235
+ return "[Unserializable value]";
236
+ }
237
+ }
238
+ const CONTINENTS = [
239
+ "15,25 45,20 75,22 110,30 120,35 105,52 100,62 85,68 75,72 60,65 50,42 30,35",
240
+ "100,82 118,80 138,90 140,100 130,118 118,132 108,138 100,122 95,100",
241
+ "175,38 185,30 198,28 208,32 205,42 198,48 188,48 178,44",
242
+ "170,58 185,52 205,56 218,68 215,88 208,108 198,125 185,128 172,112 168,88 165,70",
243
+ "212,42 228,32 255,22 282,18 310,22 325,30 330,42 318,55 300,58 282,65 265,78 248,72 232,58 218,50",
244
+ "292,112 312,108 332,114 330,126 318,134 298,128 292,118",
245
+ ];
246
+ function resolvedType(ir, type) {
247
+ if (type.kind !== "ref")
248
+ return type;
249
+ const referenced = ir.types.find((candidate) => candidate.name === type.value.name)?.type;
250
+ return referenced ? resolvedType(ir, referenced) : type;
251
+ }
252
+ export function typeDisplayName(ir, type) {
253
+ switch (type.kind) {
254
+ case "ref":
255
+ return type.value.name;
256
+ case "optional":
257
+ return `${typeDisplayName(ir, type.value.type)} (optional)`;
258
+ case "list":
259
+ return `List of ${typeDisplayName(ir, type.value.elementType)}`;
260
+ case "map":
261
+ return `Map of ${typeDisplayName(ir, type.value.valueType)}`;
262
+ case "objectReference":
263
+ return `${type.value.objectType} reference`;
264
+ case "string":
265
+ return "String";
266
+ case "boolean":
267
+ return "Boolean";
268
+ case "integer":
269
+ return "Integer";
270
+ case "float":
271
+ return "Float";
272
+ case "double":
273
+ return "Double";
274
+ case "date":
275
+ return "Date";
276
+ case "timestamp":
277
+ return "Timestamp";
278
+ case "geopoint":
279
+ return "Geopoint";
280
+ case "struct":
281
+ return "Struct";
282
+ case "union":
283
+ return "Union";
284
+ case "result":
285
+ return "Result";
286
+ case "attachment":
287
+ return "Attachment";
288
+ case "unknown":
289
+ return "Unknown";
290
+ }
291
+ }
292
+ function typeIcon(type) {
293
+ switch (type.kind) {
294
+ case "ref":
295
+ return LinkIcon;
296
+ case "optional":
297
+ return typeIcon(type.value.type);
298
+ case "list":
299
+ return ListBulletIcon;
300
+ case "map":
301
+ return Squares2X2Icon;
302
+ case "objectReference":
303
+ return LinkIcon;
304
+ case "string":
305
+ return CodeBracketSquareIcon;
306
+ case "boolean":
307
+ return CheckCircleIcon;
308
+ case "integer":
309
+ case "float":
310
+ case "double":
311
+ return HashtagIcon;
312
+ case "date":
313
+ return CalendarDaysIcon;
314
+ case "timestamp":
315
+ return ClockIcon;
316
+ case "geopoint":
317
+ return MapPinIcon;
318
+ case "attachment":
319
+ return PaperClipIcon;
320
+ case "struct":
321
+ case "union":
322
+ case "result":
323
+ return MapIcon;
324
+ case "unknown":
325
+ return QuestionMarkCircleIcon;
326
+ }
327
+ }
328
+ function DevtoolsTooltip({ children, label }) {
329
+ return (_jsxs(Tooltip.Root, { children: [_jsx(Tooltip.Trigger, { render: _jsx("span", { className: "ps:inline-flex ps:items-center" }), children: children }), _jsx(Tooltip.Portal, { children: _jsx(Tooltip.Positioner, { className: "ps:z-[100001]", sideOffset: 6, children: _jsx(Tooltip.Popup, { className: "ps:max-w-64 ps:rounded-md ps:border ps:border-zinc-700 ps:bg-zinc-900 ps:px-2.5 ps:py-1.5 ps:text-[11px] ps:text-zinc-200 ps:shadow-xl", children: label }) }) })] }));
330
+ }
331
+ function TypeIndicator({ ir, type }) {
332
+ const Icon = typeIcon(type);
333
+ return (_jsx(DevtoolsTooltip, { label: typeDisplayName(ir, type), children: _jsx(Icon, { "aria-hidden": true, className: "ps:size-3.5 ps:text-zinc-500" }) }));
334
+ }
335
+ function isGeopoint(value) {
336
+ return (typeof value === "object" &&
337
+ value !== null &&
338
+ "lat" in value &&
339
+ typeof value.lat === "number" &&
340
+ "lon" in value &&
341
+ typeof value.lon === "number");
342
+ }
343
+ function GeopointPreview({ value }) {
344
+ const cx = value.lon + 180;
345
+ const cy = 90 - value.lat;
346
+ const label = `${value.lat.toFixed(4)}, ${value.lon.toFixed(4)}`;
347
+ return (_jsx(DevtoolsTooltip, { label: _jsxs("svg", { "aria-label": `Map preview for ${label}`, className: "ps:block ps:rounded ps:border ps:border-zinc-700", height: "72", role: "img", viewBox: "0 0 360 180", width: "144", children: [_jsx("rect", { className: "ps:fill-sky-950", height: "180", width: "360" }), CONTINENTS.map((points) => (_jsx("polygon", { className: "ps:fill-zinc-700 ps:stroke-zinc-600", points: points, strokeWidth: "0.5" }, points))), _jsx("circle", { className: "ps:fill-rose-400", cx: cx, cy: cy, r: "5" })] }), children: _jsx("a", { className: "ps:text-sky-400 ps:underline ps:decoration-dotted ps:underline-offset-4", href: `https://www.google.com/maps?q=${value.lat},${value.lon}`, rel: "noopener noreferrer", target: "_blank", children: label }) }));
348
+ }
349
+ export function timestampPreview(value) {
350
+ if (value === undefined || value === null)
351
+ return undefined;
352
+ const exact = formatObjectValue(value);
353
+ let epochMilliseconds;
354
+ if (value instanceof Date) {
355
+ epochMilliseconds = value.getTime();
356
+ }
357
+ else if (typeof value === "object" &&
358
+ "epochMilliseconds" in value &&
359
+ (typeof value.epochMilliseconds === "number" ||
360
+ typeof value.epochMilliseconds === "bigint")) {
361
+ epochMilliseconds = Number(value.epochMilliseconds);
362
+ }
363
+ else if (typeof value === "string") {
364
+ const parsed = Date.parse(value);
365
+ if (!Number.isNaN(parsed))
366
+ epochMilliseconds = parsed;
367
+ }
368
+ if (epochMilliseconds === undefined || !Number.isFinite(epochMilliseconds)) {
369
+ return { display: exact, exact };
370
+ }
371
+ return {
372
+ display: new Intl.DateTimeFormat(undefined, {
373
+ dateStyle: "medium",
374
+ timeStyle: "medium",
375
+ }).format(new Date(epochMilliseconds)),
376
+ exact,
377
+ };
507
378
  }
508
- export function OntologyDevtools({ ontology, }) {
509
- return (_jsx(TanStackDevtools, { plugins: [
510
- {
511
- id: "party-stack-ontology",
512
- name: (_element, { theme }) => _jsx(PartyStackPluginName, { theme: theme }),
513
- render: (_element, { theme }) => (_jsx(OntologyPanel, { ontology: ontology, theme: theme })),
514
- defaultOpen: true,
515
- },
516
- ], config: {
517
- customTrigger: (_element, { theme }) => _jsx(OntologyTrigger, { theme: theme }),
518
- } }));
379
+ function isAttachment(value) {
380
+ return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string";
381
+ }
382
+ function AttachmentPreview({ attachment, ontology, }) {
383
+ const [preview, setPreview] = useState();
384
+ useEffect(() => {
385
+ let cancelled = false;
386
+ let objectUrl;
387
+ void ontology.attachments
388
+ .metadata(attachment)
389
+ .then(async (metadata) => {
390
+ if (metadata.type.startsWith("image/")) {
391
+ objectUrl = URL.createObjectURL(await ontology.attachments.blob(attachment));
392
+ }
393
+ if (!cancelled)
394
+ setPreview({ metadata, src: objectUrl });
395
+ })
396
+ .catch(() => {
397
+ if (!cancelled)
398
+ setPreview(undefined);
399
+ });
400
+ return () => {
401
+ cancelled = true;
402
+ if (objectUrl)
403
+ URL.revokeObjectURL(objectUrl);
404
+ };
405
+ }, [attachment, ontology]);
406
+ const label = preview?.metadata.name ?? attachment.name ?? attachment.id;
407
+ if (!preview?.src) {
408
+ return (_jsx(DevtoolsTooltip, { label: label, children: _jsxs("span", { className: "ps:inline-flex ps:max-w-40 ps:items-center ps:gap-1.5 ps:truncate", children: [_jsx(PaperClipIcon, { "aria-hidden": true, className: "ps:size-3.5 ps:flex-none ps:text-zinc-500" }), _jsx("span", { className: "ps:truncate", children: label })] }) }));
409
+ }
410
+ return (_jsxs(Dialog.Root, { children: [_jsx(Dialog.Trigger, { "aria-label": `Open ${label}`, className: "ps:block ps:overflow-hidden ps:rounded-md ps:border ps:border-zinc-700 ps:bg-transparent ps:p-0 ps:hover:border-rose-400", children: _jsx("img", { alt: label, className: "ps:size-10 ps:object-cover", src: preview.src }) }), _jsxs(Dialog.Portal, { children: [_jsx(Dialog.Backdrop, { className: "ps:fixed ps:inset-0 ps:z-[100002] ps:bg-black/75 ps:backdrop-blur-sm" }), _jsx(Dialog.Viewport, { className: "ps:fixed ps:inset-0 ps:z-[100003] ps:grid ps:place-items-center ps:p-8", children: _jsxs(Dialog.Popup, { className: "ps:relative ps:max-h-full ps:max-w-4xl ps:rounded-xl ps:border ps:border-zinc-700 ps:bg-zinc-950 ps:p-3 ps:shadow-2xl", children: [_jsx(Dialog.Title, { className: "ps:sr-only", children: label }), _jsx("img", { alt: label, className: "ps:max-h-[80vh] ps:max-w-[80vw] ps:rounded-lg ps:object-contain", src: preview.src }), _jsx(Dialog.Close, { className: "ps:absolute ps:top-4 ps:right-4 ps:rounded-md ps:border ps:border-zinc-600 ps:bg-zinc-950/90 ps:px-2.5 ps:py-1.5 ps:text-xs ps:text-white", children: "Close" })] }) })] })] }));
411
+ }
412
+ function PropertyValuePreview({ ir, ontology, type, value, }) {
413
+ const resolved = resolvedType(ir, type);
414
+ if (resolved.kind === "optional") {
415
+ return (_jsx(PropertyValuePreview, { ir: ir, ontology: ontology, type: resolved.value.type, value: value }));
416
+ }
417
+ if (resolved.kind === "geopoint" && isGeopoint(value)) {
418
+ return _jsx(GeopointPreview, { value: value });
419
+ }
420
+ if (resolved.kind === "timestamp") {
421
+ const preview = timestampPreview(value);
422
+ if (preview) {
423
+ return (_jsx(DevtoolsTooltip, { label: preview.exact, children: _jsx("span", { children: preview.display }) }));
424
+ }
425
+ }
426
+ if (resolved.kind === "attachment" && isAttachment(value)) {
427
+ return _jsx(AttachmentPreview, { attachment: value, ontology: ontology });
428
+ }
429
+ if (resolved.kind === "list" &&
430
+ resolvedType(ir, resolved.value.elementType).kind === "attachment" &&
431
+ Array.isArray(value)) {
432
+ const attachments = value.filter(isAttachment);
433
+ if (attachments.length > 0) {
434
+ return (_jsxs("div", { className: "ps:flex ps:items-center ps:gap-1.5", children: [attachments.slice(0, 3).map((attachment) => (_jsx(AttachmentPreview, { attachment: attachment, ontology: ontology }, attachment.id))), attachments.length > 3 ? (_jsxs("span", { className: "ps:text-zinc-500", children: ["+", attachments.length - 3] })) : null] }));
435
+ }
436
+ }
437
+ const formatted = formatObjectValue(value);
438
+ return (_jsx("span", { className: "ps:block ps:max-w-72 ps:truncate", title: formatted, children: formatted }));
439
+ }
440
+ function defaultColumnSize(ir, type) {
441
+ switch (resolvedType(ir, type).kind) {
442
+ case "boolean":
443
+ return 110;
444
+ case "integer":
445
+ case "float":
446
+ case "double":
447
+ return 130;
448
+ case "date":
449
+ case "timestamp":
450
+ return 190;
451
+ case "geopoint":
452
+ return 175;
453
+ case "attachment":
454
+ case "list":
455
+ return 150;
456
+ case "objectReference":
457
+ return 200;
458
+ case "string":
459
+ return 220;
460
+ default:
461
+ return 180;
462
+ }
463
+ }
464
+ export function moveColumn(columnOrder, sourceId, targetId) {
465
+ const sourceIndex = columnOrder.indexOf(sourceId);
466
+ const targetIndex = columnOrder.indexOf(targetId);
467
+ if (sourceIndex === -1 || targetIndex === -1 || sourceIndex === targetIndex) {
468
+ return columnOrder;
469
+ }
470
+ const next = [...columnOrder];
471
+ next.splice(sourceIndex, 1);
472
+ next.splice(targetIndex, 0, sourceId);
473
+ return next;
474
+ }
475
+ const objectColumnHelper = createColumnHelper();
476
+ function ObjectTable({ collection, objectType, ontology, }) {
477
+ const [sorting, setSorting] = useState([]);
478
+ const [columnSizing, setColumnSizing] = useState({});
479
+ const [columnVisibility, setColumnVisibility] = useState({});
480
+ const [columnOrder, setColumnOrder] = useState(() => objectType.properties.map((property) => property.name));
481
+ const [draggedColumnId, setDraggedColumnId] = useState();
482
+ const [dragOverColumnId, setDragOverColumnId] = useState();
483
+ const selectedSort = sorting[0];
484
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage, } = useLiveInfiniteQuery((query) => {
485
+ const from = query.from({ row: collection });
486
+ const primaryKey = objectType.primaryKey;
487
+ if (selectedSort && selectedSort.id !== primaryKey) {
488
+ return from
489
+ .orderBy(({ row }) => row[selectedSort.id], selectedSort.desc ? "desc" : "asc")
490
+ .orderBy(({ row }) => row[primaryKey], "asc")
491
+ .select(({ row }) => row);
492
+ }
493
+ return from
494
+ .orderBy(({ row }) => row[primaryKey], selectedSort?.desc ? "desc" : "asc")
495
+ .select(({ row }) => row);
496
+ }, { pageSize: 50 }, [collection.id, objectType.primaryKey, selectedSort?.id, selectedSort?.desc]);
497
+ const rows = data;
498
+ const columns = useMemo(() => objectType.properties.map((property) => objectColumnHelper.accessor((row) => row[property.name], {
499
+ id: property.name,
500
+ maxSize: 520,
501
+ minSize: 90,
502
+ size: property.name === objectType.primaryKey
503
+ ? 220
504
+ : defaultColumnSize(ontology.ir, property.type),
505
+ header: () => (_jsxs("span", { className: "ps:inline-flex ps:items-center ps:gap-1.5", children: [_jsx(TypeIndicator, { ir: ontology.ir, type: property.type }), _jsx("span", { children: property.displayName }), property.name === objectType.primaryKey ? (_jsx("span", { className: "ps:ml-1.5 ps:text-[9px] ps:font-bold ps:tracking-wide ps:text-rose-400", children: "PK" })) : null] })),
506
+ cell: (info) => (_jsx(PropertyValuePreview, { ir: ontology.ir, ontology: ontology, type: property.type, value: info.getValue() })),
507
+ })), [objectType, ontology]);
508
+ const table = useReactTable({
509
+ columnResizeMode: "onEnd",
510
+ columns,
511
+ data: rows,
512
+ enableColumnResizing: true,
513
+ getCoreRowModel: getCoreRowModel(),
514
+ getRowId: (row, index) => `${formatObjectValue(row[objectType.primaryKey])}-${index}`,
515
+ manualSorting: true,
516
+ onColumnOrderChange: setColumnOrder,
517
+ onColumnSizingChange: setColumnSizing,
518
+ onColumnVisibilityChange: setColumnVisibility,
519
+ onSortingChange: setSorting,
520
+ state: { columnOrder, columnSizing, columnVisibility, sorting },
521
+ });
522
+ const resizingColumnId = table.getState().columnSizingInfo.isResizingColumn;
523
+ const resizingColumn = typeof resizingColumnId === "string" ? table.getColumn(resizingColumnId) : undefined;
524
+ const allColumns = table.getAllLeafColumns();
525
+ return (_jsxs("div", { className: "ps:flex ps:min-h-0 ps:min-w-0 ps:flex-1 ps:flex-col ps:overflow-hidden", children: [_jsxs("div", { className: "ps:flex ps:flex-none ps:items-center ps:border-b ps:border-zinc-500/20 ps:px-4 ps:py-2", children: [_jsx("h2", { className: "ps:m-0 ps:text-lg ps:font-semibold", children: objectType.pluralDisplayName }), _jsxs("div", { className: "ps:ml-auto ps:flex ps:items-center ps:gap-1.5", children: [_jsxs("button", { className: "ps:inline-flex ps:items-center ps:gap-1.5 ps:rounded-md ps:border-0 ps:bg-transparent ps:px-2 ps:py-1.5 ps:text-xs ps:text-zinc-400 ps:hover:bg-zinc-500/10 ps:hover:text-current ps:disabled:opacity-40", disabled: Object.keys(columnSizing).length === 0, title: "Reset column widths", type: "button", onClick: () => {
526
+ table.resetColumnSizing();
527
+ }, children: [_jsx(ArrowPathIcon, { "aria-hidden": true, className: "ps:size-3.5" }), "Reset widths"] }), _jsxs(Menu.Root, { children: [_jsxs(Menu.Trigger, { className: "ps:inline-flex ps:items-center ps:gap-1.5 ps:rounded-md ps:border ps:border-zinc-500/30 ps:bg-zinc-500/10 ps:px-2.5 ps:py-1.5 ps:text-xs ps:text-inherit ps:hover:border-zinc-500/60", children: [_jsx(ViewColumnsIcon, { "aria-hidden": true, className: "ps:size-3.5" }), "Columns"] }), _jsx(Menu.Portal, { children: _jsx(Menu.Positioner, { align: "end", className: "ps:z-[100001]", sideOffset: 6, children: _jsx(Menu.Popup, { className: "ps:min-w-48 ps:rounded-lg ps:border ps:border-zinc-700 ps:bg-zinc-900 ps:p-1.5 ps:text-xs ps:text-zinc-200 ps:shadow-2xl", children: _jsxs(Menu.Group, { children: [_jsx(Menu.GroupLabel, { className: "ps:px-2 ps:py-1.5 ps:text-[10px] ps:font-bold ps:tracking-wider ps:text-zinc-500 ps:uppercase", children: "Columns" }), allColumns.map((column, columnIndex) => (_jsxs(Menu.CheckboxItem, { "aria-label": objectType.properties.find((property) => property.name === column.id)?.displayName ?? column.id, checked: column.getIsVisible(), className: dragOverColumnId === column.id &&
528
+ draggedColumnId !== column.id
529
+ ? "ps:flex ps:cursor-default ps:items-center ps:gap-2 ps:rounded-md ps:bg-rose-500/15 ps:px-2 ps:py-1.5 ps:outline-none"
530
+ : "ps:flex ps:cursor-default ps:items-center ps:gap-2 ps:rounded-md ps:px-2 ps:py-1.5 ps:outline-none ps:data-[highlighted]:bg-zinc-700/70", draggable: true, label: objectType.properties.find((property) => property.name === column.id)?.displayName ?? column.id, onCheckedChange: (checked) => {
531
+ column.toggleVisibility(checked);
532
+ }, onDragEnd: () => {
533
+ setDraggedColumnId(undefined);
534
+ setDragOverColumnId(undefined);
535
+ }, onDragOver: (event) => {
536
+ event.preventDefault();
537
+ event.dataTransfer.dropEffect = "move";
538
+ setDragOverColumnId(column.id);
539
+ }, onDragStart: (event) => {
540
+ event.dataTransfer.effectAllowed = "move";
541
+ event.dataTransfer.setData("text/plain", column.id);
542
+ setDraggedColumnId(column.id);
543
+ }, onDrop: (event) => {
544
+ event.preventDefault();
545
+ if (draggedColumnId &&
546
+ draggedColumnId !== column.id) {
547
+ setColumnOrder((current) => moveColumn(current, draggedColumnId, column.id));
548
+ }
549
+ setDraggedColumnId(undefined);
550
+ setDragOverColumnId(undefined);
551
+ }, onKeyDown: (event) => {
552
+ if (!event.altKey ||
553
+ (event.key !== "ArrowUp" &&
554
+ event.key !== "ArrowDown")) {
555
+ return;
556
+ }
557
+ event.preventDefault();
558
+ const targetIndex = event.key === "ArrowUp"
559
+ ? columnIndex - 1
560
+ : columnIndex + 1;
561
+ const target = allColumns[targetIndex];
562
+ if (target) {
563
+ setColumnOrder((current) => moveColumn(current, column.id, target.id));
564
+ }
565
+ }, children: [_jsx("span", { className: "ps:cursor-grab ps:text-zinc-500 ps:active:cursor-grabbing", title: "Drag to reorder", onClick: (event) => {
566
+ event.stopPropagation();
567
+ }, children: _jsx(Bars2Icon, { "aria-hidden": true, className: "ps:size-3.5" }) }), _jsx("span", { className: "ps:grid ps:size-4 ps:place-items-center ps:rounded ps:border ps:border-zinc-600", children: column.getIsVisible() ? (_jsx(CheckIcon, { "aria-hidden": true, className: "ps:size-3 ps:text-rose-400" })) : null }), _jsx("span", { className: "ps:truncate", children: objectType.properties.find((property) => property.name === column.id)?.displayName ?? column.id })] }, column.id))), _jsx("p", { className: "ps:m-0 ps:px-2 ps:pt-2 ps:pb-1 ps:text-[10px] ps:text-zinc-500", children: "Drag to reorder \u00B7 Alt+\u2191/\u2193 with keyboard" })] }) }) }) })] })] })] }), rows.length === 0 ? (_jsx("div", { className: "ps:m-4 ps:grid ps:min-h-40 ps:place-items-center ps:rounded-xl ps:border ps:border-dashed ps:border-zinc-500/40 ps:text-sm ps:text-zinc-500", children: "No objects loaded" })) : (_jsx("div", { className: "ps-object-table-scroll ps:min-h-0 ps:flex-1 ps:overflow-auto", children: _jsxs("div", { className: "ps:relative ps:min-h-full", style: { minWidth: "100%", width: table.getTotalSize() }, children: [_jsxs("table", { className: "ps:w-full ps:table-fixed ps:border-separate ps:border-spacing-0 ps:text-left ps:text-xs", children: [_jsx("colgroup", { children: table.getVisibleLeafColumns().map((column) => (_jsx("col", { style: { width: column.getSize() } }, column.id))) }), _jsx("thead", { className: "ps-object-table-header", children: table.getHeaderGroups().map((headerGroup) => (_jsx("tr", { children: headerGroup.headers.map((header) => {
568
+ const direction = header.column.getIsSorted();
569
+ return (_jsxs("th", { className: "ps:relative ps:border-b ps:border-zinc-500/30 ps:bg-zinc-950/95 ps:px-4 ps:py-3 ps:font-semibold ps:whitespace-nowrap ps:backdrop-blur ps:in-[.ps-theme-light]:bg-stone-50/95", children: [header.isPlaceholder ? null : (_jsxs("button", { className: "ps:flex ps:w-full ps:items-center ps:gap-2 ps:border-0 ps:bg-transparent ps:p-0 ps:text-left ps:text-inherit ps:font-inherit ps:hover:text-rose-400", title: `Sort by ${header.column.id}`, type: "button", onClick: header.column.getToggleSortingHandler(), children: [flexRender(header.column.columnDef.header, header.getContext()), direction === "asc" ? (_jsx(ChevronUpIcon, { "aria-hidden": true, className: "ps:size-3.5 ps:flex-none ps:text-rose-400" })) : direction === "desc" ? (_jsx(ChevronDownIcon, { "aria-hidden": true, className: "ps:size-3.5 ps:flex-none ps:text-rose-400" })) : (_jsx(ChevronUpDownIcon, { "aria-hidden": true, className: "ps:size-3.5 ps:flex-none ps:text-zinc-600" })), _jsx("span", { className: "ps:sr-only", children: direction === "asc"
570
+ ? "Sorted ascending"
571
+ : direction === "desc"
572
+ ? "Sorted descending"
573
+ : "Not sorted" })] })), header.column.getCanResize() ? (_jsx("button", { "aria-label": `Resize ${header.column.id} column`, "aria-orientation": "vertical", "aria-valuemax": header.column.columnDef.maxSize, "aria-valuemin": header.column.columnDef.minSize, "aria-valuenow": header.column.getSize(), className: header.column.getIsResizing()
574
+ ? "ps:absolute ps:top-0 ps:right-0 ps:z-20 ps:h-full ps:w-1 ps:cursor-col-resize ps:touch-none ps:border-0 ps:bg-transparent ps:p-0"
575
+ : resizingColumn
576
+ ? "ps:pointer-events-none ps:invisible ps:absolute ps:top-0 ps:right-0 ps:h-full ps:w-1 ps:border-0 ps:bg-transparent ps:p-0"
577
+ : "ps:absolute ps:top-0 ps:right-0 ps:h-full ps:w-1 ps:cursor-col-resize ps:touch-none ps:border-0 ps:bg-transparent ps:p-0 ps:hover:bg-rose-400/70 ps:focus-visible:bg-rose-400/70 ps:focus:outline-none", disabled: resizingColumn !== undefined &&
578
+ !header.column.getIsResizing(), role: "separator", tabIndex: 0, type: "button", onDoubleClick: () => {
579
+ header.column.resetSize();
580
+ }, onKeyDown: (event) => {
581
+ if (event.key !== "ArrowLeft" &&
582
+ event.key !== "ArrowRight") {
583
+ return;
584
+ }
585
+ event.preventDefault();
586
+ const delta = event.key === "ArrowRight" ? 10 : -10;
587
+ const min = header.column.columnDef.minSize ?? 20;
588
+ const max = header.column.columnDef.maxSize ??
589
+ Number.MAX_SAFE_INTEGER;
590
+ setColumnSizing((current) => ({
591
+ ...current,
592
+ [header.column.id]: Math.min(max, Math.max(min, header.column.getSize() +
593
+ delta)),
594
+ }));
595
+ }, onMouseDown: header.getResizeHandler(), onTouchStart: header.getResizeHandler() })) : null] }, header.id));
596
+ }) }, headerGroup.id))) }), _jsx("tbody", { children: table.getRowModel().rows.map((row) => (_jsx("tr", { className: "ps:hover:bg-zinc-500/8", children: row.getVisibleCells().map((cell) => (_jsx("td", { className: "ps:max-w-80 ps:border-b ps:border-zinc-500/15 ps:px-4 ps:py-3 ps:text-[11px] ps:whitespace-nowrap", children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id))) })] }), hasNextPage ? (_jsx("div", { className: "ps:sticky ps:left-0 ps:flex ps:w-full ps:justify-center ps:border-t ps:border-zinc-500/20 ps:bg-zinc-950/95 ps:p-3 ps:in-[.ps-theme-light]:bg-stone-50/95", children: _jsx("button", { className: "ps:rounded-md ps:border ps:border-zinc-600 ps:bg-zinc-900 ps:px-3 ps:py-1.5 ps:text-xs ps:text-zinc-200 ps:hover:border-rose-400 ps:hover:text-rose-300 ps:disabled:opacity-50", disabled: isFetchingNextPage, type: "button", onClick: fetchNextPage, children: isFetchingNextPage ? "Loading…" : "Load 50 more" }) })) : null, resizingColumn ? (_jsx("div", { className: "ps:pointer-events-none ps:absolute ps:inset-y-0 ps:z-50 ps:w-0.5 ps:bg-rose-400 ps:shadow-[0_0_0_1px_rgba(251,113,133,0.18)] ps:will-change-transform", style: {
597
+ left: resizingColumn.getStart() + resizingColumn.getSize(),
598
+ transform: `translateX(${table.getState().columnSizingInfo.deltaOffset ?? 0}px)`,
599
+ } })) : null] }) }))] }));
600
+ }
601
+ function ObjectsView({ ontology }) {
602
+ const objectTypes = ontology.ir.objectTypes;
603
+ const [selectedName, setSelectedName] = useState(() => objectTypes[0]?.name);
604
+ const selected = objectTypes.find((objectType) => objectType.name === selectedName) ?? objectTypes[0];
605
+ const collection = selected ? ontology.objects[selected.name] : undefined;
606
+ if (!selected || !collection) {
607
+ return (_jsx("div", { className: "ps:grid ps:h-full ps:place-items-center ps:text-sm ps:text-zinc-500", children: "This ontology has no object types." }));
608
+ }
609
+ return (_jsxs("div", { className: "ps:flex ps:h-full ps:min-h-0 ps:w-full ps:min-w-0 ps:overflow-hidden", children: [_jsxs("aside", { className: "ps:w-44 ps:flex-none ps:overflow-y-auto ps:border-r ps:border-zinc-500/20 ps:p-3", children: [_jsx("p", { className: "ps:mt-0 ps:mb-2 ps:px-2 ps:text-[10px] ps:font-bold ps:tracking-[0.14em] ps:text-zinc-500 ps:uppercase", children: "Object types" }), _jsx("div", { className: "ps:flex ps:flex-col ps:gap-1", children: objectTypes.map((objectType) => (_jsx("button", { className: objectType.name === selected.name
610
+ ? "ps:rounded-lg ps:border-0 ps:bg-rose-500/15 ps:px-2.5 ps:py-2 ps:text-left ps:text-xs ps:font-semibold ps:text-rose-400"
611
+ : "ps:rounded-lg ps:border-0 ps:bg-transparent ps:px-2.5 ps:py-2 ps:text-left ps:text-xs ps:text-inherit ps:hover:bg-zinc-500/10", type: "button", onClick: () => {
612
+ setSelectedName(objectType.name);
613
+ }, children: objectType.pluralDisplayName }, objectType.name))) })] }), _jsx(ObjectTable, { collection: collection, objectType: selected, ontology: ontology }, selected.name)] }));
614
+ }
615
+ export function layoutSchema(ir) {
616
+ const cardWidth = 230;
617
+ const cardHeight = 176;
618
+ const gapX = 110;
619
+ const gapY = 90;
620
+ const columns = Math.min(3, Math.max(1, Math.ceil(Math.sqrt(ir.objectTypes.length))));
621
+ const rows = Math.max(1, Math.ceil(ir.objectTypes.length / columns));
622
+ const nodes = ir.objectTypes.map((objectType, index) => ({
623
+ objectType,
624
+ x: 40 + (index % columns) * (cardWidth + gapX),
625
+ y: 40 + Math.floor(index / columns) * (cardHeight + gapY),
626
+ }));
627
+ return {
628
+ nodes,
629
+ width: 80 + columns * cardWidth + (columns - 1) * gapX,
630
+ height: 80 + rows * cardHeight + (rows - 1) * gapY,
631
+ };
632
+ }
633
+ function SchemaView({ ir }) {
634
+ const layout = layoutSchema(ir);
635
+ const nodeByName = new Map(layout.nodes.map((node) => [node.objectType.name, node]));
636
+ if (ir.objectTypes.length === 0) {
637
+ return (_jsx("div", { className: "ps:grid ps:h-full ps:place-items-center ps:text-sm ps:text-zinc-500", children: "This ontology has no schema." }));
638
+ }
639
+ return (_jsx("div", { className: "ps:h-full ps:overflow-auto ps:p-5", children: _jsxs("div", { className: "ps:relative ps:rounded-2xl ps:border ps:border-zinc-500/20 ps:bg-zinc-950/25 ps:in-[.ps-theme-light]:bg-white/45", style: { height: layout.height, minWidth: layout.width }, children: [_jsxs("svg", { "aria-label": "Ontology relations", className: "ps:absolute ps:inset-0 ps:size-full ps:overflow-visible", role: "img", viewBox: `0 0 ${layout.width} ${layout.height}`, children: [_jsx("defs", { children: _jsx("marker", { id: "ps-schema-arrow", markerHeight: "7", markerWidth: "7", orient: "auto-start-reverse", refX: "6", refY: "3.5", children: _jsx("path", { className: "ps:fill-rose-400", d: "M0,0 L7,3.5 L0,7 Z" }) }) }), ir.linkTypes.map((link) => {
640
+ const source = nodeByName.get(link.source.objectType);
641
+ const target = nodeByName.get(link.target.objectType);
642
+ if (!source || !target)
643
+ return null;
644
+ const sourceX = source.x + 115;
645
+ const sourceY = source.y + 88;
646
+ const targetX = target.x + 115;
647
+ const targetY = target.y + 88;
648
+ const labelX = (sourceX + targetX) / 2;
649
+ const labelY = (sourceY + targetY) / 2;
650
+ return (_jsxs("g", { children: [_jsx("line", { className: "ps:stroke-rose-400/70", markerEnd: "url(#ps-schema-arrow)", strokeWidth: "1.5", x1: sourceX, x2: targetX, y1: sourceY, y2: targetY }), _jsxs("text", { className: "ps:fill-zinc-400 ps:text-[10px]", textAnchor: "middle", x: labelX, y: labelY - 6, children: [link.source.displayName, " \u00B7 ", link.cardinality] })] }, link.id));
651
+ })] }), layout.nodes.map(({ objectType, x, y }) => (_jsxs("article", { className: "ps:absolute ps:h-44 ps:w-[230px] ps:overflow-hidden ps:rounded-xl ps:border ps:border-zinc-500/30 ps:bg-zinc-900/95 ps:shadow-xl ps:in-[.ps-theme-light]:bg-white/95", style: { left: x, top: y }, children: [_jsxs("header", { className: "ps:border-b ps:border-zinc-500/20 ps:px-3.5 ps:py-3", children: [_jsx("h3", { className: "ps:m-0 ps:text-sm ps:font-semibold", children: objectType.displayName }), _jsx("p", { className: "ps:mt-0.5 ps:mb-0 ps:font-mono ps:text-[10px] ps:text-zinc-500", children: objectType.name })] }), _jsx("div", { className: "ps:max-h-28 ps:overflow-y-auto ps:px-3.5 ps:py-2", children: objectType.properties.map((property) => (_jsxs("div", { className: "ps:flex ps:items-center ps:justify-between ps:gap-3 ps:py-1 ps:text-[11px]", children: [_jsx("span", { className: "ps:truncate", children: property.displayName }), property.name === objectType.primaryKey ? (_jsx("span", { className: "ps:flex-none ps:text-[9px] ps:font-bold ps:text-rose-400", children: "PK" })) : null] }, property.name))) })] }, objectType.name)))] }) }));
652
+ }
653
+ export function OntologyDevtoolsPanel({ ontology, theme = "dark", }) {
654
+ const { data: outboxEntries } = useLiveQuery((query) => query.from({ entry: ontology.outbox.collection }), [ontology.outbox]);
655
+ const outboxActivity = getOutboxActivity(outboxEntries);
656
+ return (_jsx(Tooltip.Provider, { children: _jsxs(Tabs.Root, { className: `ps-devtools ps-theme-${theme} ps:flex ps:h-full ps:min-h-0 ps:w-full ps:min-w-0 ps:flex-col ps:overflow-hidden ps:font-sans ps:text-[13px] ${theme === "dark"
657
+ ? "ps:bg-zinc-950 ps:text-stone-200"
658
+ : "ps:bg-stone-50 ps:text-stone-800"}`, defaultValue: "outbox", style: { contain: "inline-size" }, children: [_jsx(Tabs.List, { "aria-label": "Ontology devtools views", className: "ps:flex ps:flex-none ps:items-center ps:gap-1 ps:border-b ps:border-zinc-500/20 ps:px-4", style: { height: 40 }, children: ontologyViews.map((view) => {
659
+ const Icon = view.icon;
660
+ return (_jsxs(Tabs.Tab, { className: "ps:group ps:flex ps:h-full ps:items-center ps:gap-1.5 ps:border-x-0 ps:border-t-0 ps:border-b-2 ps:border-transparent ps:bg-transparent ps:px-3 ps:text-xs ps:font-medium ps:text-zinc-500 ps:hover:text-current ps:data-[active]:border-rose-400 ps:data-[active]:font-semibold ps:data-[active]:text-rose-400", value: view.id, children: [_jsx(Icon, { "aria-hidden": true, className: "ps:flex-none", style: { height: 16, width: 16 } }), _jsx("span", { children: view.label }), view.id === "outbox" ? (_jsx(_Fragment, { children: _jsx("span", { className: `ps:inline-flex ps:min-w-5 ps:items-center ps:justify-center ps:rounded-full ps:px-1.5 ps:py-0.5 ps:text-center ps:text-[10px] ps:leading-none ps:transition-colors ${outboxBadgeClass(outboxActivity)}`, title: `${outboxEntries.length} outbox entries · ${outboxActivityLabel(outboxActivity)}`, children: _jsx(NumberFlow, { className: "ps:tabular-nums", isolate: true, value: outboxEntries.length }) }) })) : null] }, view.id));
661
+ }) }), _jsx(Tabs.Panel, { className: "ps:min-h-0 ps:min-w-0 ps:flex-1 ps:overflow-hidden", value: "outbox", children: _jsx(OutboxRows, { outbox: ontology.outbox, theme: theme }) }), _jsx(Tabs.Panel, { className: "ps:min-h-0 ps:min-w-0 ps:flex-1 ps:overflow-hidden", value: "schema", children: _jsx(SchemaView, { ir: ontology.ir }) }), _jsx(Tabs.Panel, { className: "ps:min-h-0 ps:min-w-0 ps:flex-1 ps:overflow-hidden", value: "objects", children: _jsx(ObjectsView, { ontology: ontology }) })] }) }));
662
+ }
663
+ export function createOntologyDevtoolsPlugin({ ontology, id = "party-stack-ontology", name, defaultOpen, }) {
664
+ const [createPlugin] = createReactPlugin({
665
+ id,
666
+ name: "Ontology",
667
+ defaultOpen,
668
+ Component: ({ theme }) => _jsx(OntologyDevtoolsPanel, { ontology: ontology, theme: theme }),
669
+ });
670
+ return {
671
+ ...createPlugin(),
672
+ name: name ??
673
+ ((_element, { theme }) => _jsx(OntologyDevtoolsPluginName, { theme: theme })),
674
+ };
519
675
  }
520
676
  //# sourceMappingURL=OntologyDevtools.js.map