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