@arbidocs/blocks 0.4.78 → 0.4.79
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/agents/index.cjs +281 -281
- package/dist/agents/index.js +6 -6
- package/dist/{chunk-SSDISIDW.cjs → chunk-3YET6D5E.cjs} +39 -39
- package/dist/{chunk-SSDISIDW.cjs.map → chunk-3YET6D5E.cjs.map} +1 -1
- package/dist/{chunk-B3WCKBJ7.cjs → chunk-5XVWGJ2H.cjs} +3 -3
- package/dist/{chunk-B3WCKBJ7.cjs.map → chunk-5XVWGJ2H.cjs.map} +1 -1
- package/dist/{chunk-WXRCMIAE.js → chunk-6GXF62XN.js} +3 -3
- package/dist/{chunk-WXRCMIAE.js.map → chunk-6GXF62XN.js.map} +1 -1
- package/dist/{chunk-5OC5CGYX.js → chunk-BMOL5DN6.js} +7 -7
- package/dist/{chunk-5OC5CGYX.js.map → chunk-BMOL5DN6.js.map} +1 -1
- package/dist/{chunk-GJV2G4FC.js → chunk-O35LRB3P.js} +3 -3
- package/dist/{chunk-GJV2G4FC.js.map → chunk-O35LRB3P.js.map} +1 -1
- package/dist/{chunk-VFIZYSZB.cjs → chunk-YGUKTFKW.cjs} +3 -3
- package/dist/{chunk-VFIZYSZB.cjs.map → chunk-YGUKTFKW.cjs.map} +1 -1
- package/dist/connect/index.cjs +41 -41
- package/dist/connect/index.js +4 -4
- package/dist/databases/ColumnConfigFields.d.ts +68 -0
- package/dist/databases/DatabaseColumnDialog.d.ts +27 -2
- package/dist/databases/DatabaseTable.d.ts +8 -1
- package/dist/databases/PickCellEditor.d.ts +21 -0
- package/dist/databases/column-types.d.ts +73 -9
- package/dist/databases/databaseColumns.d.ts +4 -0
- package/dist/databases/index.cjs +758 -27
- package/dist/databases/index.cjs.map +1 -1
- package/dist/databases/index.d.ts +4 -1
- package/dist/databases/index.js +745 -31
- package/dist/databases/index.js.map +1 -1
- package/dist/databases/linkCells.d.ts +48 -0
- package/dist/facts/index.cjs +1 -1
- package/dist/facts/index.js +1 -1
- package/dist/files/index.cjs +3 -3
- package/dist/files/index.js +3 -3
- package/dist/home/index.cjs +1 -1
- package/dist/home/index.js +1 -1
- package/dist/index.cjs +506 -506
- package/dist/index.js +10 -10
- package/dist/settings/index.cjs +35 -35
- package/dist/settings/index.js +3 -3
- package/dist/shell/index.cjs +1 -1
- package/dist/shell/index.js +1 -1
- package/dist/studio/index.cjs +1 -1
- package/dist/studio/index.js +1 -1
- package/dist/workflow-canvas/index.cjs +49 -49
- package/dist/workflow-canvas/index.js +6 -6
- package/dist/workspaces/index.cjs +1 -1
- package/dist/workspaces/index.js +1 -1
- package/package.json +4 -4
package/dist/databases/index.js
CHANGED
|
@@ -3,9 +3,9 @@ import { GridView } from '../chunk-ZPLG7QYF.js';
|
|
|
3
3
|
import '../chunk-SLSBJRWW.js';
|
|
4
4
|
import { testIdOr } from '../chunk-7F6CBP3Z.js';
|
|
5
5
|
import '../chunk-VUNV25KB.js';
|
|
6
|
-
import { ChevronDown, CheckSquare, CalendarDays, Hash, Type, Check, Plus, ArrowLeft, FileSpreadsheet, Eye,
|
|
7
|
-
import { cn, Button, Input, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Label, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DialogFooter, Textarea } from '@arbidocs/react/ui';
|
|
8
|
-
import {
|
|
6
|
+
import { Sigma, Search, ArrowUpRight, FileText, UserRound, ChevronDown, CheckSquare, CalendarDays, Hash, Type, X, Loader2, Check, Plus, ArrowLeft, FileSpreadsheet, Eye, Trash2, MoreHorizontal, Upload, Download, Table2, Lock } from 'lucide-react';
|
|
7
|
+
import { cn, Button, Input, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Label, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DialogFooter, Textarea, Switch } from '@arbidocs/react/ui';
|
|
8
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
9
|
import { useState, useRef, useEffect, useMemo } from 'react';
|
|
10
10
|
|
|
11
11
|
// src/databases/column-types.ts
|
|
@@ -14,14 +14,34 @@ var DATABASE_COLUMN_TYPES = [
|
|
|
14
14
|
"number",
|
|
15
15
|
"date",
|
|
16
16
|
"checkbox",
|
|
17
|
-
"select"
|
|
17
|
+
"select",
|
|
18
|
+
"person",
|
|
19
|
+
"document",
|
|
20
|
+
"relation",
|
|
21
|
+
"lookup",
|
|
22
|
+
"rollup"
|
|
18
23
|
];
|
|
24
|
+
var COMPUTED_COLUMN_TYPES = /* @__PURE__ */ new Set([
|
|
25
|
+
"lookup",
|
|
26
|
+
"rollup"
|
|
27
|
+
]);
|
|
28
|
+
var LINK_COLUMN_TYPES = /* @__PURE__ */ new Set([
|
|
29
|
+
"document",
|
|
30
|
+
"relation"
|
|
31
|
+
]);
|
|
32
|
+
var isComputedColumn = (column) => COMPUTED_COLUMN_TYPES.has(column.type);
|
|
33
|
+
var DATABASE_ROLLUP_OPS = ["count", "sum", "avg", "min", "max"];
|
|
19
34
|
var DEFAULT_DATABASE_COLUMN_TYPE_LABELS = {
|
|
20
35
|
text: "Text",
|
|
21
36
|
number: "Number",
|
|
22
37
|
date: "Date",
|
|
23
38
|
checkbox: "Checkbox",
|
|
24
|
-
select: "Select"
|
|
39
|
+
select: "Select",
|
|
40
|
+
person: "Person",
|
|
41
|
+
document: "Documents",
|
|
42
|
+
relation: "Relation",
|
|
43
|
+
lookup: "Lookup",
|
|
44
|
+
rollup: "Rollup"
|
|
25
45
|
};
|
|
26
46
|
var OPTION_TONES = [
|
|
27
47
|
"bg-blue-bg text-blue-text",
|
|
@@ -36,7 +56,25 @@ function optionTone(option) {
|
|
|
36
56
|
for (const ch of option.toLowerCase()) hash = hash * 31 + ch.charCodeAt(0) >>> 0;
|
|
37
57
|
return OPTION_TONES[hash % OPTION_TONES.length];
|
|
38
58
|
}
|
|
39
|
-
var isEmpty = (value) => value === null || value === void 0 || typeof value === "string" && value.trim() === "";
|
|
59
|
+
var isEmpty = (value) => value === null || value === void 0 || typeof value === "string" && value.trim() === "" || Array.isArray(value) && value.length === 0;
|
|
60
|
+
function cellList(value) {
|
|
61
|
+
if (isEmpty(value)) return [];
|
|
62
|
+
return Array.isArray(value) ? value.filter((v) => !isEmpty(v)) : [value];
|
|
63
|
+
}
|
|
64
|
+
function cellIds(value) {
|
|
65
|
+
return cellList(value).filter((v) => typeof v === "string");
|
|
66
|
+
}
|
|
67
|
+
function titleOf(id, titles) {
|
|
68
|
+
return titles?.[id] ?? id;
|
|
69
|
+
}
|
|
70
|
+
function sameCellValue(a, b) {
|
|
71
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
72
|
+
const la = cellList(a);
|
|
73
|
+
const lb = cellList(b);
|
|
74
|
+
return la.length === lb.length && la.every((v, i) => v === lb[i]);
|
|
75
|
+
}
|
|
76
|
+
return (a ?? null) === (b ?? null);
|
|
77
|
+
}
|
|
40
78
|
function formatDateValue(value, locale) {
|
|
41
79
|
const day = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
42
80
|
if (day) {
|
|
@@ -49,15 +87,37 @@ function formatDateValue(value, locale) {
|
|
|
49
87
|
function formatNumberValue(value, locale) {
|
|
50
88
|
return value.toLocaleString(locale, { maximumFractionDigits: 6 });
|
|
51
89
|
}
|
|
52
|
-
|
|
90
|
+
var ISO_DATE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
91
|
+
function formatLookupValue(value, titles, locale) {
|
|
53
92
|
if (isEmpty(value)) return "";
|
|
54
|
-
if (
|
|
93
|
+
if (typeof value === "number") return formatNumberValue(value, locale);
|
|
94
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
95
|
+
if (typeof value === "string") {
|
|
96
|
+
if (titles?.[value] !== void 0) return titles[value];
|
|
97
|
+
if (ISO_DATE.test(value)) return formatDateValue(value, locale);
|
|
98
|
+
}
|
|
99
|
+
return String(value);
|
|
100
|
+
}
|
|
101
|
+
var NAMED_TYPES = /* @__PURE__ */ new Set([
|
|
102
|
+
"person",
|
|
103
|
+
"document",
|
|
104
|
+
"relation"
|
|
105
|
+
]);
|
|
106
|
+
function formatCell(value, type, locale, titles) {
|
|
107
|
+
if (isEmpty(value)) return "";
|
|
108
|
+
if ((type === "number" || type === "rollup") && typeof value === "number")
|
|
109
|
+
return formatNumberValue(value, locale);
|
|
55
110
|
if (type === "date" && typeof value === "string") return formatDateValue(value, locale);
|
|
56
111
|
if (type === "checkbox") return value ? "Yes" : "No";
|
|
112
|
+
if (NAMED_TYPES.has(type))
|
|
113
|
+
return cellIds(value).map((id) => titleOf(id, titles)).join(", ");
|
|
114
|
+
if (type === "lookup")
|
|
115
|
+
return cellList(value).map((v) => formatLookupValue(v, titles, locale)).join(", ");
|
|
57
116
|
return String(value);
|
|
58
117
|
}
|
|
59
118
|
function toApiValue(value, type) {
|
|
60
119
|
if (isEmpty(value)) return null;
|
|
120
|
+
if (LINK_COLUMN_TYPES.has(type)) return cellIds(value);
|
|
61
121
|
if (type === "checkbox") return Boolean(value);
|
|
62
122
|
if (type === "date" && value instanceof Date) {
|
|
63
123
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -69,12 +129,299 @@ function toApiValue(value, type) {
|
|
|
69
129
|
}
|
|
70
130
|
return value;
|
|
71
131
|
}
|
|
72
|
-
function compareCells(a, b, type) {
|
|
132
|
+
function compareCells(a, b, type, titlesA, titlesB) {
|
|
73
133
|
const ea = isEmpty(a);
|
|
74
134
|
const eb = isEmpty(b);
|
|
75
135
|
if (ea || eb) return ea === eb ? 0 : ea ? 1 : -1;
|
|
76
|
-
if (type === "number" || type === "checkbox") return Number(a) - Number(b);
|
|
77
|
-
|
|
136
|
+
if (type === "number" || type === "checkbox" || type === "rollup") return Number(a) - Number(b);
|
|
137
|
+
const shown = NAMED_TYPES.has(type) || type === "lookup";
|
|
138
|
+
const ta = shown ? formatCell(a, type, void 0, titlesA) : String(a);
|
|
139
|
+
const tb = shown ? formatCell(b, type, void 0, titlesB) : String(b);
|
|
140
|
+
return ta.localeCompare(tb, void 0, { sensitivity: "base", numeric: true });
|
|
141
|
+
}
|
|
142
|
+
var CHIP = "inline-flex h-[22px] min-w-0 max-w-[14rem] shrink-0 items-center gap-1 rounded-md border border-border bg-muted/50 px-1.5 text-xs text-foreground";
|
|
143
|
+
function initials(name) {
|
|
144
|
+
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
145
|
+
const letters = parts.length > 1 ? parts[0][0] + parts[parts.length - 1][0] : name.slice(0, 2);
|
|
146
|
+
return letters.toUpperCase();
|
|
147
|
+
}
|
|
148
|
+
function PersonChip({ name }) {
|
|
149
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5 text-sm", children: [
|
|
150
|
+
/* @__PURE__ */ jsx(
|
|
151
|
+
"span",
|
|
152
|
+
{
|
|
153
|
+
"aria-hidden": true,
|
|
154
|
+
className: "flex size-5 shrink-0 items-center justify-center rounded-full bg-muted text-[10px] font-medium text-muted-foreground",
|
|
155
|
+
children: initials(name)
|
|
156
|
+
}
|
|
157
|
+
),
|
|
158
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: name })
|
|
159
|
+
] });
|
|
160
|
+
}
|
|
161
|
+
function LinkChip({
|
|
162
|
+
label,
|
|
163
|
+
kind,
|
|
164
|
+
onOpen,
|
|
165
|
+
testId
|
|
166
|
+
}) {
|
|
167
|
+
const Icon = kind === "document" ? FileText : ArrowUpRight;
|
|
168
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
169
|
+
/* @__PURE__ */ jsx(Icon, { className: "size-3 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
170
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: label })
|
|
171
|
+
] });
|
|
172
|
+
if (!onOpen) {
|
|
173
|
+
return /* @__PURE__ */ jsx("span", { className: CHIP, title: label, "data-testid": testId, children: body });
|
|
174
|
+
}
|
|
175
|
+
const stop = (e) => e.stopPropagation();
|
|
176
|
+
return /* @__PURE__ */ jsx(
|
|
177
|
+
"button",
|
|
178
|
+
{
|
|
179
|
+
type: "button",
|
|
180
|
+
title: label,
|
|
181
|
+
"data-testid": testId,
|
|
182
|
+
onClick: (e) => {
|
|
183
|
+
e.stopPropagation();
|
|
184
|
+
onOpen();
|
|
185
|
+
},
|
|
186
|
+
onDoubleClick: stop,
|
|
187
|
+
onMouseDown: stop,
|
|
188
|
+
className: cn(CHIP, "hover:bg-muted hover:underline"),
|
|
189
|
+
children: body
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
function ChipRow({ children }) {
|
|
194
|
+
return /* @__PURE__ */ jsx("span", { className: "flex h-full min-w-0 items-center gap-1 overflow-hidden", children });
|
|
195
|
+
}
|
|
196
|
+
function PersonCell({ value, titles }) {
|
|
197
|
+
const [id] = cellIds(value);
|
|
198
|
+
return id ? /* @__PURE__ */ jsx(PersonChip, { name: titleOf(id, titles) }) : null;
|
|
199
|
+
}
|
|
200
|
+
function LinkChipsCell({
|
|
201
|
+
value,
|
|
202
|
+
titles,
|
|
203
|
+
kind,
|
|
204
|
+
onOpenDocument
|
|
205
|
+
}) {
|
|
206
|
+
const ids = cellIds(value);
|
|
207
|
+
if (ids.length === 0) return null;
|
|
208
|
+
return /* @__PURE__ */ jsx(ChipRow, { children: ids.map((id) => /* @__PURE__ */ jsx(
|
|
209
|
+
LinkChip,
|
|
210
|
+
{
|
|
211
|
+
label: titleOf(id, titles),
|
|
212
|
+
kind,
|
|
213
|
+
testId: `database-chip-${id}`,
|
|
214
|
+
onOpen: kind === "document" && onOpenDocument ? () => onOpenDocument(id) : void 0
|
|
215
|
+
},
|
|
216
|
+
id
|
|
217
|
+
)) });
|
|
218
|
+
}
|
|
219
|
+
function LookupCell({
|
|
220
|
+
value,
|
|
221
|
+
titles,
|
|
222
|
+
locale
|
|
223
|
+
}) {
|
|
224
|
+
const values = cellList(value);
|
|
225
|
+
if (values.length === 0) return null;
|
|
226
|
+
return /* @__PURE__ */ jsx(ChipRow, { children: values.map((v, i) => {
|
|
227
|
+
const text = formatLookupValue(v, titles, locale);
|
|
228
|
+
const key = `${i}-${text}`;
|
|
229
|
+
if (typeof v === "string" && /^doc-/.test(v))
|
|
230
|
+
return /* @__PURE__ */ jsx(LinkChip, { label: text, kind: "document" }, key);
|
|
231
|
+
if (typeof v === "string" && /^dbr-/.test(v))
|
|
232
|
+
return /* @__PURE__ */ jsx(LinkChip, { label: text, kind: "relation" }, key);
|
|
233
|
+
return /* @__PURE__ */ jsx(
|
|
234
|
+
"span",
|
|
235
|
+
{
|
|
236
|
+
title: text,
|
|
237
|
+
className: "inline-flex h-[22px] min-w-0 max-w-[14rem] shrink-0 items-center truncate rounded-md bg-muted/60 px-1.5 text-xs text-foreground",
|
|
238
|
+
children: text
|
|
239
|
+
},
|
|
240
|
+
key
|
|
241
|
+
);
|
|
242
|
+
}) });
|
|
243
|
+
}
|
|
244
|
+
var MAX_SHOWN = 50;
|
|
245
|
+
var SEARCH_DEBOUNCE_MS = 200;
|
|
246
|
+
var DEFAULT_PICK_CELL_EDITOR_LABELS = {
|
|
247
|
+
searchPlaceholder: "Search\u2026",
|
|
248
|
+
noMatches: "No matches",
|
|
249
|
+
searching: "Searching\u2026",
|
|
250
|
+
clear: "Clear",
|
|
251
|
+
done: "Done",
|
|
252
|
+
remove: (label) => `Remove ${label}`
|
|
253
|
+
};
|
|
254
|
+
function useChoices(query, options, search) {
|
|
255
|
+
const [found, setFound] = useState([]);
|
|
256
|
+
const [loading, setLoading] = useState(Boolean(search));
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
if (!search) return;
|
|
259
|
+
let live = true;
|
|
260
|
+
setLoading(true);
|
|
261
|
+
const t = setTimeout(
|
|
262
|
+
() => {
|
|
263
|
+
search(query.trim()).then((rows) => live && setFound(rows)).catch(() => live && setFound([])).finally(() => live && setLoading(false));
|
|
264
|
+
},
|
|
265
|
+
query ? SEARCH_DEBOUNCE_MS : 0
|
|
266
|
+
);
|
|
267
|
+
return () => {
|
|
268
|
+
live = false;
|
|
269
|
+
clearTimeout(t);
|
|
270
|
+
};
|
|
271
|
+
}, [query, search]);
|
|
272
|
+
const filtered = useMemo(() => {
|
|
273
|
+
if (search) return found.slice(0, MAX_SHOWN);
|
|
274
|
+
const q = query.trim().toLowerCase();
|
|
275
|
+
const all = options ?? [];
|
|
276
|
+
const hits = q ? all.filter(
|
|
277
|
+
(o) => o.label.toLowerCase().includes(q) || (o.description ?? "").toLowerCase().includes(q)
|
|
278
|
+
) : all;
|
|
279
|
+
return hits.slice(0, MAX_SHOWN);
|
|
280
|
+
}, [found, options, query, search]);
|
|
281
|
+
return { choices: filtered, loading: Boolean(search) && loading };
|
|
282
|
+
}
|
|
283
|
+
function PickCellEditor(props) {
|
|
284
|
+
const { value, onValueChange, stopEditing, data, kind, options, search } = props;
|
|
285
|
+
const l = { ...DEFAULT_PICK_CELL_EDITOR_LABELS, ...props.labels };
|
|
286
|
+
const multiple = kind !== "person";
|
|
287
|
+
const [chosen, setChosen] = useState(() => cellIds(value));
|
|
288
|
+
const [query, setQuery] = useState("");
|
|
289
|
+
const [active, setActive] = useState(0);
|
|
290
|
+
const input = useRef(null);
|
|
291
|
+
const { choices, loading } = useChoices(query, options, search);
|
|
292
|
+
const names = useRef({ ...data?.titles ?? {} });
|
|
293
|
+
for (const o of options ?? []) names.current[o.id] ??= o.label;
|
|
294
|
+
for (const o of choices) names.current[o.id] = o.label;
|
|
295
|
+
const nameOf = (id) => names.current[id] ?? id;
|
|
296
|
+
useEffect(() => input.current?.focus(), []);
|
|
297
|
+
useEffect(() => setActive(0), [query]);
|
|
298
|
+
const commit = (ids) => {
|
|
299
|
+
setChosen(ids);
|
|
300
|
+
if (data) {
|
|
301
|
+
const titles = { ...data.titles ?? {} };
|
|
302
|
+
for (const id of ids) titles[id] ??= nameOf(id);
|
|
303
|
+
data.titles = titles;
|
|
304
|
+
}
|
|
305
|
+
onValueChange(multiple ? ids : ids[0] ?? null);
|
|
306
|
+
};
|
|
307
|
+
const close = () => setTimeout(() => stopEditing(), 0);
|
|
308
|
+
const toggle = (id) => {
|
|
309
|
+
if (!multiple) {
|
|
310
|
+
commit([id]);
|
|
311
|
+
close();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
commit(chosen.includes(id) ? chosen.filter((x) => x !== id) : [...chosen, id]);
|
|
315
|
+
};
|
|
316
|
+
const onKeyDown = (e) => {
|
|
317
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
318
|
+
e.preventDefault();
|
|
319
|
+
e.stopPropagation();
|
|
320
|
+
if (choices.length === 0) return;
|
|
321
|
+
const step = e.key === "ArrowDown" ? 1 : -1;
|
|
322
|
+
setActive((i) => (i + step + choices.length) % choices.length);
|
|
323
|
+
} else if (e.key === "Enter") {
|
|
324
|
+
e.preventDefault();
|
|
325
|
+
e.stopPropagation();
|
|
326
|
+
const pick = choices[active];
|
|
327
|
+
if (pick) toggle(pick.id);
|
|
328
|
+
else if (multiple) close();
|
|
329
|
+
} else if (e.key === "Backspace" && multiple && !query && chosen.length > 0) {
|
|
330
|
+
commit(chosen.slice(0, -1));
|
|
331
|
+
} else if (e.key === "ArrowLeft" || e.key === "ArrowRight" || e.key === "Home" || e.key === "End") {
|
|
332
|
+
e.stopPropagation();
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
return /* @__PURE__ */ jsxs(
|
|
336
|
+
"div",
|
|
337
|
+
{
|
|
338
|
+
"data-testid": "database-pick-editor",
|
|
339
|
+
className: "flex w-72 flex-col rounded-md border border-border bg-popover shadow-md",
|
|
340
|
+
children: [
|
|
341
|
+
multiple && chosen.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 border-b border-border p-1.5", children: chosen.map((id) => /* @__PURE__ */ jsxs("span", { className: "inline-flex min-w-0 items-center", children: [
|
|
342
|
+
/* @__PURE__ */ jsx(LinkChip, { label: nameOf(id), kind: kind === "document" ? "document" : "relation" }),
|
|
343
|
+
/* @__PURE__ */ jsx(
|
|
344
|
+
"button",
|
|
345
|
+
{
|
|
346
|
+
type: "button",
|
|
347
|
+
"aria-label": l.remove(nameOf(id)),
|
|
348
|
+
onClick: () => commit(chosen.filter((x) => x !== id)),
|
|
349
|
+
"data-testid": `database-pick-remove-${id}`,
|
|
350
|
+
className: "rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
351
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3" })
|
|
352
|
+
}
|
|
353
|
+
)
|
|
354
|
+
] }, id)) }),
|
|
355
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 border-b border-border px-2", children: [
|
|
356
|
+
/* @__PURE__ */ jsx(Search, { className: "size-3.5 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
357
|
+
/* @__PURE__ */ jsx(
|
|
358
|
+
"input",
|
|
359
|
+
{
|
|
360
|
+
ref: input,
|
|
361
|
+
value: query,
|
|
362
|
+
placeholder: l.searchPlaceholder,
|
|
363
|
+
onChange: (e) => setQuery(e.target.value),
|
|
364
|
+
onKeyDown,
|
|
365
|
+
"data-testid": "database-pick-search",
|
|
366
|
+
className: "h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
367
|
+
}
|
|
368
|
+
),
|
|
369
|
+
loading && /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin text-muted-foreground" })
|
|
370
|
+
] }),
|
|
371
|
+
/* @__PURE__ */ jsxs("div", { role: "listbox", "aria-multiselectable": multiple, className: "max-h-64 overflow-y-auto p-1", children: [
|
|
372
|
+
choices.map((o, i) => {
|
|
373
|
+
const isChosen = chosen.includes(o.id);
|
|
374
|
+
return /* @__PURE__ */ jsxs(
|
|
375
|
+
"button",
|
|
376
|
+
{
|
|
377
|
+
type: "button",
|
|
378
|
+
role: "option",
|
|
379
|
+
"aria-selected": isChosen,
|
|
380
|
+
onMouseEnter: () => setActive(i),
|
|
381
|
+
onClick: () => toggle(o.id),
|
|
382
|
+
"data-testid": `database-pick-option-${o.id}`,
|
|
383
|
+
className: cn(
|
|
384
|
+
"flex w-full items-center gap-2 rounded px-2 py-1 text-left text-sm",
|
|
385
|
+
i === active && "bg-muted"
|
|
386
|
+
),
|
|
387
|
+
children: [
|
|
388
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
389
|
+
kind === "person" ? /* @__PURE__ */ jsx(PersonChip, { name: o.label }) : /* @__PURE__ */ jsx("span", { className: "truncate", children: o.label }),
|
|
390
|
+
o.description && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: o.description })
|
|
391
|
+
] }),
|
|
392
|
+
isChosen && /* @__PURE__ */ jsx(Check, { className: "size-3.5 shrink-0" })
|
|
393
|
+
]
|
|
394
|
+
},
|
|
395
|
+
o.id
|
|
396
|
+
);
|
|
397
|
+
}),
|
|
398
|
+
choices.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-2 py-1.5 text-sm text-muted-foreground", children: loading ? l.searching : l.noMatches })
|
|
399
|
+
] }),
|
|
400
|
+
(multiple || chosen.length > 0) && /* @__PURE__ */ jsx("div", { className: "flex justify-end border-t border-border p-1", children: !multiple ? /* @__PURE__ */ jsx(
|
|
401
|
+
"button",
|
|
402
|
+
{
|
|
403
|
+
type: "button",
|
|
404
|
+
onClick: () => {
|
|
405
|
+
commit([]);
|
|
406
|
+
close();
|
|
407
|
+
},
|
|
408
|
+
"data-testid": "database-pick-clear",
|
|
409
|
+
className: "rounded px-2 py-1 text-sm text-muted-foreground hover:bg-muted",
|
|
410
|
+
children: l.clear
|
|
411
|
+
}
|
|
412
|
+
) : /* @__PURE__ */ jsx(
|
|
413
|
+
"button",
|
|
414
|
+
{
|
|
415
|
+
type: "button",
|
|
416
|
+
onClick: close,
|
|
417
|
+
"data-testid": "database-pick-done",
|
|
418
|
+
className: "rounded px-2 py-1 text-sm font-medium hover:bg-muted",
|
|
419
|
+
children: l.done
|
|
420
|
+
}
|
|
421
|
+
) })
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
);
|
|
78
425
|
}
|
|
79
426
|
function SelectChip({ value }) {
|
|
80
427
|
return /* @__PURE__ */ jsx(
|
|
@@ -144,7 +491,8 @@ function SelectCellEditor(props) {
|
|
|
144
491
|
}
|
|
145
492
|
var DEFAULT_DATABASE_COLUMN_LABELS = {
|
|
146
493
|
addColumn: "Add a column",
|
|
147
|
-
columnSettings: (name) => `${name} column settings
|
|
494
|
+
columnSettings: (name) => `${name} column settings`,
|
|
495
|
+
computed: "Computed from a relation (read-only)"
|
|
148
496
|
};
|
|
149
497
|
var ADD_COLUMN_COL_ID = "__add_column";
|
|
150
498
|
var COLUMN_TYPE_ICONS = {
|
|
@@ -152,24 +500,36 @@ var COLUMN_TYPE_ICONS = {
|
|
|
152
500
|
number: Hash,
|
|
153
501
|
date: CalendarDays,
|
|
154
502
|
checkbox: CheckSquare,
|
|
155
|
-
select: ChevronDown
|
|
503
|
+
select: ChevronDown,
|
|
504
|
+
person: UserRound,
|
|
505
|
+
document: FileText,
|
|
506
|
+
relation: ArrowUpRight,
|
|
507
|
+
lookup: Search,
|
|
508
|
+
rollup: Sigma
|
|
156
509
|
};
|
|
157
510
|
var WIDTHS = {
|
|
158
511
|
text: 220,
|
|
159
512
|
number: 130,
|
|
160
513
|
date: 140,
|
|
161
514
|
checkbox: 110,
|
|
162
|
-
select: 160
|
|
515
|
+
select: 160,
|
|
516
|
+
person: 180,
|
|
517
|
+
document: 240,
|
|
518
|
+
relation: 240,
|
|
519
|
+
lookup: 200,
|
|
520
|
+
rollup: 130
|
|
163
521
|
};
|
|
164
522
|
function HeaderLabel({
|
|
165
523
|
column,
|
|
166
524
|
onEdit,
|
|
167
|
-
label
|
|
525
|
+
label,
|
|
526
|
+
computedLabel
|
|
168
527
|
}) {
|
|
169
528
|
const Icon = COLUMN_TYPE_ICONS[column.type];
|
|
170
529
|
return /* @__PURE__ */ jsxs("span", { className: "group/dbh flex min-w-0 items-center gap-1.5", children: [
|
|
171
530
|
/* @__PURE__ */ jsx(Icon, { className: "size-3.5 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
172
531
|
/* @__PURE__ */ jsx("span", { className: "truncate text-[12.5px] font-medium", children: column.name }),
|
|
532
|
+
isComputedColumn(column) && /* @__PURE__ */ jsx("span", { title: computedLabel, "data-testid": `database-column-computed-${column.id}`, children: /* @__PURE__ */ jsx(Lock, { className: "size-3 shrink-0 text-muted-foreground/70", "aria-label": computedLabel }) }),
|
|
173
533
|
onEdit && /* @__PURE__ */ jsx(
|
|
174
534
|
"button",
|
|
175
535
|
{
|
|
@@ -189,9 +549,22 @@ function HeaderLabel({
|
|
|
189
549
|
] });
|
|
190
550
|
}
|
|
191
551
|
function columnWidth(column) {
|
|
192
|
-
|
|
552
|
+
const header = Math.round(column.name.length * 8) + 76 + (isComputedColumn(column) ? 16 : 0);
|
|
553
|
+
return Math.min(320, Math.max(WIDTHS[column.type], header));
|
|
193
554
|
}
|
|
194
|
-
function
|
|
555
|
+
function canEdit(column, sources) {
|
|
556
|
+
if (isComputedColumn(column)) return false;
|
|
557
|
+
if (column.type === "person") return Boolean(sources?.members);
|
|
558
|
+
if (column.type === "document") return Boolean(sources?.documents);
|
|
559
|
+
if (column.type === "relation") return Boolean(sources?.searchRows && column.relation);
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
function editorFor(column, sources) {
|
|
563
|
+
const pick = (params) => ({
|
|
564
|
+
cellEditor: PickCellEditor,
|
|
565
|
+
cellEditorPopup: true,
|
|
566
|
+
cellEditorParams: params
|
|
567
|
+
});
|
|
195
568
|
switch (column.type) {
|
|
196
569
|
case "number":
|
|
197
570
|
return { cellEditor: "agNumberCellEditor", cellEditorParams: { showStepperButtons: false } };
|
|
@@ -205,11 +578,26 @@ function editorFor(column) {
|
|
|
205
578
|
cellEditorPopup: true,
|
|
206
579
|
cellEditorParams: { options: column.options ?? [] }
|
|
207
580
|
};
|
|
581
|
+
case "person":
|
|
582
|
+
return pick({ kind: "person", options: sources?.members ?? [] });
|
|
583
|
+
case "document":
|
|
584
|
+
return pick({ kind: "document", options: sources?.documents ?? [] });
|
|
585
|
+
case "relation": {
|
|
586
|
+
const target = column.relation?.database_id;
|
|
587
|
+
const searchRows = sources?.searchRows;
|
|
588
|
+
return pick({
|
|
589
|
+
kind: "relation",
|
|
590
|
+
search: target && searchRows ? (q) => searchRows(target, q) : void 0
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
case "lookup":
|
|
594
|
+
case "rollup":
|
|
595
|
+
return {};
|
|
208
596
|
default:
|
|
209
597
|
return { cellEditor: "agTextCellEditor" };
|
|
210
598
|
}
|
|
211
599
|
}
|
|
212
|
-
function rendererFor(column, locale) {
|
|
600
|
+
function rendererFor(column, locale, sources) {
|
|
213
601
|
switch (column.type) {
|
|
214
602
|
case "checkbox":
|
|
215
603
|
return {
|
|
@@ -225,16 +613,42 @@ function rendererFor(column, locale) {
|
|
|
225
613
|
cellClass: "cell-numeric",
|
|
226
614
|
valueFormatter: (p) => formatCell(p.value, "number", locale)
|
|
227
615
|
};
|
|
616
|
+
case "rollup":
|
|
617
|
+
return {
|
|
618
|
+
cellClass: "cell-numeric text-muted-foreground",
|
|
619
|
+
valueFormatter: (p) => formatCell(p.value, "rollup", locale)
|
|
620
|
+
};
|
|
228
621
|
case "date":
|
|
229
622
|
return {
|
|
230
623
|
valueFormatter: (p) => formatCell(p.value, "date", locale)
|
|
231
624
|
};
|
|
625
|
+
case "person":
|
|
626
|
+
return {
|
|
627
|
+
cellRenderer: (p) => /* @__PURE__ */ jsx(PersonCell, { value: p.value, titles: p.data?.titles })
|
|
628
|
+
};
|
|
629
|
+
case "document":
|
|
630
|
+
case "relation":
|
|
631
|
+
return {
|
|
632
|
+
cellRenderer: (p) => /* @__PURE__ */ jsx(
|
|
633
|
+
LinkChipsCell,
|
|
634
|
+
{
|
|
635
|
+
value: p.value,
|
|
636
|
+
titles: p.data?.titles,
|
|
637
|
+
kind: column.type === "document" ? "document" : "relation",
|
|
638
|
+
onOpenDocument: sources?.onOpenDocument
|
|
639
|
+
}
|
|
640
|
+
)
|
|
641
|
+
};
|
|
642
|
+
case "lookup":
|
|
643
|
+
return {
|
|
644
|
+
cellRenderer: (p) => /* @__PURE__ */ jsx(LookupCell, { value: p.value, titles: p.data?.titles, locale })
|
|
645
|
+
};
|
|
232
646
|
default:
|
|
233
647
|
return {};
|
|
234
648
|
}
|
|
235
649
|
}
|
|
236
650
|
function createDatabaseColumns(columns, options = {}) {
|
|
237
|
-
const { readOnly, onEditColumn, onAddColumn, locale } = options;
|
|
651
|
+
const { readOnly, onEditColumn, onAddColumn, locale, sources } = options;
|
|
238
652
|
const l = { ...DEFAULT_DATABASE_COLUMN_LABELS, ...options.labels };
|
|
239
653
|
const defs = columns.map((column) => ({
|
|
240
654
|
colId: column.id,
|
|
@@ -243,7 +657,7 @@ function createDatabaseColumns(columns, options = {}) {
|
|
|
243
657
|
minWidth: 90,
|
|
244
658
|
resizable: true,
|
|
245
659
|
sortable: true,
|
|
246
|
-
editable: !readOnly,
|
|
660
|
+
editable: !readOnly && canEdit(column, sources),
|
|
247
661
|
// The grid's own type inference must not second-guess the database's column type.
|
|
248
662
|
cellDataType: false,
|
|
249
663
|
headerComponentParams: {
|
|
@@ -252,6 +666,7 @@ function createDatabaseColumns(columns, options = {}) {
|
|
|
252
666
|
{
|
|
253
667
|
column,
|
|
254
668
|
label: l.columnSettings(column.name),
|
|
669
|
+
computedLabel: l.computed,
|
|
255
670
|
onEdit: !readOnly && onEditColumn ? () => onEditColumn(column) : void 0
|
|
256
671
|
}
|
|
257
672
|
)
|
|
@@ -263,14 +678,14 @@ function createDatabaseColumns(columns, options = {}) {
|
|
|
263
678
|
valueSetter: (p) => {
|
|
264
679
|
if (!p.data) return false;
|
|
265
680
|
const next = toApiValue(p.newValue, column.type);
|
|
266
|
-
if (next
|
|
681
|
+
if (sameCellValue(next, p.oldValue)) return false;
|
|
267
682
|
p.data.values = { ...p.data.values, [column.id]: next };
|
|
268
683
|
return true;
|
|
269
684
|
},
|
|
270
|
-
comparator: (a, b) => compareCells(a, b, column.type),
|
|
271
|
-
getQuickFilterText: (p) => formatCell(p.value, column.type, locale),
|
|
272
|
-
...editorFor(column),
|
|
273
|
-
...rendererFor(column, locale)
|
|
685
|
+
comparator: (a, b, nodeA, nodeB) => compareCells(a, b, column.type, nodeA?.data?.titles, nodeB?.data?.titles),
|
|
686
|
+
getQuickFilterText: (p) => formatCell(p.value, column.type, locale, p.data?.titles),
|
|
687
|
+
...editorFor(column, sources),
|
|
688
|
+
...rendererFor(column, locale, sources)
|
|
274
689
|
}));
|
|
275
690
|
if (!readOnly && onAddColumn) {
|
|
276
691
|
defs.push({
|
|
@@ -302,6 +717,225 @@ function createDatabaseColumns(columns, options = {}) {
|
|
|
302
717
|
}
|
|
303
718
|
return defs;
|
|
304
719
|
}
|
|
720
|
+
var DEFAULT_COLUMN_CONFIG_LABELS = {
|
|
721
|
+
relatedDatabase: "Related database",
|
|
722
|
+
pickDatabase: "Pick a database",
|
|
723
|
+
noDatabases: "There is no database to link to yet.",
|
|
724
|
+
targetFixed: "The related database can\u2019t be changed once the column has been added.",
|
|
725
|
+
thisDatabase: "this database",
|
|
726
|
+
showOnTarget: (target) => `Show on ${target}`,
|
|
727
|
+
showOnTargetHint: "Adds a column to the related database that lists the links back.",
|
|
728
|
+
pairName: "Name of the column there",
|
|
729
|
+
pairNamePlaceholder: (name) => name,
|
|
730
|
+
pairedWith: (target) => `Shown on ${target} too (two-way).`,
|
|
731
|
+
oneWay: "One-way: only this database shows the links.",
|
|
732
|
+
relation: "Relation",
|
|
733
|
+
pickRelation: "Pick a relation column",
|
|
734
|
+
noRelations: "Add a relation column first: lookups and rollups read through one.",
|
|
735
|
+
lookupColumn: "Column to show",
|
|
736
|
+
pickColumn: "Pick a column",
|
|
737
|
+
noColumns: "The related database has no column to show.",
|
|
738
|
+
rollupOp: "Calculate",
|
|
739
|
+
rollupColumn: "Of the number column",
|
|
740
|
+
noNumberColumns: "The related database has no number column.",
|
|
741
|
+
ops: { count: "Count", sum: "Sum", avg: "Average", min: "Min", max: "Max" }
|
|
742
|
+
};
|
|
743
|
+
function Field({ label, children, hint }) {
|
|
744
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
745
|
+
/* @__PURE__ */ jsx(Label, { children: label }),
|
|
746
|
+
children,
|
|
747
|
+
hint && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: hint })
|
|
748
|
+
] });
|
|
749
|
+
}
|
|
750
|
+
function Hint({ children, testId }) {
|
|
751
|
+
return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", "data-testid": testId, children });
|
|
752
|
+
}
|
|
753
|
+
function ColumnSelect({
|
|
754
|
+
value,
|
|
755
|
+
onChange,
|
|
756
|
+
columns,
|
|
757
|
+
placeholder,
|
|
758
|
+
testId
|
|
759
|
+
}) {
|
|
760
|
+
return /* @__PURE__ */ jsxs(Select, { value: value || void 0, onValueChange: onChange, children: [
|
|
761
|
+
/* @__PURE__ */ jsx(SelectTrigger, { "data-testid": testId, children: /* @__PURE__ */ jsx(SelectValue, { placeholder }) }),
|
|
762
|
+
/* @__PURE__ */ jsx(SelectContent, { children: columns.map((c) => {
|
|
763
|
+
const Icon = COLUMN_TYPE_ICONS[c.type];
|
|
764
|
+
return /* @__PURE__ */ jsx(SelectItem, { value: c.id, "data-testid": `${testId}-${c.id}`, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
765
|
+
/* @__PURE__ */ jsx(Icon, { className: "size-4 text-muted-foreground" }),
|
|
766
|
+
c.name
|
|
767
|
+
] }) }, c.id);
|
|
768
|
+
}) })
|
|
769
|
+
] });
|
|
770
|
+
}
|
|
771
|
+
function RelationFields({
|
|
772
|
+
draft,
|
|
773
|
+
onChange,
|
|
774
|
+
databases,
|
|
775
|
+
databaseId,
|
|
776
|
+
databaseName,
|
|
777
|
+
editing,
|
|
778
|
+
column,
|
|
779
|
+
labels
|
|
780
|
+
}) {
|
|
781
|
+
const nameOf = (id) => databases.find((d) => d.id === id)?.name ?? (id === databaseId ? labels.thisDatabase : id);
|
|
782
|
+
if (editing && column?.relation) {
|
|
783
|
+
const target2 = nameOf(column.relation.database_id);
|
|
784
|
+
return /* @__PURE__ */ jsxs(Field, { label: labels.relatedDatabase, hint: labels.targetFixed, children: [
|
|
785
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm", "data-testid": "database-column-relation-target", children: target2 }),
|
|
786
|
+
/* @__PURE__ */ jsx(Hint, { children: column.relation.two_way ? labels.pairedWith(target2) : labels.oneWay })
|
|
787
|
+
] });
|
|
788
|
+
}
|
|
789
|
+
if (databases.length === 0) return /* @__PURE__ */ jsx(Hint, { children: labels.noDatabases });
|
|
790
|
+
const target = draft.database_id ? nameOf(draft.database_id) : "";
|
|
791
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
792
|
+
/* @__PURE__ */ jsx(Field, { label: labels.relatedDatabase, children: /* @__PURE__ */ jsxs(
|
|
793
|
+
Select,
|
|
794
|
+
{
|
|
795
|
+
value: draft.database_id || void 0,
|
|
796
|
+
onValueChange: (v) => onChange({ ...draft, database_id: v }),
|
|
797
|
+
children: [
|
|
798
|
+
/* @__PURE__ */ jsx(SelectTrigger, { "data-testid": "database-column-relation-database", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: labels.pickDatabase }) }),
|
|
799
|
+
/* @__PURE__ */ jsx(SelectContent, { children: databases.map((d) => /* @__PURE__ */ jsx(
|
|
800
|
+
SelectItem,
|
|
801
|
+
{
|
|
802
|
+
value: d.id,
|
|
803
|
+
"data-testid": `database-column-relation-database-${d.id}`,
|
|
804
|
+
children: d.id === databaseId ? `${d.name} (${labels.thisDatabase})` : d.name
|
|
805
|
+
},
|
|
806
|
+
d.id
|
|
807
|
+
)) })
|
|
808
|
+
]
|
|
809
|
+
}
|
|
810
|
+
) }),
|
|
811
|
+
draft.database_id && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-border p-3", children: [
|
|
812
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center justify-between gap-3 text-sm", children: [
|
|
813
|
+
/* @__PURE__ */ jsxs("span", { className: "flex flex-col", children: [
|
|
814
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: labels.showOnTarget(target) }),
|
|
815
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: labels.showOnTargetHint })
|
|
816
|
+
] }),
|
|
817
|
+
/* @__PURE__ */ jsx(
|
|
818
|
+
Switch,
|
|
819
|
+
{
|
|
820
|
+
checked: draft.two_way,
|
|
821
|
+
onCheckedChange: (two_way) => onChange({ ...draft, two_way }),
|
|
822
|
+
"data-testid": "database-column-relation-two-way"
|
|
823
|
+
}
|
|
824
|
+
)
|
|
825
|
+
] }),
|
|
826
|
+
draft.two_way && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
827
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "database-column-pair-name", children: labels.pairName }),
|
|
828
|
+
/* @__PURE__ */ jsx(
|
|
829
|
+
Input,
|
|
830
|
+
{
|
|
831
|
+
id: "database-column-pair-name",
|
|
832
|
+
value: draft.pair_name,
|
|
833
|
+
maxLength: 200,
|
|
834
|
+
placeholder: labels.pairNamePlaceholder(databaseName ?? ""),
|
|
835
|
+
onChange: (e) => onChange({ ...draft, pair_name: e.target.value }),
|
|
836
|
+
"data-testid": "database-column-pair-name"
|
|
837
|
+
}
|
|
838
|
+
)
|
|
839
|
+
] })
|
|
840
|
+
] })
|
|
841
|
+
] });
|
|
842
|
+
}
|
|
843
|
+
function relationColumnsOf(columns) {
|
|
844
|
+
return columns.filter((c) => c.type === "relation" && c.relation);
|
|
845
|
+
}
|
|
846
|
+
function targetColumnsOf(relationId, columns, databases, databaseId) {
|
|
847
|
+
const relation = columns.find((c) => c.id === relationId)?.relation;
|
|
848
|
+
if (!relation) return [];
|
|
849
|
+
if (relation.database_id === databaseId) return columns;
|
|
850
|
+
return databases.find((d) => d.id === relation.database_id)?.columns ?? [];
|
|
851
|
+
}
|
|
852
|
+
function LookupFields({
|
|
853
|
+
draft,
|
|
854
|
+
onChange,
|
|
855
|
+
columns,
|
|
856
|
+
databases,
|
|
857
|
+
databaseId,
|
|
858
|
+
labels
|
|
859
|
+
}) {
|
|
860
|
+
const relations = relationColumnsOf(columns);
|
|
861
|
+
if (relations.length === 0)
|
|
862
|
+
return /* @__PURE__ */ jsx(Hint, { testId: "database-column-no-relations", children: labels.noRelations });
|
|
863
|
+
const shown = targetColumnsOf(draft.relation, columns, databases, databaseId).filter(
|
|
864
|
+
(c) => !isComputedColumn(c)
|
|
865
|
+
);
|
|
866
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
867
|
+
/* @__PURE__ */ jsx(Field, { label: labels.relation, children: /* @__PURE__ */ jsx(
|
|
868
|
+
ColumnSelect,
|
|
869
|
+
{
|
|
870
|
+
value: draft.relation,
|
|
871
|
+
onChange: (relation) => onChange({ relation, column: "" }),
|
|
872
|
+
columns: relations,
|
|
873
|
+
placeholder: labels.pickRelation,
|
|
874
|
+
testId: "database-column-lookup-relation"
|
|
875
|
+
}
|
|
876
|
+
) }),
|
|
877
|
+
draft.relation && /* @__PURE__ */ jsx(Field, { label: labels.lookupColumn, children: shown.length ? /* @__PURE__ */ jsx(
|
|
878
|
+
ColumnSelect,
|
|
879
|
+
{
|
|
880
|
+
value: draft.column,
|
|
881
|
+
onChange: (column) => onChange({ ...draft, column }),
|
|
882
|
+
columns: shown,
|
|
883
|
+
placeholder: labels.pickColumn,
|
|
884
|
+
testId: "database-column-lookup-column"
|
|
885
|
+
}
|
|
886
|
+
) : /* @__PURE__ */ jsx(Hint, { children: labels.noColumns }) })
|
|
887
|
+
] });
|
|
888
|
+
}
|
|
889
|
+
function RollupFields({
|
|
890
|
+
draft,
|
|
891
|
+
onChange,
|
|
892
|
+
columns,
|
|
893
|
+
databases,
|
|
894
|
+
databaseId,
|
|
895
|
+
labels
|
|
896
|
+
}) {
|
|
897
|
+
const relations = relationColumnsOf(columns);
|
|
898
|
+
if (relations.length === 0)
|
|
899
|
+
return /* @__PURE__ */ jsx(Hint, { testId: "database-column-no-relations", children: labels.noRelations });
|
|
900
|
+
const numbers = targetColumnsOf(draft.relation, columns, databases, databaseId).filter(
|
|
901
|
+
(c) => c.type === "number"
|
|
902
|
+
);
|
|
903
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
904
|
+
/* @__PURE__ */ jsx(Field, { label: labels.relation, children: /* @__PURE__ */ jsx(
|
|
905
|
+
ColumnSelect,
|
|
906
|
+
{
|
|
907
|
+
value: draft.relation,
|
|
908
|
+
onChange: (relation) => onChange({ ...draft, relation, column: "" }),
|
|
909
|
+
columns: relations,
|
|
910
|
+
placeholder: labels.pickRelation,
|
|
911
|
+
testId: "database-column-rollup-relation"
|
|
912
|
+
}
|
|
913
|
+
) }),
|
|
914
|
+
draft.relation && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[8rem_1fr] gap-2", children: [
|
|
915
|
+
/* @__PURE__ */ jsx(Field, { label: labels.rollupOp, children: /* @__PURE__ */ jsxs(
|
|
916
|
+
Select,
|
|
917
|
+
{
|
|
918
|
+
value: draft.op,
|
|
919
|
+
onValueChange: (op) => onChange({ ...draft, op }),
|
|
920
|
+
children: [
|
|
921
|
+
/* @__PURE__ */ jsx(SelectTrigger, { "data-testid": "database-column-rollup-op", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
922
|
+
/* @__PURE__ */ jsx(SelectContent, { children: DATABASE_ROLLUP_OPS.map((op) => /* @__PURE__ */ jsx(SelectItem, { value: op, "data-testid": `database-column-rollup-op-${op}`, children: labels.ops[op] }, op)) })
|
|
923
|
+
]
|
|
924
|
+
}
|
|
925
|
+
) }),
|
|
926
|
+
draft.op !== "count" && /* @__PURE__ */ jsx(Field, { label: labels.rollupColumn, children: numbers.length ? /* @__PURE__ */ jsx(
|
|
927
|
+
ColumnSelect,
|
|
928
|
+
{
|
|
929
|
+
value: draft.column,
|
|
930
|
+
onChange: (column) => onChange({ ...draft, column }),
|
|
931
|
+
columns: numbers,
|
|
932
|
+
placeholder: labels.pickColumn,
|
|
933
|
+
testId: "database-column-rollup-column"
|
|
934
|
+
}
|
|
935
|
+
) : /* @__PURE__ */ jsx(Hint, { children: labels.noNumberColumns }) })
|
|
936
|
+
] })
|
|
937
|
+
] });
|
|
938
|
+
}
|
|
305
939
|
var escapeHtml = (text) => text.replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
|
|
306
940
|
var DEFAULT_DATABASE_TABLE_LABELS = {
|
|
307
941
|
newRow: "New row",
|
|
@@ -318,6 +952,7 @@ function DatabaseTable({
|
|
|
318
952
|
onAddRow,
|
|
319
953
|
onAddColumn,
|
|
320
954
|
onEditColumn,
|
|
955
|
+
sources,
|
|
321
956
|
onSelectionChange,
|
|
322
957
|
selected,
|
|
323
958
|
readOnly = false,
|
|
@@ -337,10 +972,11 @@ function DatabaseTable({
|
|
|
337
972
|
readOnly,
|
|
338
973
|
onEditColumn,
|
|
339
974
|
onAddColumn,
|
|
975
|
+
sources,
|
|
340
976
|
locale,
|
|
341
977
|
labels: columnLabels
|
|
342
978
|
}),
|
|
343
|
-
[columns, readOnly, onEditColumn, onAddColumn, locale, columnLabels]
|
|
979
|
+
[columns, readOnly, onEditColumn, onAddColumn, sources, locale, columnLabels]
|
|
344
980
|
);
|
|
345
981
|
const noColumns = columns.length === 0;
|
|
346
982
|
const emptyState = {
|
|
@@ -660,25 +1296,45 @@ var DEFAULT_DATABASE_COLUMN_DIALOG_LABELS = {
|
|
|
660
1296
|
delete: "Delete column",
|
|
661
1297
|
confirmDelete: "Delete this column and every value in it?"
|
|
662
1298
|
};
|
|
1299
|
+
var NO_RELATION = { database_id: "", two_way: false, pair_name: "" };
|
|
1300
|
+
var NO_LOOKUP = { relation: "", column: "" };
|
|
1301
|
+
var NO_ROLLUP = { relation: "", column: "", op: "count" };
|
|
1302
|
+
var NO_COLUMNS = [];
|
|
1303
|
+
var NO_DATABASES = [];
|
|
1304
|
+
function configComplete(type, editing, relation, lookup, rollup) {
|
|
1305
|
+
if (type === "relation") return editing || Boolean(relation.database_id);
|
|
1306
|
+
if (type === "lookup") return Boolean(lookup.relation && lookup.column);
|
|
1307
|
+
if (type === "rollup") return Boolean(rollup.relation && (rollup.op === "count" || rollup.column));
|
|
1308
|
+
return true;
|
|
1309
|
+
}
|
|
663
1310
|
function DatabaseColumnDialog({
|
|
664
1311
|
open,
|
|
665
1312
|
onOpenChange,
|
|
666
1313
|
column,
|
|
1314
|
+
databaseId,
|
|
1315
|
+
databaseName,
|
|
1316
|
+
columns = NO_COLUMNS,
|
|
1317
|
+
databases = NO_DATABASES,
|
|
667
1318
|
onSave,
|
|
668
1319
|
onDelete,
|
|
669
1320
|
isSaving = false,
|
|
670
1321
|
error,
|
|
671
1322
|
labels,
|
|
672
|
-
typeLabels
|
|
1323
|
+
typeLabels,
|
|
1324
|
+
configLabels
|
|
673
1325
|
}) {
|
|
674
1326
|
const l = { ...DEFAULT_DATABASE_COLUMN_DIALOG_LABELS, ...labels };
|
|
675
1327
|
const tl = { ...DEFAULT_DATABASE_COLUMN_TYPE_LABELS, ...typeLabels };
|
|
1328
|
+
const cl = { ...DEFAULT_COLUMN_CONFIG_LABELS, ...configLabels };
|
|
676
1329
|
const editing = Boolean(column);
|
|
677
1330
|
const [name, setName] = useState("");
|
|
678
1331
|
const [type, setType] = useState("text");
|
|
679
1332
|
const [options, setOptions] = useState([]);
|
|
680
1333
|
const [optionDraft, setOptionDraft] = useState("");
|
|
681
1334
|
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
|
1335
|
+
const [relation, setRelation] = useState(NO_RELATION);
|
|
1336
|
+
const [lookup, setLookup] = useState(NO_LOOKUP);
|
|
1337
|
+
const [rollup, setRollup] = useState(NO_ROLLUP);
|
|
682
1338
|
useEffect(() => {
|
|
683
1339
|
if (!open) return;
|
|
684
1340
|
setName(column?.name ?? "");
|
|
@@ -686,6 +1342,15 @@ function DatabaseColumnDialog({
|
|
|
686
1342
|
setOptions(column?.options ?? []);
|
|
687
1343
|
setOptionDraft("");
|
|
688
1344
|
setConfirmingDelete(false);
|
|
1345
|
+
setRelation(NO_RELATION);
|
|
1346
|
+
setLookup(column?.lookup ? { ...column.lookup } : NO_LOOKUP);
|
|
1347
|
+
setRollup(
|
|
1348
|
+
column?.rollup ? {
|
|
1349
|
+
relation: column.rollup.relation,
|
|
1350
|
+
column: column.rollup.column ?? "",
|
|
1351
|
+
op: column.rollup.op ?? "count"
|
|
1352
|
+
} : NO_ROLLUP
|
|
1353
|
+
);
|
|
689
1354
|
}, [open, column]);
|
|
690
1355
|
const addOption = () => {
|
|
691
1356
|
const option = optionDraft.trim();
|
|
@@ -696,15 +1361,29 @@ function DatabaseColumnDialog({
|
|
|
696
1361
|
};
|
|
697
1362
|
const pendingOptions = optionDraft.trim() ? [...options, optionDraft.trim()] : options;
|
|
698
1363
|
const missingOptions = type === "select" && pendingOptions.length === 0;
|
|
699
|
-
const canSave = name.trim().length > 0 && !missingOptions && !isSaving;
|
|
1364
|
+
const canSave = name.trim().length > 0 && !missingOptions && configComplete(type, editing, relation, lookup, rollup) && !isSaving;
|
|
700
1365
|
const submit = (e) => {
|
|
701
1366
|
e.preventDefault();
|
|
702
1367
|
if (!canSave) return;
|
|
703
|
-
|
|
1368
|
+
const draft = {
|
|
704
1369
|
name: name.trim(),
|
|
705
1370
|
type,
|
|
706
1371
|
options: type === "select" ? Array.from(new Set(pendingOptions)) : null
|
|
707
|
-
}
|
|
1372
|
+
};
|
|
1373
|
+
if (type === "relation" && !editing)
|
|
1374
|
+
draft.relation = {
|
|
1375
|
+
database_id: relation.database_id,
|
|
1376
|
+
two_way: relation.two_way,
|
|
1377
|
+
pair_name: relation.two_way ? relation.pair_name.trim() || null : null
|
|
1378
|
+
};
|
|
1379
|
+
if (type === "lookup") draft.lookup = { ...lookup };
|
|
1380
|
+
if (type === "rollup")
|
|
1381
|
+
draft.rollup = {
|
|
1382
|
+
relation: rollup.relation,
|
|
1383
|
+
op: rollup.op,
|
|
1384
|
+
column: rollup.op === "count" ? null : rollup.column
|
|
1385
|
+
};
|
|
1386
|
+
onSave(draft);
|
|
708
1387
|
};
|
|
709
1388
|
const onOptionKey = (e) => {
|
|
710
1389
|
if (e.key === "Enter") {
|
|
@@ -795,6 +1474,41 @@ function DatabaseColumnDialog({
|
|
|
795
1474
|
] }),
|
|
796
1475
|
missingOptions && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: l.needsOptions })
|
|
797
1476
|
] }),
|
|
1477
|
+
type === "relation" && /* @__PURE__ */ jsx(
|
|
1478
|
+
RelationFields,
|
|
1479
|
+
{
|
|
1480
|
+
draft: relation,
|
|
1481
|
+
onChange: setRelation,
|
|
1482
|
+
databases,
|
|
1483
|
+
databaseId,
|
|
1484
|
+
databaseName,
|
|
1485
|
+
editing,
|
|
1486
|
+
column,
|
|
1487
|
+
labels: cl
|
|
1488
|
+
}
|
|
1489
|
+
),
|
|
1490
|
+
type === "lookup" && /* @__PURE__ */ jsx(
|
|
1491
|
+
LookupFields,
|
|
1492
|
+
{
|
|
1493
|
+
draft: lookup,
|
|
1494
|
+
onChange: setLookup,
|
|
1495
|
+
columns,
|
|
1496
|
+
databases,
|
|
1497
|
+
databaseId,
|
|
1498
|
+
labels: cl
|
|
1499
|
+
}
|
|
1500
|
+
),
|
|
1501
|
+
type === "rollup" && /* @__PURE__ */ jsx(
|
|
1502
|
+
RollupFields,
|
|
1503
|
+
{
|
|
1504
|
+
draft: rollup,
|
|
1505
|
+
onChange: setRollup,
|
|
1506
|
+
columns,
|
|
1507
|
+
databases,
|
|
1508
|
+
databaseId,
|
|
1509
|
+
labels: cl
|
|
1510
|
+
}
|
|
1511
|
+
),
|
|
798
1512
|
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-red-text", "data-testid": "database-column-error", children: error }),
|
|
799
1513
|
/* @__PURE__ */ jsxs(DialogFooter, { className: "flex-row items-center gap-2 sm:justify-between", children: [
|
|
800
1514
|
editing && onDelete ? confirmingDelete ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm", children: [
|
|
@@ -1050,6 +1764,6 @@ function NewDatabaseDialog({
|
|
|
1050
1764
|
] }) }) });
|
|
1051
1765
|
}
|
|
1052
1766
|
|
|
1053
|
-
export { ADD_COLUMN_COL_ID, COLUMN_TYPE_ICONS, DATABASE_COLUMN_TYPES, DEFAULT_DATABASE_COLUMN_DIALOG_LABELS, DEFAULT_DATABASE_COLUMN_LABELS, DEFAULT_DATABASE_COLUMN_TYPE_LABELS, DEFAULT_DATABASE_LIST_LABELS, DEFAULT_DATABASE_TABLE_LABELS, DEFAULT_DATABASE_TOOLBAR_LABELS, DEFAULT_NEW_DATABASE_DIALOG_LABELS, DatabaseColumnDialog, DatabaseList, DatabaseTable, DatabaseToolbar, NewDatabaseDialog, SelectCellEditor, SelectChip, columnWidth, compareCells, createDatabaseColumns, formatCell, formatDateValue, formatNumberValue, optionTone, toApiValue };
|
|
1767
|
+
export { ADD_COLUMN_COL_ID, COLUMN_TYPE_ICONS, COMPUTED_COLUMN_TYPES, DATABASE_COLUMN_TYPES, DATABASE_ROLLUP_OPS, DEFAULT_COLUMN_CONFIG_LABELS, DEFAULT_DATABASE_COLUMN_DIALOG_LABELS, DEFAULT_DATABASE_COLUMN_LABELS, DEFAULT_DATABASE_COLUMN_TYPE_LABELS, DEFAULT_DATABASE_LIST_LABELS, DEFAULT_DATABASE_TABLE_LABELS, DEFAULT_DATABASE_TOOLBAR_LABELS, DEFAULT_NEW_DATABASE_DIALOG_LABELS, DEFAULT_PICK_CELL_EDITOR_LABELS, DatabaseColumnDialog, DatabaseList, DatabaseTable, DatabaseToolbar, LINK_COLUMN_TYPES, LinkChip, LinkChipsCell, LookupCell, NewDatabaseDialog, PersonCell, PersonChip, PickCellEditor, SelectCellEditor, SelectChip, cellIds, cellList, columnWidth, compareCells, createDatabaseColumns, formatCell, formatDateValue, formatLookupValue, formatNumberValue, isComputedColumn, optionTone, sameCellValue, titleOf, toApiValue };
|
|
1054
1768
|
//# sourceMappingURL=index.js.map
|
|
1055
1769
|
//# sourceMappingURL=index.js.map
|