@abraca/nuxt 2.11.0 → 2.14.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.
Files changed (93) hide show
  1. package/dist/module.d.mts +15 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +9 -0
  4. package/dist/runtime/components/ACodeEditor.vue +123 -22
  5. package/dist/runtime/components/ADocPickerModal.d.vue.ts +31 -0
  6. package/dist/runtime/components/ADocPickerModal.vue +191 -0
  7. package/dist/runtime/components/ADocPickerModal.vue.d.ts +31 -0
  8. package/dist/runtime/components/ADocViewToggle.d.vue.ts +40 -0
  9. package/dist/runtime/components/ADocViewToggle.vue +234 -0
  10. package/dist/runtime/components/ADocViewToggle.vue.d.ts +40 -0
  11. package/dist/runtime/components/ADocumentTree.vue +66 -1
  12. package/dist/runtime/components/AEditor.d.vue.ts +17 -10
  13. package/dist/runtime/components/AEditor.vue +403 -167
  14. package/dist/runtime/components/AEditor.vue.d.ts +17 -10
  15. package/dist/runtime/components/ANodePanel.d.vue.ts +9 -1
  16. package/dist/runtime/components/ANodePanel.vue +553 -481
  17. package/dist/runtime/components/ANodePanel.vue.d.ts +9 -1
  18. package/dist/runtime/components/ATagsEditor.d.vue.ts +19 -0
  19. package/dist/runtime/components/ATagsEditor.vue +60 -0
  20. package/dist/runtime/components/ATagsEditor.vue.d.ts +19 -0
  21. package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
  22. package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
  23. package/dist/runtime/components/chat/AChatInput.d.vue.ts +11 -6
  24. package/dist/runtime/components/chat/AChatInput.vue +33 -2
  25. package/dist/runtime/components/chat/AChatInput.vue.d.ts +11 -6
  26. package/dist/runtime/components/chat/AChatList.d.vue.ts +12 -0
  27. package/dist/runtime/components/chat/AChatList.vue +76 -32
  28. package/dist/runtime/components/chat/AChatList.vue.d.ts +12 -0
  29. package/dist/runtime/components/chat/AChatMessages.d.vue.ts +4 -0
  30. package/dist/runtime/components/chat/AChatMessages.vue +57 -4
  31. package/dist/runtime/components/chat/AChatMessages.vue.d.ts +4 -0
  32. package/dist/runtime/components/chat/AChatPanel.d.vue.ts +6 -2
  33. package/dist/runtime/components/chat/AChatPanel.vue +17 -1
  34. package/dist/runtime/components/chat/AChatPanel.vue.d.ts +6 -2
  35. package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -1
  36. package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
  37. package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
  38. package/dist/runtime/components/editor/ADocSuggestMenu.d.vue.ts +7 -0
  39. package/dist/runtime/components/editor/ADocSuggestMenu.vue +68 -0
  40. package/dist/runtime/components/editor/ADocSuggestMenu.vue.d.ts +7 -0
  41. package/dist/runtime/components/renderers/AChartRenderer.client.d.vue.ts +17 -0
  42. package/dist/runtime/components/renderers/AChartRenderer.client.vue +622 -0
  43. package/dist/runtime/components/renderers/AChartRenderer.client.vue.d.ts +17 -0
  44. package/dist/runtime/components/renderers/AGraphRenderer.vue +64 -15
  45. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +2 -2
  46. package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +2 -2
  47. package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
  48. package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
  49. package/dist/runtime/components/renderers/sheets/ASheetsGrid.d.vue.ts +2 -2
  50. package/dist/runtime/components/renderers/sheets/ASheetsGrid.vue.d.ts +2 -2
  51. package/dist/runtime/components/settings/ASettingsAppearancePanel.d.vue.ts +3 -0
  52. package/dist/runtime/components/settings/ASettingsAppearancePanel.vue +67 -0
  53. package/dist/runtime/components/settings/ASettingsAppearancePanel.vue.d.ts +3 -0
  54. package/dist/runtime/components/settings/ASettingsGroup.d.vue.ts +24 -0
  55. package/dist/runtime/components/settings/ASettingsGroup.vue +31 -0
  56. package/dist/runtime/components/settings/ASettingsGroup.vue.d.ts +24 -0
  57. package/dist/runtime/components/settings/ASettingsModal.vue +84 -53
  58. package/dist/runtime/components/settings/ASettingsPlaceholder.d.vue.ts +20 -0
  59. package/dist/runtime/components/settings/ASettingsPlaceholder.vue +32 -0
  60. package/dist/runtime/components/settings/ASettingsPlaceholder.vue.d.ts +20 -0
  61. package/dist/runtime/components/settings/ASettingsRow.d.vue.ts +34 -0
  62. package/dist/runtime/components/settings/ASettingsRow.vue +34 -0
  63. package/dist/runtime/components/settings/ASettingsRow.vue.d.ts +34 -0
  64. package/dist/runtime/components/settings/sections.d.ts +37 -0
  65. package/dist/runtime/components/settings/sections.js +45 -0
  66. package/dist/runtime/components/shell/AUserMenu.d.vue.ts +2 -2
  67. package/dist/runtime/components/shell/AUserMenu.vue.d.ts +2 -2
  68. package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
  69. package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
  70. package/dist/runtime/composables/useChat.d.ts +22 -1
  71. package/dist/runtime/composables/useChat.js +79 -8
  72. package/dist/runtime/composables/useDocLinkPick.d.ts +9 -8
  73. package/dist/runtime/composables/useDocLinkPick.js +7 -18
  74. package/dist/runtime/composables/useDocSuggest.d.ts +34 -0
  75. package/dist/runtime/composables/useDocSuggest.js +56 -0
  76. package/dist/runtime/composables/useNodeContextMenu.d.ts +4 -0
  77. package/dist/runtime/composables/useNodeContextMenu.js +18 -0
  78. package/dist/runtime/composables/useSettingsModal.d.ts +1 -1
  79. package/dist/runtime/extensions/doc-link-drop.js +2 -2
  80. package/dist/runtime/extensions/doc-suggest.d.ts +28 -0
  81. package/dist/runtime/extensions/doc-suggest.js +85 -0
  82. package/dist/runtime/locale.d.ts +8 -0
  83. package/dist/runtime/locale.js +9 -1
  84. package/dist/runtime/utils/chatContent.d.ts +20 -2
  85. package/dist/runtime/utils/chatContent.js +20 -1
  86. package/dist/runtime/utils/codeHighlightStyle.d.ts +15 -0
  87. package/dist/runtime/utils/codeHighlightStyle.js +34 -0
  88. package/dist/runtime/utils/docTypes.js +43 -0
  89. package/dist/runtime/utils/loadCodeMirror.d.ts +1 -0
  90. package/dist/runtime/utils/loadCodeMirror.js +6 -3
  91. package/dist/runtime/utils/titleSync.d.ts +130 -0
  92. package/dist/runtime/utils/titleSync.js +53 -0
  93. package/package.json +12 -1
