@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.
- package/dist/module.d.mts +15 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -0
- package/dist/runtime/components/ACodeEditor.vue +123 -22
- package/dist/runtime/components/ADocPickerModal.d.vue.ts +31 -0
- package/dist/runtime/components/ADocPickerModal.vue +191 -0
- package/dist/runtime/components/ADocPickerModal.vue.d.ts +31 -0
- package/dist/runtime/components/ADocViewToggle.d.vue.ts +40 -0
- package/dist/runtime/components/ADocViewToggle.vue +234 -0
- package/dist/runtime/components/ADocViewToggle.vue.d.ts +40 -0
- package/dist/runtime/components/ADocumentTree.vue +66 -1
- package/dist/runtime/components/AEditor.d.vue.ts +17 -10
- package/dist/runtime/components/AEditor.vue +403 -167
- package/dist/runtime/components/AEditor.vue.d.ts +17 -10
- package/dist/runtime/components/ANodePanel.d.vue.ts +9 -1
- package/dist/runtime/components/ANodePanel.vue +553 -481
- package/dist/runtime/components/ANodePanel.vue.d.ts +9 -1
- package/dist/runtime/components/ATagsEditor.d.vue.ts +19 -0
- package/dist/runtime/components/ATagsEditor.vue +60 -0
- package/dist/runtime/components/ATagsEditor.vue.d.ts +19 -0
- package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
- package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
- package/dist/runtime/components/chat/AChatInput.d.vue.ts +11 -6
- package/dist/runtime/components/chat/AChatInput.vue +33 -2
- package/dist/runtime/components/chat/AChatInput.vue.d.ts +11 -6
- package/dist/runtime/components/chat/AChatList.d.vue.ts +12 -0
- package/dist/runtime/components/chat/AChatList.vue +76 -32
- package/dist/runtime/components/chat/AChatList.vue.d.ts +12 -0
- package/dist/runtime/components/chat/AChatMessages.d.vue.ts +4 -0
- package/dist/runtime/components/chat/AChatMessages.vue +57 -4
- package/dist/runtime/components/chat/AChatMessages.vue.d.ts +4 -0
- package/dist/runtime/components/chat/AChatPanel.d.vue.ts +6 -2
- package/dist/runtime/components/chat/AChatPanel.vue +17 -1
- package/dist/runtime/components/chat/AChatPanel.vue.d.ts +6 -2
- package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -1
- package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
- package/dist/runtime/components/editor/ADocSuggestMenu.d.vue.ts +7 -0
- package/dist/runtime/components/editor/ADocSuggestMenu.vue +68 -0
- package/dist/runtime/components/editor/ADocSuggestMenu.vue.d.ts +7 -0
- package/dist/runtime/components/renderers/AChartRenderer.client.d.vue.ts +17 -0
- package/dist/runtime/components/renderers/AChartRenderer.client.vue +622 -0
- package/dist/runtime/components/renderers/AChartRenderer.client.vue.d.ts +17 -0
- package/dist/runtime/components/renderers/AGraphRenderer.vue +64 -15
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/sheets/ASheetsGrid.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/sheets/ASheetsGrid.vue.d.ts +2 -2
- package/dist/runtime/components/settings/ASettingsAppearancePanel.d.vue.ts +3 -0
- package/dist/runtime/components/settings/ASettingsAppearancePanel.vue +67 -0
- package/dist/runtime/components/settings/ASettingsAppearancePanel.vue.d.ts +3 -0
- package/dist/runtime/components/settings/ASettingsGroup.d.vue.ts +24 -0
- package/dist/runtime/components/settings/ASettingsGroup.vue +31 -0
- package/dist/runtime/components/settings/ASettingsGroup.vue.d.ts +24 -0
- package/dist/runtime/components/settings/ASettingsModal.vue +84 -53
- package/dist/runtime/components/settings/ASettingsPlaceholder.d.vue.ts +20 -0
- package/dist/runtime/components/settings/ASettingsPlaceholder.vue +32 -0
- package/dist/runtime/components/settings/ASettingsPlaceholder.vue.d.ts +20 -0
- package/dist/runtime/components/settings/ASettingsRow.d.vue.ts +34 -0
- package/dist/runtime/components/settings/ASettingsRow.vue +34 -0
- package/dist/runtime/components/settings/ASettingsRow.vue.d.ts +34 -0
- package/dist/runtime/components/settings/sections.d.ts +37 -0
- package/dist/runtime/components/settings/sections.js +45 -0
- package/dist/runtime/components/shell/AUserMenu.d.vue.ts +2 -2
- package/dist/runtime/components/shell/AUserMenu.vue.d.ts +2 -2
- package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
- package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
- package/dist/runtime/composables/useChat.d.ts +22 -1
- package/dist/runtime/composables/useChat.js +79 -8
- package/dist/runtime/composables/useDocLinkPick.d.ts +9 -8
- package/dist/runtime/composables/useDocLinkPick.js +7 -18
- package/dist/runtime/composables/useDocSuggest.d.ts +34 -0
- package/dist/runtime/composables/useDocSuggest.js +56 -0
- package/dist/runtime/composables/useNodeContextMenu.d.ts +4 -0
- package/dist/runtime/composables/useNodeContextMenu.js +18 -0
- package/dist/runtime/composables/useSettingsModal.d.ts +1 -1
- package/dist/runtime/extensions/doc-link-drop.js +2 -2
- package/dist/runtime/extensions/doc-suggest.d.ts +28 -0
- package/dist/runtime/extensions/doc-suggest.js +85 -0
- package/dist/runtime/locale.d.ts +8 -0
- package/dist/runtime/locale.js +9 -1
- package/dist/runtime/utils/chatContent.d.ts +20 -2
- package/dist/runtime/utils/chatContent.js +20 -1
- package/dist/runtime/utils/codeHighlightStyle.d.ts +15 -0
- package/dist/runtime/utils/codeHighlightStyle.js +34 -0
- package/dist/runtime/utils/docTypes.js +43 -0
- package/dist/runtime/utils/loadCodeMirror.d.ts +1 -0
- package/dist/runtime/utils/loadCodeMirror.js +6 -3
- package/dist/runtime/utils/titleSync.d.ts +130 -0
- package/dist/runtime/utils/titleSync.js +53 -0
- 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;
|