@@ -0,0 +1,622 @@
1
+ <script setup>
2
+ import { ref, computed, shallowRef, onMounted, onBeforeUnmount } from "vue";
3
+ import { useElementSize } from "@vueuse/core";
4
+ import { useRendererBase } from "../../composables/useRendererBase";
5
+ import { useNodePanel } from "../../composables/useNodePanel";
6
+ import { useNodePanelFollow } from "../../composables/useNodePanelFollow";
7
+ import { useDocumentPermissions } from "../../composables/useDocumentPermissions";
8
+ import { useAbracadabra } from "../../composables/useAbracadabra";
9
+ import { usePluginRegistry } from "../../composables/usePluginRegistry";
10
+ import { resolveDocType } from "../../utils/docTypes";
11
+ const props = defineProps({
12
+ docId: { type: String, required: true },
13
+ childProvider: { type: null, required: true },
14
+ docLabel: { type: String, required: true },
15
+ pageTypes: { type: Array, required: false },
16
+ followingUser: { type: [String, null], required: false, default: null }
17
+ });
18
+ const { childProviderRef, tree, connectedUsers, states, setLocalState } = useRendererBase(props);
19
+ const { canWrite } = useDocumentPermissions(useAbracadabra().effectiveRole);
20
+ const registry = usePluginRegistry();
21
+ const {
22
+ openNodeId,
23
+ openNodeLabel,
24
+ openNodeProvider,
25
+ isLoading: nodePanelLoading,
26
+ openNode,
27
+ closePanel
28
+ } = useNodePanel(childProviderRef);
29
+ useNodePanelFollow(openNodeId, openNodeLabel, openNode, closePanel, () => props.followingUser, states, setLocalState);
30
+ const vis = shallowRef(null);
31
+ const visError = ref(false);
32
+ onMounted(async () => {
33
+ try {
34
+ vis.value = await import("@unovis/vue");
35
+ } catch {
36
+ visError.value = true;
37
+ }
38
+ });
39
+ const containerRef = ref(null);
40
+ const { width } = useElementSize(containerRef);
41
+ onBeforeUnmount(() => {
42
+ setLocalState({ "chart:focused": null });
43
+ });
44
+ const PALETTES = {
45
+ default: ["#6366f1", "#ec4899", "#f97316", "#22c55e", "#3b82f6", "#a855f7", "#14b8a6", "#eab308"],
46
+ warm: ["#ef4444", "#f97316", "#eab308", "#f59e0b", "#dc2626", "#fb923c", "#fbbf24", "#fde047"],
47
+ cool: ["#3b82f6", "#06b6d4", "#14b8a6", "#22c55e", "#6366f1", "#0ea5e9", "#10b981", "#34d399"],
48
+ mono: ["#18181b", "#3f3f46", "#52525b", "#71717a", "#a1a1aa", "#d4d4d8", "#e4e4e7", "#f4f4f5"]
49
+ };
50
+ const PRIORITY_LABELS = {
51
+ 0: "None",
52
+ 1: "Low",
53
+ 2: "Medium",
54
+ 3: "High",
55
+ 4: "Urgent"
56
+ };
57
+ const docMeta = computed(() => tree.treeMap.data?.[props.docId]?.meta);
58
+ const chartConfig = computed(() => {
59
+ const meta = docMeta.value ?? {};
60
+ return {
61
+ type: meta.chartType || "bar",
62
+ metric: meta.chartMetric || "value",
63
+ colorScheme: meta.chartColorScheme || "default",
64
+ limit: meta.chartLimit || 12,
65
+ showLegend: meta.chartShowLegend !== false,
66
+ showValues: meta.chartShowValues === true
67
+ };
68
+ });
69
+ function setConfig(patch) {
70
+ if (!canWrite.value) return;
71
+ tree.updateMeta(props.docId, patch);
72
+ }
73
+ const TYPE_OPTIONS = ["bar", "stacked bar", "line", "donut", "treemap"];
74
+ const METRIC_OPTIONS = ["value", "type", "tag", "status", "priority", "activity", "completion"];
75
+ const COLOR_OPTIONS = ["default", "warm", "cool", "mono"];
76
+ const colors = computed(() => PALETTES[chartConfig.value.colorScheme] ?? PALETTES.default);
77
+ const isXY = computed(() => ["bar", "stacked bar", "line"].includes(chartConfig.value.type));
78
+ const isValueMode = computed(() => chartConfig.value.metric === "value");
79
+ const allEntries = computed(() => {
80
+ if (isValueMode.value) return [];
81
+ const result = [];
82
+ function collect(parentId) {
83
+ for (const e of tree.childrenOf(parentId)) {
84
+ result.push(e);
85
+ collect(e.id);
86
+ }
87
+ }
88
+ collect(null);
89
+ return result;
90
+ });
91
+ const directChildren = computed(() => tree.childrenOf(null));
92
+ const aggregated = computed(() => {
93
+ const entries = allEntries.value;
94
+ const byType = /* @__PURE__ */ new Map();
95
+ const byTag = /* @__PURE__ */ new Map();
96
+ const byStatus = /* @__PURE__ */ new Map();
97
+ const byPriority = /* @__PURE__ */ new Map();
98
+ const byWeek = /* @__PURE__ */ new Map();
99
+ let taskDone = 0;
100
+ let taskPending = 0;
101
+ for (const e of entries) {
102
+ const type = e.type || "doc";
103
+ byType.set(type, (byType.get(type) || 0) + 1);
104
+ if (e.meta?.tags) {
105
+ for (const tag of e.meta.tags) {
106
+ byTag.set(tag, (byTag.get(tag) || 0) + 1);
107
+ }
108
+ }
109
+ if (e.meta?.status) {
110
+ const s = e.meta.status;
111
+ byStatus.set(s, (byStatus.get(s) || 0) + 1);
112
+ }
113
+ if (e.meta?.priority !== void 0) {
114
+ const p = e.meta.priority;
115
+ byPriority.set(p, (byPriority.get(p) || 0) + 1);
116
+ }
117
+ if (e.meta?.checked !== void 0) {
118
+ if (e.meta.checked) {
119
+ taskDone++;
120
+ if (e.updatedAt) {
121
+ const d = new Date(e.updatedAt);
122
+ const jan1 = new Date(d.getFullYear(), 0, 1);
123
+ const weekNum = Math.ceil(((d.getTime() - jan1.getTime()) / 864e5 + jan1.getDay() + 1) / 7);
124
+ const weekKey = `${d.getFullYear()}-W${String(weekNum).padStart(2, "0")}`;
125
+ byWeek.set(weekKey, (byWeek.get(weekKey) || 0) + 1);
126
+ }
127
+ } else {
128
+ taskPending++;
129
+ }
130
+ }
131
+ }
132
+ return { byType, byTag, byStatus, byPriority, byWeek, taskDone, taskPending };
133
+ });
134
+ const metricTitle = computed(() => {
135
+ const titles = {
136
+ value: "Values",
137
+ type: "Documents by Type",
138
+ tag: "Documents by Tag",
139
+ status: "Documents by Status",
140
+ priority: "Documents by Priority",
141
+ activity: "Tasks Over Time",
142
+ completion: "Task Completion"
143
+ };
144
+ return titles[chartConfig.value.metric] ?? "Values";
145
+ });
146
+ const summaryText = computed(() => {
147
+ const data = xyData.value;
148
+ if (!data.length) return "";
149
+ const total = data.reduce((s, d) => s + d.value, 0);
150
+ const categories = data.length;
151
+ const done = aggregated.value.taskDone;
152
+ const pending = aggregated.value.taskPending;
153
+ const summaries = {
154
+ value: `${total} total across ${categories} data points`,
155
+ type: `${total} documents across ${categories} types`,
156
+ tag: `${total} tagged items across ${categories} tags`,
157
+ status: `${total} items across ${categories} statuses`,
158
+ priority: `${total} items across ${categories} priority levels`,
159
+ activity: `${total} completed tasks across ${categories} weeks`,
160
+ completion: `${done} done, ${pending} pending`
161
+ };
162
+ return summaries[chartConfig.value.metric] ?? "";
163
+ });
164
+ const xyData = computed(() => {
165
+ const { metric, limit } = chartConfig.value;
166
+ const agg = aggregated.value;
167
+ switch (metric) {
168
+ case "value": {
169
+ const children = directChildren.value;
170
+ const hasGrandchildren = children.some((c) => tree.childrenOf(c.id).length > 0);
171
+ if (hasGrandchildren) {
172
+ return children.map((child) => {
173
+ const cells = tree.childrenOf(child.id);
174
+ const sum = cells.reduce((s, cell) => s + (cell.meta?.number ?? 0), 0);
175
+ return {
176
+ label: child.label || "Untitled",
177
+ value: sum,
178
+ id: child.id
179
+ };
180
+ });
181
+ }
182
+ return children.map((child) => ({
183
+ label: child.label || "Untitled",
184
+ value: child.meta?.number ?? 0,
185
+ id: child.id
186
+ }));
187
+ }
188
+ case "type":
189
+ return [...agg.byType.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit).map(([k, v]) => ({ label: resolveDocType(k, registry).label, value: v, id: "" }));
190
+ case "tag":
191
+ return [...agg.byTag.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit).map(([k, v]) => ({ label: k, value: v, id: "" }));
192
+ case "status":
193
+ return [...agg.byStatus.entries()].sort((a, b) => b[1] - a[1]).slice(0, limit).map(([k, v]) => ({ label: k, value: v, id: "" }));
194
+ case "priority":
195
+ return [...agg.byPriority.entries()].sort((a, b) => a[0] - b[0]).map(([k, v]) => ({ label: PRIORITY_LABELS[k] ?? String(k), value: v, id: "" }));
196
+ case "activity":
197
+ return [...agg.byWeek.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-limit).map(([k, v]) => ({ label: k, value: v, id: "" }));
198
+ case "completion":
199
+ return [
200
+ { label: "Done", value: agg.taskDone, id: "" },
201
+ { label: "Pending", value: agg.taskPending, id: "" }
202
+ ].filter((d) => d.value > 0);
203
+ default:
204
+ return [];
205
+ }
206
+ });
207
+ const itemColors = computed(() => {
208
+ if (!isValueMode.value) return colors.value;
209
+ return xyData.value.map((d, i) => {
210
+ const child = directChildren.value.find((c) => c.id === d.id);
211
+ return child?.meta?.color ?? colors.value[i % colors.value.length];
212
+ });
213
+ });
214
+ const singleData = computed(() => {
215
+ return xyData.value.map((d) => ({
216
+ name: d.label,
217
+ value: d.value,
218
+ id: d.id
219
+ }));
220
+ });
221
+ const treemapData = computed(() => {
222
+ if (isValueMode.value) {
223
+ return directChildren.value.map((child) => ({
224
+ name: child.label || "Untitled",
225
+ value: child.meta?.number ?? 1,
226
+ id: child.id
227
+ }));
228
+ }
229
+ const children = tree.childrenOf(null);
230
+ if (!children.length) return [];
231
+ return children.map((child) => ({
232
+ name: child.label || "Untitled",
233
+ value: countDescendants(child.id) + 1,
234
+ id: child.id
235
+ }));
236
+ });
237
+ function countDescendants(parentId) {
238
+ const kids = tree.childrenOf(parentId);
239
+ let count = kids.length;
240
+ for (const k of kids) count += countDescendants(k.id);
241
+ return count;
242
+ }
243
+ const x = (_, i) => i;
244
+ const y = (d) => d.value;
245
+ const xTickFormat = (i) => xyData.value[i]?.label ?? "";
246
+ const donutValue = (d) => d.value;
247
+ const donutColor = (_d, i) => itemColors.value[i % itemColors.value.length];
248
+ const treemapValue = (d) => d.value;
249
+ const hasData = computed(() => {
250
+ if (isValueMode.value) return directChildren.value.length > 0;
251
+ return allEntries.value.length > 0;
252
+ });
253
+ const tooltipTemplate = (d) => `<strong>${d.label}</strong>: ${d.value}`;
254
+ const valueLabel = (d) => String(d.value);
255
+ const legendItems = computed(() => {
256
+ return xyData.value.map((d, i) => ({
257
+ label: d.label,
258
+ value: d.value,
259
+ color: itemColors.value[i % itemColors.value.length],
260
+ id: d.id
261
+ }));
262
+ });
263
+ function addDataPoint() {
264
+ const children = tree.childrenOf(null);
265
+ const id = tree.createChild(null, "Data point");
266
+ tree.updateMeta(id, {
267
+ number: 0,
268
+ color: PALETTES.default[children.length % PALETTES.default.length]
269
+ });
270
+ openNode(id, "Data point");
271
+ }
272
+ function onLegendItemClick(item) {
273
+ if (item.id) openNode(item.id, item.label);
274
+ }
275
+ function onLegendItemHover(itemId) {
276
+ setLocalState({ "chart:focused": itemId });
277
+ }
278
+ const remoteFocused = computed(() => {
279
+ const focused = /* @__PURE__ */ new Map();
280
+ for (const s of states.value) {
281
+ const fid = s["chart:focused"];
282
+ if (fid && s.user) {
283
+ focused.set(fid, { name: s.user.name ?? "", color: s.user.color ?? "#888" });
284
+ }
285
+ }
286
+ return focused;
287
+ });
288
+ defineExpose({ connectedUsers });
289
+ </script>
290
+
291
+ <template>
292
+ <div
293
+ ref="containerRef"
294
+ class="flex-1 overflow-y-auto"
295
+ >
296
+ <div class="max-w-5xl mx-auto p-4 sm:p-6 lg:p-8 pr-8 sm:pr-10 lg:pr-12 space-y-6">
297
+ <!-- Header: title + summary + config + add button -->
298
+ <div class="flex items-start justify-between gap-4">
299
+ <div class="space-y-1 min-w-0">
300
+ <h2 class="text-lg font-semibold text-(--ui-text-highlighted)">
301
+ {{ metricTitle }}
302
+ </h2>
303
+ <p
304
+ v-if="summaryText"
305
+ class="text-sm text-(--ui-text-dimmed)"
306
+ >
307
+ {{ summaryText }}
308
+ </p>
309
+ </div>
310
+ <div class="flex items-center gap-1.5 shrink-0">
311
+ <!-- Config popover -->
312
+ <UPopover v-if="canWrite">
313
+ <UButton
314
+ icon="i-lucide-settings-2"
315
+ size="xs"
316
+ variant="ghost"
317
+ color="neutral"
318
+ aria-label="Chart settings"
319
+ />
320
+ <template #content>
321
+ <div class="p-3 w-64 space-y-3">
322
+ <UFormField
323
+ label="Chart type"
324
+ size="xs"
325
+ >
326
+ <USelectMenu
327
+ :model-value="chartConfig.type"
328
+ :items="TYPE_OPTIONS"
329
+ size="xs"
330
+ class="w-full"
331
+ @update:model-value="(v) => setConfig({ chartType: v })"
332
+ />
333
+ </UFormField>
334
+ <UFormField
335
+ label="Metric"
336
+ size="xs"
337
+ >
338
+ <USelectMenu
339
+ :model-value="chartConfig.metric"
340
+ :items="METRIC_OPTIONS"
341
+ size="xs"
342
+ class="w-full"
343
+ @update:model-value="(v) => setConfig({ chartMetric: v })"
344
+ />
345
+ </UFormField>
346
+ <UFormField
347
+ label="Colors"
348
+ size="xs"
349
+ >
350
+ <USelectMenu
351
+ :model-value="chartConfig.colorScheme"
352
+ :items="COLOR_OPTIONS"
353
+ size="xs"
354
+ class="w-full"
355
+ @update:model-value="(v) => setConfig({ chartColorScheme: v })"
356
+ />
357
+ </UFormField>
358
+ <UFormField
359
+ label="Max items"
360
+ size="xs"
361
+ >
362
+ <UInputNumber
363
+ :model-value="chartConfig.limit"
364
+ :min="3"
365
+ :max="30"
366
+ size="xs"
367
+ class="w-full"
368
+ @update:model-value="(v) => setConfig({ chartLimit: v })"
369
+ />
370
+ </UFormField>
371
+ <div class="flex items-center justify-between">
372
+ <span class="text-xs text-(--ui-text-muted)">Show legend</span>
373
+ <USwitch
374
+ :model-value="chartConfig.showLegend"
375
+ size="xs"
376
+ @update:model-value="(v) => setConfig({ chartShowLegend: v })"
377
+ />
378
+ </div>
379
+ <div class="flex items-center justify-between">
380
+ <span class="text-xs text-(--ui-text-muted)">Show values</span>
381
+ <USwitch
382
+ :model-value="chartConfig.showValues"
383
+ size="xs"
384
+ @update:model-value="(v) => setConfig({ chartShowValues: v })"
385
+ />
386
+ </div>
387
+ </div>
388
+ </template>
389
+ </UPopover>
390
+ <UButton
391
+ v-if="isValueMode && canWrite"
392
+ icon="i-lucide-plus"
393
+ size="xs"
394
+ variant="ghost"
395
+ label="Add data point"
396
+ @click="addDataPoint"
397
+ />
398
+ </div>
399
+ </div>
400
+
401
+ <!-- Unovis not installed fallback -->
402
+ <div
403
+ v-if="visError"
404
+ class="flex flex-col items-center justify-center py-24 text-center gap-3 text-(--ui-text-dimmed)"
405
+ >
406
+ <UIcon
407
+ name="i-lucide-bar-chart-3"
408
+ class="size-10 opacity-30"
409
+ />
410
+ <p class="text-sm text-(--ui-text-muted)">
411
+ Chart renderer requires <code>@unovis/vue</code>
412
+ </p>
413
+ <p class="text-xs">
414
+ Install it with: <code>pnpm add @unovis/vue @unovis/ts</code>
415
+ </p>
416
+ </div>
417
+
418
+ <!-- Chart area -->
419
+ <template v-else-if="hasData && vis">
420
+ <!-- XY charts: bar, stacked bar, line -->
421
+ <template v-if="isXY && xyData.length">
422
+ <component
423
+ :is="vis.VisXYContainer"
424
+ :data="xyData"
425
+ :padding="{ top: 10, left: 10, right: 30, bottom: 4 }"
426
+ class="h-96"
427
+ :width="width ? width - 32 : void 0"
428
+ >
429
+ <component
430
+ :is="vis.VisGroupedBar"
431
+ v-if="chartConfig.type === 'bar'"
432
+ :x="x"
433
+ :y="[y]"
434
+ :color="itemColors"
435
+ :bar-padding="0.2"
436
+ :rounded-corners="4"
437
+ />
438
+ <component
439
+ :is="vis.VisStackedBar"
440
+ v-else-if="chartConfig.type === 'stacked bar'"
441
+ :x="x"
442
+ :y="[y]"
443
+ :color="itemColors"
444
+ :bar-padding="0.2"
445
+ :rounded-corners="4"
446
+ />
447
+ <template v-else-if="chartConfig.type === 'line'">
448
+ <component
449
+ :is="vis.VisLine"
450
+ :x="x"
451
+ :y="y"
452
+ :color="itemColors[0]"
453
+ />
454
+ <component
455
+ :is="vis.VisArea"
456
+ :x="x"
457
+ :y="y"
458
+ :color="itemColors[0]"
459
+ :opacity="0.1"
460
+ />
461
+ </template>
462
+
463
+ <component
464
+ :is="vis.VisAxis"
465
+ type="x"
466
+ :tick-format="xTickFormat"
467
+ />
468
+ <component
469
+ :is="vis.VisAxis"
470
+ type="y"
471
+ />
472
+
473
+ <component
474
+ :is="vis.VisXYLabels"
475
+ v-if="chartConfig.showValues"
476
+ :x="x"
477
+ :y="y"
478
+ :label="valueLabel"
479
+ color="var(--ui-text-dimmed)"
480
+ />
481
+
482
+ <component
483
+ :is="vis.VisCrosshair"
484
+ :color="itemColors[0]"
485
+ :template="tooltipTemplate"
486
+ />
487
+ <component :is="vis.VisTooltip" />
488
+ </component>
489
+ </template>
490
+
491
+ <!-- Donut -->
492
+ <template v-else-if="chartConfig.type === 'donut' && singleData.length">
493
+ <component
494
+ :is="vis.VisSingleContainer"
495
+ :data="singleData"
496
+ class="h-96"
497
+ :width="width ? width - 32 : void 0"
498
+ >
499
+ <component
500
+ :is="vis.VisDonut"
501
+ :value="donutValue"
502
+ :color="donutColor"
503
+ :arc-width="60"
504
+ :corner-radius="4"
505
+ :pad-angle="0.02"
506
+ />
507
+ </component>
508
+ </template>
509
+
510
+ <!-- Treemap -->
511
+ <template v-else-if="chartConfig.type === 'treemap' && treemapData.length">
512
+ <component
513
+ :is="vis.VisSingleContainer"
514
+ :data="treemapData"
515
+ class="h-96"
516
+ :width="width ? width - 32 : void 0"
517
+ >
518
+ <component
519
+ :is="vis.VisTreemap"
520
+ :value="treemapValue"
521
+ :color="donutColor"
522
+ :rounded-corners="4"
523
+ :padding="2"
524
+ :tile-label="(d) => d.name"
525
+ />
526
+ </component>
527
+ </template>
528
+
529
+ <!-- Legend / data point list -->
530
+ <div
531
+ v-if="chartConfig.showLegend && legendItems.length"
532
+ class="flex flex-wrap gap-x-4 gap-y-1.5"
533
+ >
534
+ <div
535
+ v-for="item in legendItems"
536
+ :key="item.label"
537
+ class="flex items-center gap-1.5 text-xs rounded-md px-1.5 py-0.5 transition-colors"
538
+ :class="[
539
+ item.id ? 'cursor-pointer hover:bg-(--ui-bg-elevated)' : '',
540
+ remoteFocused.has(item.id) ? 'ring-1.5 ring-offset-1' : ''
541
+ ]"
542
+ :style="remoteFocused.has(item.id) ? { '--tw-ring-color': remoteFocused.get(item.id).color } : void 0"
543
+ @click="item.id ? onLegendItemClick(item) : void 0"
544
+ @pointerenter="item.id ? onLegendItemHover(item.id) : void 0"
545
+ @pointerleave="onLegendItemHover(null)"
546
+ >
547
+ <div
548
+ class="size-2.5 rounded-sm shrink-0"
549
+ :style="{ background: item.color }"
550
+ />
551
+ <span class="text-(--ui-text-muted)">{{ item.label || "Untitled" }}</span>
552
+ <span class="text-(--ui-text-dimmed) tabular-nums">{{ item.value }}</span>
553
+ <span
554
+ v-if="remoteFocused.has(item.id)"
555
+ class="text-[10px] ml-0.5 opacity-70"
556
+ :style="{ color: remoteFocused.get(item.id).color }"
557
+ >
558
+ {{ remoteFocused.get(item.id).name }}
559
+ </span>
560
+ </div>
561
+ </div>
562
+ </template>
563
+
564
+ <!-- Empty state: value mode -->
565
+ <div
566
+ v-else-if="isValueMode"
567
+ class="flex flex-col items-center justify-center py-24 text-(--ui-text-dimmed)"
568
+ >
569
+ <UIcon
570
+ name="i-lucide-plus-circle"
571
+ class="size-12 mb-3 opacity-20"
572
+ />
573
+ <p class="text-sm font-medium">
574
+ Add your first data point
575
+ </p>
576
+ <p class="text-xs mt-1 opacity-60 max-w-xs text-center">
577
+ Each child document becomes a bar, slice, or point on your chart. Set its value in the metadata panel.
578
+ </p>
579
+ <UButton
580
+ v-if="canWrite"
581
+ icon="i-lucide-plus"
582
+ size="sm"
583
+ variant="soft"
584
+ class="mt-4"
585
+ label="Add data point"
586
+ @click="addDataPoint"
587
+ />
588
+ </div>
589
+
590
+ <!-- Empty state: aggregation mode -->
591
+ <div
592
+ v-else
593
+ class="flex flex-col items-center justify-center py-24 text-(--ui-text-dimmed)"
594
+ >
595
+ <UIcon
596
+ name="i-lucide-bar-chart-3"
597
+ class="size-12 mb-3 opacity-20"
598
+ />
599
+ <p class="text-sm font-medium">
600
+ No documents to aggregate
601
+ </p>
602
+ <p class="text-xs mt-1 opacity-60 max-w-xs text-center">
603
+ Add child documents with metadata (tags, status, priority) to see analytics
604
+ </p>
605
+ </div>
606
+
607
+ <!-- Node panel -->
608
+ <ANodePanel
609
+ :node-id="openNodeId"
610
+ :node-label="openNodeLabel"
611
+ :child-provider="openNodeProvider"
612
+ :is-loading="nodePanelLoading"
613
+ :following-user="followingUser"
614
+ @close="closePanel"
615
+ />
616
+ </div>
617
+ </div>
618
+ </template>
619
+
620
+ <style scoped>
621
+ :deep(.unovis-single-container),:deep(.unovis-xy-container){--vis-crosshair-line-stroke-color:var(--ui-primary);--vis-crosshair-circle-stroke-color:var(--ui-bg);--vis-axis-grid-color:var(--ui-border);--vis-axis-tick-color:var(--ui-border);--vis-axis-tick-label-color:var(--ui-text-dimmed);--vis-tooltip-background-color:var(--ui-bg);--vis-tooltip-border-color:var(--ui-border);--vis-tooltip-text-color:var(--ui-text-highlighted)}
622
+ </style>
@@ -0,0 +1,17 @@
1
+ import { type RendererBaseProps } from '../../composables/useRendererBase.js';
2
+ type __VLS_Props = RendererBaseProps & {
3
+ followingUser?: string | null;
4
+ };
5
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
6
+ connectedUsers: import("vue").ComputedRef<{
7
+ clientId: number;
8
+ name: string;
9
+ color: string;
10
+ avatar: string | undefined;
11
+ publicKey: any;
12
+ }[]>;
13
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
14
+ followingUser: string | null;
15
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
+ declare const _default: typeof __VLS_export;
17
+ export default _default;