@pythia-software/query-table-ui 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -1
- package/dist/FormulaEditor-YCPICDIH.js +251 -0
- package/dist/FormulaEditor-YCPICDIH.js.map +1 -0
- package/dist/index.d.ts +18 -4
- package/dist/index.js +1853 -509
- package/dist/index.js.map +1 -1
- package/dist/theme.css +1084 -4
- package/dist/theme.css.map +1 -1
- package/package.json +13 -5
package/dist/index.js
CHANGED
|
@@ -137,16 +137,18 @@ function formatRelative(iso) {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// src/DataTable.tsx
|
|
140
|
-
import {
|
|
140
|
+
import { isComputedCellError } from "@pythia-software/query-table-core";
|
|
141
|
+
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
142
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
141
143
|
import { isSortable, readFieldValue } from "@pythia-software/query-table-core";
|
|
142
144
|
import { useColumnDrag } from "@pythia-software/query-table-react";
|
|
143
145
|
|
|
144
146
|
// src/CellMenu.tsx
|
|
145
147
|
import { useEffect } from "react";
|
|
146
|
-
import { NULLARY_OPS, opsForField } from "@pythia-software/query-table-core";
|
|
148
|
+
import { NULLARY_OPS, negateClause, opsForField, opPairsForField, isFilterable } from "@pythia-software/query-table-core";
|
|
147
149
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
148
150
|
var cx = (...parts) => parts.filter(Boolean).join(" ");
|
|
149
|
-
var MENU_WIDTH =
|
|
151
|
+
var MENU_WIDTH = 320;
|
|
150
152
|
var ROW_HEIGHT = 32;
|
|
151
153
|
var POINTER_OFFSET = 4;
|
|
152
154
|
function CellMenu({ field, value, x, y, onAddFilter, onClose, classNames }) {
|
|
@@ -165,73 +167,36 @@ function CellMenu({ field, value, x, y, onAddFilter, onClose, classNames }) {
|
|
|
165
167
|
clearTimeout(t);
|
|
166
168
|
};
|
|
167
169
|
}, [onClose]);
|
|
168
|
-
const ops = opsForField(field);
|
|
170
|
+
const ops = isFilterable(field) ? opsForField(field) : [];
|
|
171
|
+
const pairs = isFilterable(field) ? opPairsForField(field) : [];
|
|
172
|
+
const hasNullity = pairs.some((p) => p.keep.op === "is_null");
|
|
169
173
|
const isArray = Array.isArray(value);
|
|
170
174
|
const isNullish = value == null || value === "" || isArray && value.length === 0;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
onAddFilter(
|
|
175
|
+
const cellText = valueToString(value);
|
|
176
|
+
function applyClause(clause) {
|
|
177
|
+
onAddFilter(clause);
|
|
174
178
|
onClose();
|
|
175
179
|
}
|
|
176
|
-
const
|
|
180
|
+
const rows = [];
|
|
181
|
+
const pairRow = (op, raw) => {
|
|
182
|
+
const positive = { field: field.name, op, value: NULLARY_OPS.has(op) ? "" : raw };
|
|
183
|
+
rows.push({ positive, negative: negateClause(positive, ops) });
|
|
184
|
+
};
|
|
177
185
|
if (isArray && !isNullish) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
" ",
|
|
186
|
-
/* @__PURE__ */ jsx("code", { children: s })
|
|
187
|
-
] }, `el-${i}`)
|
|
188
|
-
);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
for (const op of ops) {
|
|
192
|
-
const nullary = NULLARY_OPS.has(op);
|
|
193
|
-
if (isNullish && !nullary) continue;
|
|
194
|
-
if (isArray && !nullary && !isNullish) continue;
|
|
195
|
-
filterItems.push(
|
|
196
|
-
/* @__PURE__ */ jsxs(MenuItem, { classNames, onClick: () => apply(op), children: [
|
|
197
|
-
"filter: ",
|
|
198
|
-
opSymbol(op),
|
|
199
|
-
!nullary && !isNullish ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
200
|
-
" ",
|
|
201
|
-
/* @__PURE__ */ jsx("code", { children: previewValue(value) })
|
|
202
|
-
] }) : null
|
|
203
|
-
] }, op)
|
|
204
|
-
);
|
|
186
|
+
const arrayOp = pairs.find((p) => p.keep.op === "includes")?.keep.op ?? pairs[0]?.keep.op ?? "=";
|
|
187
|
+
value.forEach((el) => pairRow(arrayOp, String(el)));
|
|
188
|
+
if (hasNullity) pairRow("is_null", cellText);
|
|
189
|
+
} else if (!isNullish) {
|
|
190
|
+
for (const pair of pairs) pairRow(pair.keep.op, cellText);
|
|
191
|
+
} else if (hasNullity) {
|
|
192
|
+
pairRow("is_null", cellText);
|
|
205
193
|
}
|
|
206
|
-
const copyItem = /* @__PURE__ */ jsx(
|
|
207
|
-
MenuItem,
|
|
208
|
-
{
|
|
209
|
-
classNames,
|
|
210
|
-
onClick: async () => {
|
|
211
|
-
try {
|
|
212
|
-
await navigator.clipboard.writeText(valueToString(value));
|
|
213
|
-
} catch {
|
|
214
|
-
}
|
|
215
|
-
onClose();
|
|
216
|
-
},
|
|
217
|
-
children: "copy value"
|
|
218
|
-
},
|
|
219
|
-
"copy"
|
|
220
|
-
);
|
|
221
|
-
const items = [
|
|
222
|
-
/* @__PURE__ */ jsxs("div", { className: "qt-cm-header", children: [
|
|
223
|
-
/* @__PURE__ */ jsx("span", { className: "qt-cm-field", children: field.label }),
|
|
224
|
-
/* @__PURE__ */ jsx("span", { className: "qt-cm-value", children: previewValue(value) })
|
|
225
|
-
] }, "header"),
|
|
226
|
-
copyItem,
|
|
227
|
-
/* @__PURE__ */ jsx("div", { className: cx("qt-cm-sep", classNames?.separator) }, "sep"),
|
|
228
|
-
...filterItems
|
|
229
|
-
];
|
|
230
194
|
const vw = typeof window !== "undefined" ? window.innerWidth : 9999;
|
|
231
195
|
const vh = typeof window !== "undefined" ? window.innerHeight : 9999;
|
|
196
|
+
const approxRows = 3 + rows.length;
|
|
232
197
|
const left = Math.max(8, Math.min(x + POINTER_OFFSET, vw - MENU_WIDTH - 8));
|
|
233
|
-
const top = Math.max(8, Math.min(y + POINTER_OFFSET, vh - ROW_HEIGHT *
|
|
234
|
-
return /* @__PURE__ */
|
|
198
|
+
const top = Math.max(8, Math.min(y + POINTER_OFFSET, vh - ROW_HEIGHT * approxRows - 8));
|
|
199
|
+
return /* @__PURE__ */ jsxs(
|
|
235
200
|
"div",
|
|
236
201
|
{
|
|
237
202
|
className: cx("qt-cell-menu", classNames?.popover),
|
|
@@ -239,7 +204,69 @@ function CellMenu({ field, value, x, y, onAddFilter, onClose, classNames }) {
|
|
|
239
204
|
onClick: (e) => e.stopPropagation(),
|
|
240
205
|
onContextMenu: (e) => e.preventDefault(),
|
|
241
206
|
role: "menu",
|
|
242
|
-
children:
|
|
207
|
+
children: [
|
|
208
|
+
/* @__PURE__ */ jsxs("div", { className: "qt-cm-header", children: [
|
|
209
|
+
/* @__PURE__ */ jsx("span", { className: "qt-cm-field", children: field.label }),
|
|
210
|
+
/* @__PURE__ */ jsx("span", { className: "qt-cm-value", children: previewValue(value) })
|
|
211
|
+
] }),
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
MenuItem,
|
|
214
|
+
{
|
|
215
|
+
classNames,
|
|
216
|
+
onClick: async () => {
|
|
217
|
+
try {
|
|
218
|
+
await navigator.clipboard.writeText(cellText);
|
|
219
|
+
} catch {
|
|
220
|
+
}
|
|
221
|
+
onClose();
|
|
222
|
+
},
|
|
223
|
+
children: "copy value"
|
|
224
|
+
}
|
|
225
|
+
),
|
|
226
|
+
/* @__PURE__ */ jsx("div", { className: cx("qt-cm-sep", classNames?.separator) }),
|
|
227
|
+
rows.length > 0 && /* @__PURE__ */ jsxs("div", { className: "qt-cm-filter-grid", role: "group", "aria-label": "Filter by this cell", children: [
|
|
228
|
+
/* @__PURE__ */ jsx("span", { className: "qt-cm-col-head", children: "keep" }),
|
|
229
|
+
/* @__PURE__ */ jsx("span", { className: "qt-cm-col-head qt-cm-col-head--neg", children: "exclude" }),
|
|
230
|
+
rows.map((row, i) => /* @__PURE__ */ jsx(FilterPairRow, { row, value, classNames, onApply: applyClause }, i))
|
|
231
|
+
] })
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
function FilterPairRow({
|
|
237
|
+
row,
|
|
238
|
+
value,
|
|
239
|
+
classNames,
|
|
240
|
+
onApply
|
|
241
|
+
}) {
|
|
242
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
243
|
+
/* @__PURE__ */ jsx(FilterButton, { clause: row.positive, value, classNames, onApply }),
|
|
244
|
+
/* @__PURE__ */ jsx(FilterButton, { clause: row.negative, value, negative: true, classNames, onApply })
|
|
245
|
+
] });
|
|
246
|
+
}
|
|
247
|
+
function FilterButton({
|
|
248
|
+
clause,
|
|
249
|
+
value,
|
|
250
|
+
negative,
|
|
251
|
+
classNames,
|
|
252
|
+
onApply
|
|
253
|
+
}) {
|
|
254
|
+
if (!clause) return /* @__PURE__ */ jsx("span", { className: "qt-cm-filter-empty", "aria-hidden": true });
|
|
255
|
+
const nullary = NULLARY_OPS.has(clause.op);
|
|
256
|
+
return /* @__PURE__ */ jsxs(
|
|
257
|
+
"button",
|
|
258
|
+
{
|
|
259
|
+
type: "button",
|
|
260
|
+
className: cx("qt-cm-item", "qt-cm-filter", negative && "qt-cm-filter--neg", classNames?.item),
|
|
261
|
+
onClick: () => onApply(clause),
|
|
262
|
+
role: "menuitem",
|
|
263
|
+
children: [
|
|
264
|
+
/* @__PURE__ */ jsx("span", { className: "qt-cm-op", children: predicateSymbol(clause) }),
|
|
265
|
+
!nullary ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
266
|
+
" ",
|
|
267
|
+
/* @__PURE__ */ jsx("code", { children: previewValue(value) })
|
|
268
|
+
] }) : null
|
|
269
|
+
]
|
|
243
270
|
}
|
|
244
271
|
);
|
|
245
272
|
}
|
|
@@ -250,6 +277,10 @@ function MenuItem({
|
|
|
250
277
|
}) {
|
|
251
278
|
return /* @__PURE__ */ jsx("button", { type: "button", className: cx("qt-cm-item", classNames?.item), onClick, role: "menuitem", children });
|
|
252
279
|
}
|
|
280
|
+
function predicateSymbol(clause) {
|
|
281
|
+
const base = opSymbol(clause.op);
|
|
282
|
+
return clause.negated ? `not ${base}` : base;
|
|
283
|
+
}
|
|
253
284
|
function opSymbol(op) {
|
|
254
285
|
switch (op) {
|
|
255
286
|
case "!=":
|
|
@@ -258,6 +289,14 @@ function opSymbol(op) {
|
|
|
258
289
|
return "\u2265";
|
|
259
290
|
case "<=":
|
|
260
291
|
return "\u2264";
|
|
292
|
+
case "starts_with":
|
|
293
|
+
return "starts with";
|
|
294
|
+
case "ends_with":
|
|
295
|
+
return "ends with";
|
|
296
|
+
case "matches_regex":
|
|
297
|
+
return "matches";
|
|
298
|
+
case "not_matches_regex":
|
|
299
|
+
return "not matches";
|
|
261
300
|
case "is_null":
|
|
262
301
|
return "is null";
|
|
263
302
|
case "is_not_null":
|
|
@@ -292,6 +331,9 @@ var AUTOFIT_EXTRA_PX = 16;
|
|
|
292
331
|
var MENU_WIDTH2 = 240;
|
|
293
332
|
var MENU_ROW_HEIGHT = 32;
|
|
294
333
|
var COPY_FEEDBACK_MS = 900;
|
|
334
|
+
var DEFAULT_MAX_HEIGHT = 600;
|
|
335
|
+
var DEFAULT_ROW_HEIGHT = 37;
|
|
336
|
+
var DEFAULT_OVERSCAN = 8;
|
|
295
337
|
var SELECTION_COLUMN = "__qt_selection__";
|
|
296
338
|
function DataTable(props) {
|
|
297
339
|
const {
|
|
@@ -307,28 +349,76 @@ function DataTable(props) {
|
|
|
307
349
|
total = null,
|
|
308
350
|
loading,
|
|
309
351
|
emptyMessage,
|
|
352
|
+
maxHeight = DEFAULT_MAX_HEIGHT,
|
|
353
|
+
estimateRowHeight = DEFAULT_ROW_HEIGHT,
|
|
354
|
+
overscan = DEFAULT_OVERSCAN,
|
|
310
355
|
classNames
|
|
311
356
|
} = props;
|
|
312
357
|
const [menu, setMenu] = useState2(null);
|
|
313
|
-
const [resizingField, setResizingField] = useState2(null);
|
|
314
|
-
const [draftWidths, setDraftWidths] = useState2({});
|
|
315
358
|
const [selectionColumnWidth, setSelectionColumnWidth] = useState2(DEFAULT_SELECTION_WIDTH);
|
|
316
359
|
const [tableColumnOrder, setTableColumnOrder] = useState2([]);
|
|
317
360
|
const [copiedCellKey, setCopiedCellKey] = useState2(null);
|
|
361
|
+
const [headerHeight, setHeaderHeight] = useState2(0);
|
|
362
|
+
const [showHorizontalCue, setShowHorizontalCue] = useState2(false);
|
|
318
363
|
const tableWrapRef = useRef(null);
|
|
364
|
+
const tableHeadRef = useRef(null);
|
|
365
|
+
const tableRef = useRef(null);
|
|
366
|
+
const resizeGuideRef = useRef(null);
|
|
319
367
|
const resizeCommitRef = useRef(null);
|
|
368
|
+
const resizePreviewRef = useRef(null);
|
|
369
|
+
const resizeFrameRef = useRef(null);
|
|
370
|
+
const cancelResizeRef = useRef(null);
|
|
320
371
|
const resizingFieldRef = useRef(null);
|
|
321
372
|
const copiedCellTimerRef = useRef(null);
|
|
322
373
|
const localColumnDrag = useColumnDrag();
|
|
323
374
|
const columnDrag = props.columnDrag ?? localColumnDrag;
|
|
375
|
+
const rowIds = useMemo(() => rows.map(rowId), [rowId, rows]);
|
|
376
|
+
const virtualRowKeys = useMemo(
|
|
377
|
+
() => rowIds.map((id, index) => id == null ? `index:${index}` : `${typeof id}:${String(id)}`),
|
|
378
|
+
[rowIds]
|
|
379
|
+
);
|
|
380
|
+
const getVirtualRowKey = useCallback((index) => virtualRowKeys[index] ?? `index:${index}`, [virtualRowKeys]);
|
|
381
|
+
const normalizedEstimate = Number.isFinite(estimateRowHeight) ? Math.max(1, estimateRowHeight) : DEFAULT_ROW_HEIGHT;
|
|
382
|
+
const normalizedOverscan = Number.isFinite(overscan) ? Math.max(0, Math.floor(overscan)) : DEFAULT_OVERSCAN;
|
|
383
|
+
const estimateVirtualRow = useCallback(() => normalizedEstimate, [normalizedEstimate]);
|
|
384
|
+
const initialViewportHeight = typeof maxHeight === "number" && maxHeight > 0 ? maxHeight : DEFAULT_MAX_HEIGHT;
|
|
385
|
+
const initialRect = useMemo(() => ({ width: 0, height: initialViewportHeight }), [initialViewportHeight]);
|
|
386
|
+
const rowVirtualizer = useVirtualizer({
|
|
387
|
+
count: rows.length,
|
|
388
|
+
getScrollElement: () => tableWrapRef.current,
|
|
389
|
+
estimateSize: estimateVirtualRow,
|
|
390
|
+
getItemKey: getVirtualRowKey,
|
|
391
|
+
overscan: normalizedOverscan,
|
|
392
|
+
// The header participates in normal table layout even while it is sticky.
|
|
393
|
+
// Offset row measurements so the virtual range matches the body's origin.
|
|
394
|
+
scrollMargin: headerHeight,
|
|
395
|
+
initialRect
|
|
396
|
+
});
|
|
324
397
|
useEffect2(() => {
|
|
325
398
|
return () => {
|
|
326
399
|
if (copiedCellTimerRef.current) {
|
|
327
400
|
clearTimeout(copiedCellTimerRef.current);
|
|
328
401
|
copiedCellTimerRef.current = null;
|
|
329
402
|
}
|
|
403
|
+
cancelResizeRef.current?.();
|
|
330
404
|
};
|
|
331
405
|
}, []);
|
|
406
|
+
useEffect2(() => {
|
|
407
|
+
const header = tableHeadRef.current;
|
|
408
|
+
if (!header) return;
|
|
409
|
+
const measureHeader = () => {
|
|
410
|
+
const next = header.getBoundingClientRect().height;
|
|
411
|
+
setHeaderHeight((current) => current === next ? current : next);
|
|
412
|
+
};
|
|
413
|
+
measureHeader();
|
|
414
|
+
if (typeof ResizeObserver === "undefined") {
|
|
415
|
+
window.addEventListener("resize", measureHeader);
|
|
416
|
+
return () => window.removeEventListener("resize", measureHeader);
|
|
417
|
+
}
|
|
418
|
+
const observer = new ResizeObserver(measureHeader);
|
|
419
|
+
observer.observe(header);
|
|
420
|
+
return () => observer.disconnect();
|
|
421
|
+
}, []);
|
|
332
422
|
function cellKey(rowIdValue, rowIndex, fieldName) {
|
|
333
423
|
return rowIdValue == null ? `row:${rowIndex}:${fieldName}` : `id:${rowIdValue}:${fieldName}`;
|
|
334
424
|
}
|
|
@@ -361,7 +451,7 @@ function DataTable(props) {
|
|
|
361
451
|
}
|
|
362
452
|
showCopyFeedback(cellKey(rowIdValue, rowIndex, f.name));
|
|
363
453
|
}
|
|
364
|
-
const pageIds =
|
|
454
|
+
const pageIds = rowIds.filter((id) => id != null);
|
|
365
455
|
const headerState = selection ? selection.pageState(pageIds) : "none";
|
|
366
456
|
const showSel = selection != null;
|
|
367
457
|
function sortKeyFor(f) {
|
|
@@ -396,11 +486,11 @@ function DataTable(props) {
|
|
|
396
486
|
return col?.width ?? f.select?.width;
|
|
397
487
|
}
|
|
398
488
|
function resolvedWidthFor(name, f) {
|
|
399
|
-
return
|
|
489
|
+
return widthFor(name, f);
|
|
400
490
|
}
|
|
401
491
|
function resolvedColumnWidthFor(name, f) {
|
|
402
|
-
if (name === SELECTION_COLUMN) return
|
|
403
|
-
return f ? resolvedWidthFor(name, f) :
|
|
492
|
+
if (name === SELECTION_COLUMN) return selectionColumnWidth;
|
|
493
|
+
return f ? resolvedWidthFor(name, f) : void 0;
|
|
404
494
|
}
|
|
405
495
|
function displayColumnWidthFor(name, f) {
|
|
406
496
|
if (name === SELECTION_COLUMN) return resolvedColumnWidthFor(name) ?? DEFAULT_SELECTION_WIDTH;
|
|
@@ -424,16 +514,20 @@ function DataTable(props) {
|
|
|
424
514
|
const px = clampWidth(width);
|
|
425
515
|
onQueryChange((prev) => ({ ...prev, select: writeWidth(prev.select, fields, name, px) }));
|
|
426
516
|
}
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
517
|
+
function applyResizeGuide() {
|
|
518
|
+
const preview = resizePreviewRef.current;
|
|
519
|
+
const guide = resizeGuideRef.current;
|
|
520
|
+
if (!preview || !guide) return;
|
|
521
|
+
guide.style.transform = `translateX(${preview.guideX}px)`;
|
|
522
|
+
}
|
|
523
|
+
function applyCommittedResize() {
|
|
524
|
+
const preview = resizePreviewRef.current;
|
|
525
|
+
const table = tableRef.current;
|
|
526
|
+
if (!preview || !table) return;
|
|
527
|
+
const selector = `col[data-qt-column="${cssAttributeValue(preview.name)}"]`;
|
|
528
|
+
const col = table.querySelector(selector);
|
|
529
|
+
if (col) col.style.width = `${preview.width}px`;
|
|
530
|
+
table.style.minWidth = `${preview.startTableWidth + preview.width - preview.previousWidth}px`;
|
|
437
531
|
}
|
|
438
532
|
function startResize(name, startWidth, e) {
|
|
439
533
|
e.preventDefault();
|
|
@@ -442,30 +536,70 @@ function DataTable(props) {
|
|
|
442
536
|
const startW = startWidth ?? e.currentTarget.parentElement?.getBoundingClientRect().width ?? DEFAULT_WIDTH;
|
|
443
537
|
const previousCursor = document.body.style.cursor;
|
|
444
538
|
const previousUserSelect = document.body.style.userSelect;
|
|
539
|
+
const initialWidth = clampColumnWidth(name, startW);
|
|
540
|
+
const wrap = tableWrapRef.current;
|
|
541
|
+
const wrapRect = wrap?.getBoundingClientRect();
|
|
542
|
+
const header = e.currentTarget.parentElement;
|
|
445
543
|
resizeCommitRef.current = null;
|
|
544
|
+
resizePreviewRef.current = {
|
|
545
|
+
name,
|
|
546
|
+
width: initialWidth,
|
|
547
|
+
previousWidth: displayColumnWidthFor(name, fieldByName.get(name)),
|
|
548
|
+
startTableWidth: tableWidth,
|
|
549
|
+
guideX: startX - (wrapRect?.left ?? 0) + (wrap?.scrollLeft ?? 0)
|
|
550
|
+
};
|
|
446
551
|
resizingFieldRef.current = name;
|
|
447
|
-
|
|
448
|
-
|
|
552
|
+
tableWrapRef.current?.classList.add("qt-table-wrap--resizing");
|
|
553
|
+
header?.classList.add("qt-th--resizing");
|
|
554
|
+
if (header) header.draggable = false;
|
|
555
|
+
if (resizeGuideRef.current) {
|
|
556
|
+
resizeGuideRef.current.style.display = "block";
|
|
557
|
+
resizeGuideRef.current.style.top = `${wrap?.scrollTop ?? 0}px`;
|
|
558
|
+
resizeGuideRef.current.style.height = `${wrap?.clientHeight ?? 0}px`;
|
|
559
|
+
}
|
|
560
|
+
applyResizeGuide();
|
|
449
561
|
document.body.style.cursor = "col-resize";
|
|
450
562
|
document.body.style.userSelect = "none";
|
|
451
|
-
const
|
|
563
|
+
const cleanup = (commitWidth) => {
|
|
452
564
|
window.removeEventListener("pointermove", onMove);
|
|
453
565
|
window.removeEventListener("pointerup", finish);
|
|
454
566
|
window.removeEventListener("pointercancel", finish);
|
|
567
|
+
if (resizeFrameRef.current != null) {
|
|
568
|
+
cancelAnimationFrame(resizeFrameRef.current);
|
|
569
|
+
resizeFrameRef.current = null;
|
|
570
|
+
applyResizeGuide();
|
|
571
|
+
}
|
|
455
572
|
document.body.style.cursor = previousCursor;
|
|
456
573
|
document.body.style.userSelect = previousUserSelect;
|
|
457
|
-
|
|
574
|
+
tableWrapRef.current?.classList.remove("qt-table-wrap--resizing");
|
|
575
|
+
tableRef.current?.querySelector(`th[data-qt-field="${cssAttributeValue(name)}"]`)?.classList.remove("qt-th--resizing");
|
|
576
|
+
if (header) header.draggable = true;
|
|
577
|
+
const commit = commitWidth ? resizeCommitRef.current : null;
|
|
578
|
+
if (commit) applyCommittedResize();
|
|
579
|
+
if (resizeGuideRef.current) resizeGuideRef.current.style.display = "none";
|
|
458
580
|
resizeCommitRef.current = null;
|
|
581
|
+
resizePreviewRef.current = null;
|
|
459
582
|
resizingFieldRef.current = null;
|
|
460
|
-
|
|
461
|
-
clearDraftWidth(name);
|
|
583
|
+
cancelResizeRef.current = null;
|
|
462
584
|
if (commit) commitColumnWidth(commit.name, commit.width);
|
|
463
585
|
};
|
|
586
|
+
const finish = () => cleanup(true);
|
|
464
587
|
const onMove = (ev) => {
|
|
465
588
|
const next = clampColumnWidth(name, startW + (ev.clientX - startX));
|
|
466
589
|
resizeCommitRef.current = { name, width: next };
|
|
467
|
-
|
|
590
|
+
const preview = resizePreviewRef.current;
|
|
591
|
+
if (preview) {
|
|
592
|
+
preview.width = next;
|
|
593
|
+
preview.guideX = startX + (next - startW) - (wrapRect?.left ?? 0) + (wrap?.scrollLeft ?? 0);
|
|
594
|
+
}
|
|
595
|
+
if (resizeFrameRef.current == null) {
|
|
596
|
+
resizeFrameRef.current = requestAnimationFrame(() => {
|
|
597
|
+
resizeFrameRef.current = null;
|
|
598
|
+
applyResizeGuide();
|
|
599
|
+
});
|
|
600
|
+
}
|
|
468
601
|
};
|
|
602
|
+
cancelResizeRef.current = () => cleanup(false);
|
|
469
603
|
window.addEventListener("pointermove", onMove);
|
|
470
604
|
window.addEventListener("pointerup", finish);
|
|
471
605
|
window.addEventListener("pointercancel", finish);
|
|
@@ -573,6 +707,22 @@ function DataTable(props) {
|
|
|
573
707
|
const f = fieldByName.get(name);
|
|
574
708
|
return sum + displayColumnWidthFor(name, f);
|
|
575
709
|
}, trailing ? 40 : 0);
|
|
710
|
+
useEffect2(() => {
|
|
711
|
+
const wrap = tableWrapRef.current;
|
|
712
|
+
if (!wrap) return;
|
|
713
|
+
const updateCue = () => {
|
|
714
|
+
const hasMoreToRight = wrap.scrollWidth - wrap.clientWidth - wrap.scrollLeft > 2;
|
|
715
|
+
setShowHorizontalCue(hasMoreToRight);
|
|
716
|
+
};
|
|
717
|
+
updateCue();
|
|
718
|
+
window.addEventListener("resize", updateCue);
|
|
719
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(updateCue);
|
|
720
|
+
observer?.observe(wrap);
|
|
721
|
+
return () => {
|
|
722
|
+
window.removeEventListener("resize", updateCue);
|
|
723
|
+
observer?.disconnect();
|
|
724
|
+
};
|
|
725
|
+
}, [renderedColumnNames, tableWidth]);
|
|
576
726
|
const start = query.offset;
|
|
577
727
|
const end = total != null ? Math.min(start + query.limit, total) : start + rows.length;
|
|
578
728
|
const canPrev = start > 0;
|
|
@@ -580,7 +730,16 @@ function DataTable(props) {
|
|
|
580
730
|
const prevPage = () => onQueryChange((prev) => ({ ...prev, offset: Math.max(0, prev.offset - prev.limit) }));
|
|
581
731
|
const nextPage = () => onQueryChange((prev) => ({ ...prev, offset: prev.offset + prev.limit }));
|
|
582
732
|
const summaryText = total == null || total === 0 ? `${rows.length} rows` : `${start + 1}\u2013${end} of ${total}`;
|
|
733
|
+
const virtualRows = rowVirtualizer.getVirtualItems();
|
|
734
|
+
const firstVirtualRow = virtualRows[0];
|
|
735
|
+
const lastVirtualRow = virtualRows[virtualRows.length - 1];
|
|
736
|
+
const paddingTop = firstVirtualRow ? Math.max(0, firstVirtualRow.start - headerHeight) : 0;
|
|
737
|
+
const paddingBottom = lastVirtualRow ? Math.max(0, rowVirtualizer.getTotalSize() - (lastVirtualRow.end - headerHeight)) : 0;
|
|
583
738
|
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
739
|
+
showHorizontalCue ? /* @__PURE__ */ jsxs2("div", { className: "qt-table-scroll-cue", "aria-hidden": "true", children: [
|
|
740
|
+
"Swipe to see more ",
|
|
741
|
+
/* @__PURE__ */ jsx2("span", { children: "\u2192" })
|
|
742
|
+
] }) : null,
|
|
584
743
|
/* @__PURE__ */ jsxs2(
|
|
585
744
|
"div",
|
|
586
745
|
{
|
|
@@ -588,24 +747,40 @@ function DataTable(props) {
|
|
|
588
747
|
className: cx2(
|
|
589
748
|
"qt-table-wrap",
|
|
590
749
|
loading && "qt-table-wrap--loading",
|
|
591
|
-
!!resizingField && "qt-table-wrap--resizing",
|
|
592
750
|
classNames?.wrap
|
|
593
751
|
),
|
|
594
752
|
onWheel: onTableWheel,
|
|
753
|
+
style: { maxHeight, overflowY: "auto" },
|
|
754
|
+
onScroll: () => {
|
|
755
|
+
const wrap = tableWrapRef.current;
|
|
756
|
+
if (!wrap) return;
|
|
757
|
+
setShowHorizontalCue(wrap.scrollWidth - wrap.clientWidth - wrap.scrollLeft > 2);
|
|
758
|
+
},
|
|
759
|
+
role: "region",
|
|
760
|
+
"aria-label": "Scrollable data table",
|
|
761
|
+
tabIndex: 0,
|
|
595
762
|
children: [
|
|
596
763
|
loading && /* @__PURE__ */ jsx2("div", { className: cx2("qt-loading-bar", classNames?.loadingBar), role: "progressbar", "aria-label": "loading" }),
|
|
597
|
-
/* @__PURE__ */
|
|
764
|
+
/* @__PURE__ */ jsx2(
|
|
765
|
+
"div",
|
|
766
|
+
{
|
|
767
|
+
ref: resizeGuideRef,
|
|
768
|
+
className: cx2("qt-resize-guide", classNames?.resizeGuide),
|
|
769
|
+
"aria-hidden": "true"
|
|
770
|
+
}
|
|
771
|
+
),
|
|
772
|
+
/* @__PURE__ */ jsxs2("table", { ref: tableRef, className: cx2("qt-table", classNames?.table), style: { minWidth: tableWidth }, children: [
|
|
598
773
|
/* @__PURE__ */ jsxs2("colgroup", { children: [
|
|
599
774
|
renderedColumnNames.map((name) => {
|
|
600
775
|
const f = fieldByName.get(name);
|
|
601
776
|
const w = displayColumnWidthFor(name, f);
|
|
602
|
-
if (name === SELECTION_COLUMN) return /* @__PURE__ */ jsx2("col", { style: { width: w } }, name);
|
|
777
|
+
if (name === SELECTION_COLUMN) return /* @__PURE__ */ jsx2("col", { "data-qt-column": name, style: { width: w } }, name);
|
|
603
778
|
if (!f) return null;
|
|
604
|
-
return /* @__PURE__ */ jsx2("col", { style: { width: w } }, f.name);
|
|
779
|
+
return /* @__PURE__ */ jsx2("col", { "data-qt-column": f.name, style: { width: w } }, f.name);
|
|
605
780
|
}),
|
|
606
781
|
trailing && /* @__PURE__ */ jsx2("col", { style: { width: 40 } })
|
|
607
782
|
] }),
|
|
608
|
-
/* @__PURE__ */ jsx2("thead", { className: classNames?.thead, children: /* @__PURE__ */ jsxs2("tr", { className: classNames?.headerRow, children: [
|
|
783
|
+
/* @__PURE__ */ jsx2("thead", { ref: tableHeadRef, className: classNames?.thead, children: /* @__PURE__ */ jsxs2("tr", { className: classNames?.headerRow, children: [
|
|
609
784
|
renderedColumnNames.map((name) => {
|
|
610
785
|
if (name === SELECTION_COLUMN) {
|
|
611
786
|
return /* @__PURE__ */ jsxs2(
|
|
@@ -616,11 +791,10 @@ function DataTable(props) {
|
|
|
616
791
|
"qt-th",
|
|
617
792
|
"qt-checkbox-cell",
|
|
618
793
|
dragSource === name && "qt-th--dragging",
|
|
619
|
-
resizingField === name && "qt-th--resizing",
|
|
620
794
|
classNames?.th,
|
|
621
795
|
classNames?.checkboxCell
|
|
622
796
|
),
|
|
623
|
-
draggable:
|
|
797
|
+
draggable: true,
|
|
624
798
|
onDragStart: (e) => {
|
|
625
799
|
if (resizingFieldRef.current) {
|
|
626
800
|
e.preventDefault();
|
|
@@ -683,11 +857,10 @@ function DataTable(props) {
|
|
|
683
857
|
"qt-th",
|
|
684
858
|
sortable && "qt-th--sortable",
|
|
685
859
|
dragSource === f.name && "qt-th--dragging",
|
|
686
|
-
resizingField === f.name && "qt-th--resizing",
|
|
687
860
|
classNames?.th
|
|
688
861
|
),
|
|
689
862
|
style: f.select?.align ? { textAlign: f.select.align } : void 0,
|
|
690
|
-
draggable:
|
|
863
|
+
draggable: true,
|
|
691
864
|
onDragStart: (e) => {
|
|
692
865
|
if (resizingFieldRef.current) {
|
|
693
866
|
e.preventDefault();
|
|
@@ -746,12 +919,17 @@ function DataTable(props) {
|
|
|
746
919
|
] }) }),
|
|
747
920
|
/* @__PURE__ */ jsxs2("tbody", { className: classNames?.tbody, children: [
|
|
748
921
|
rows.length === 0 && /* @__PURE__ */ jsx2("tr", { children: /* @__PURE__ */ jsx2("td", { colSpan: totalCols, className: cx2("qt-cell", "qt-empty", classNames?.empty), children: emptyMessage ?? "No rows." }) }),
|
|
749
|
-
|
|
750
|
-
|
|
922
|
+
paddingTop > 0 && /* @__PURE__ */ jsx2("tr", { className: "qt-virtual-spacer", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("td", { colSpan: totalCols, style: { height: paddingTop, padding: 0, border: 0 } }) }),
|
|
923
|
+
virtualRows.map((virtualRow) => {
|
|
924
|
+
const i = virtualRow.index;
|
|
925
|
+
const row = rows[i];
|
|
926
|
+
const id = rowIds[i] ?? null;
|
|
751
927
|
const selected = id != null && selection ? selection.isSelected(id) : false;
|
|
752
928
|
return /* @__PURE__ */ jsxs2(
|
|
753
929
|
"tr",
|
|
754
930
|
{
|
|
931
|
+
"data-index": i,
|
|
932
|
+
ref: rowVirtualizer.measureElement,
|
|
755
933
|
className: cx2("qt-row", selected && "qt-row--selected", classNames?.row, selected && classNames?.rowSelected),
|
|
756
934
|
children: [
|
|
757
935
|
renderedColumnNames.map((name) => {
|
|
@@ -796,7 +974,7 @@ function DataTable(props) {
|
|
|
796
974
|
onClick: (e) => openMenu(e, f, row),
|
|
797
975
|
onContextMenu: (e) => openMenu(e, f, row),
|
|
798
976
|
onDoubleClick: (e) => copyCellToClipboard(e, f, id, i, value),
|
|
799
|
-
children: copiedCellKey === key ? /* @__PURE__ */ jsx2("span", { className: "qt-cell-copy-chip", children: "\u2713 copied" }) : render({ value, row, field: f, query })
|
|
977
|
+
children: copiedCellKey === key ? /* @__PURE__ */ jsx2("span", { className: "qt-cell-copy-chip", children: "\u2713 copied" }) : isComputedCellError(value) ? /* @__PURE__ */ jsx2("span", { className: "qt-formula-error", title: value.computedError, children: value.computedError === "Calculating\u2026" ? "Calculating\u2026" : "Error" }) : render({ value, row, field: f, query })
|
|
800
978
|
},
|
|
801
979
|
f.name
|
|
802
980
|
);
|
|
@@ -804,9 +982,10 @@ function DataTable(props) {
|
|
|
804
982
|
trailing && /* @__PURE__ */ jsx2("td", { className: cx2("qt-cell", "qt-trailing-cell", classNames?.cell), children: trailing(row) })
|
|
805
983
|
]
|
|
806
984
|
},
|
|
807
|
-
|
|
985
|
+
virtualRow.key
|
|
808
986
|
);
|
|
809
|
-
})
|
|
987
|
+
}),
|
|
988
|
+
paddingBottom > 0 && /* @__PURE__ */ jsx2("tr", { className: "qt-virtual-spacer", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("td", { colSpan: totalCols, style: { height: paddingBottom, padding: 0, border: 0 } }) })
|
|
810
989
|
] }),
|
|
811
990
|
/* @__PURE__ */ jsx2("tfoot", { children: /* @__PURE__ */ jsx2("tr", { className: cx2("qt-table-summary-row", classNames?.summaryRow), children: /* @__PURE__ */ jsx2("td", { colSpan: totalCols, className: cx2("qt-cell", "qt-table-summary-cell", classNames?.summaryCell), children: /* @__PURE__ */ jsxs2("div", { className: cx2("qt-table-summary", classNames?.summaryControls), children: [
|
|
812
991
|
/* @__PURE__ */ jsx2(
|
|
@@ -973,25 +1152,804 @@ function valueToString2(value) {
|
|
|
973
1152
|
return String(value);
|
|
974
1153
|
}
|
|
975
1154
|
|
|
1155
|
+
// src/SelectColumnEditor.tsx
|
|
1156
|
+
import {
|
|
1157
|
+
lazy,
|
|
1158
|
+
Suspense,
|
|
1159
|
+
useEffect as useEffect3,
|
|
1160
|
+
useId,
|
|
1161
|
+
useMemo as useMemo2,
|
|
1162
|
+
useRef as useRef2,
|
|
1163
|
+
useState as useState3
|
|
1164
|
+
} from "react";
|
|
1165
|
+
import {
|
|
1166
|
+
computedFieldName,
|
|
1167
|
+
FORMULA_FUNCTIONS,
|
|
1168
|
+
isComputedField,
|
|
1169
|
+
isSelectable
|
|
1170
|
+
} from "@pythia-software/query-table-core";
|
|
1171
|
+
|
|
1172
|
+
// src/previewOrdering.ts
|
|
1173
|
+
var collator = new Intl.Collator(void 0, {
|
|
1174
|
+
numeric: true,
|
|
1175
|
+
sensitivity: "base"
|
|
1176
|
+
});
|
|
1177
|
+
var valueLabel = (value) => value === null ? "NULL" : typeof value === "string" ? value : JSON.stringify(value);
|
|
1178
|
+
function mixedRank(group) {
|
|
1179
|
+
const value = JSON.stringify([
|
|
1180
|
+
group.inputs,
|
|
1181
|
+
group.result.value,
|
|
1182
|
+
group.result.error ?? null
|
|
1183
|
+
]);
|
|
1184
|
+
let hash = 2166136261;
|
|
1185
|
+
for (let i = 0; i < value.length; i++)
|
|
1186
|
+
hash = Math.imul(hash ^ value.charCodeAt(i), 16777619);
|
|
1187
|
+
return hash >>> 0;
|
|
1188
|
+
}
|
|
1189
|
+
function compareValue(a, b) {
|
|
1190
|
+
return collator.compare(valueLabel(a), valueLabel(b));
|
|
1191
|
+
}
|
|
1192
|
+
function sortValue(group, key) {
|
|
1193
|
+
switch (key.kind) {
|
|
1194
|
+
case "input":
|
|
1195
|
+
return group.inputs[key.index] ?? null;
|
|
1196
|
+
case "result":
|
|
1197
|
+
return group.result.error ?? group.result.value;
|
|
1198
|
+
case "count":
|
|
1199
|
+
case "percentage":
|
|
1200
|
+
return group.count;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
function orderPreviewGroups(groups, sort) {
|
|
1204
|
+
return groups.map((group, index) => ({ group, index })).sort((a, b) => {
|
|
1205
|
+
if (!sort) {
|
|
1206
|
+
const rank = mixedRank(a.group) - mixedRank(b.group);
|
|
1207
|
+
return rank || a.index - b.index;
|
|
1208
|
+
}
|
|
1209
|
+
const comparison = compareValue(
|
|
1210
|
+
sortValue(a.group, sort.key),
|
|
1211
|
+
sortValue(b.group, sort.key)
|
|
1212
|
+
);
|
|
1213
|
+
return (sort.direction === "asc" ? comparison : -comparison) || a.index - b.index;
|
|
1214
|
+
}).map(({ group }) => group);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// src/SelectColumnEditor.tsx
|
|
1218
|
+
import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1219
|
+
var FormulaEditor = lazy(() => import("./FormulaEditor-YCPICDIH.js"));
|
|
1220
|
+
var fieldExpression = (name) => `[${name.replace(/\]/g, "]]")}]`;
|
|
1221
|
+
var display = (v) => v === null ? "NULL" : typeof v === "string" ? v === "" ? '""' : v : JSON.stringify(v);
|
|
1222
|
+
function SelectColumnEditor({
|
|
1223
|
+
api,
|
|
1224
|
+
onClose
|
|
1225
|
+
}) {
|
|
1226
|
+
const titleId = useId(), dialog = useRef2(null);
|
|
1227
|
+
const closeRef = useRef2(onClose);
|
|
1228
|
+
closeRef.current = onClose;
|
|
1229
|
+
const [columns, setColumns] = useState3(
|
|
1230
|
+
() => api.select.visible.map((c) => ({ ...c }))
|
|
1231
|
+
);
|
|
1232
|
+
const [search, setSearch] = useState3(""), [active, setActive] = useState3(api.select.visible[0]?.field ?? "");
|
|
1233
|
+
const [editing, setEditing] = useState3(false), [id, setId] = useState3(""), [revision, setRevision] = useState3(null);
|
|
1234
|
+
const [label, setLabel] = useState3(""), [source, setSource] = useState3("");
|
|
1235
|
+
const [count, setCount] = useState3(1e3), [preview, setPreview] = useState3(null), [loading, setLoading] = useState3(false);
|
|
1236
|
+
const [error, setError] = useState3(null), [notice, setNotice] = useState3(""), [saving, setSaving] = useState3(false);
|
|
1237
|
+
const [filter, setFilter] = useState3("all"), [page, setPage] = useState3(0), [retry, setRetry] = useState3(0), [functionSearch, setFunctionSearch] = useState3("");
|
|
1238
|
+
const [previewSort, setPreviewSort] = useState3(null);
|
|
1239
|
+
const drag = useRef2(null);
|
|
1240
|
+
const [inspection, setInspection] = useState3(null);
|
|
1241
|
+
const catalogue = api.computed.catalogue.filter(isSelectable);
|
|
1242
|
+
const chosen = catalogue.find((f) => f.name === active);
|
|
1243
|
+
const expression = editing ? source : active ? fieldExpression(active) : "";
|
|
1244
|
+
const validation = useMemo2(() => {
|
|
1245
|
+
if (!expression)
|
|
1246
|
+
return { error: "Choose a column or create a computed column." };
|
|
1247
|
+
try {
|
|
1248
|
+
return {
|
|
1249
|
+
plan: api.computed.compile(expression, editing ? id : void 0)
|
|
1250
|
+
};
|
|
1251
|
+
} catch (e) {
|
|
1252
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
1253
|
+
}
|
|
1254
|
+
}, [expression, editing, id, api.computed.compile]);
|
|
1255
|
+
useEffect3(() => {
|
|
1256
|
+
const previous = document.activeElement;
|
|
1257
|
+
dialog.current?.focus();
|
|
1258
|
+
const onKey = (e) => {
|
|
1259
|
+
if (e.key === "Escape") {
|
|
1260
|
+
if (document.querySelector(".cm-tooltip-autocomplete")) return;
|
|
1261
|
+
closeRef.current();
|
|
1262
|
+
}
|
|
1263
|
+
if (e.key === "Tab") {
|
|
1264
|
+
const focusable = Array.from(
|
|
1265
|
+
dialog.current?.querySelectorAll(
|
|
1266
|
+
'button:not([disabled]), input:not([disabled]), select, textarea, [tabindex="0"], [contenteditable="true"]'
|
|
1267
|
+
) ?? []
|
|
1268
|
+
).filter((el) => el.getClientRects().length);
|
|
1269
|
+
const first = focusable[0], last = focusable[focusable.length - 1];
|
|
1270
|
+
if (e.shiftKey && (document.activeElement === first || document.activeElement === dialog.current)) {
|
|
1271
|
+
e.preventDefault();
|
|
1272
|
+
last?.focus();
|
|
1273
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
1274
|
+
e.preventDefault();
|
|
1275
|
+
first?.focus();
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
document.addEventListener("keydown", onKey);
|
|
1280
|
+
const old = document.body.style.overflow;
|
|
1281
|
+
document.body.style.overflow = "hidden";
|
|
1282
|
+
return () => {
|
|
1283
|
+
document.removeEventListener("keydown", onKey);
|
|
1284
|
+
document.body.style.overflow = old;
|
|
1285
|
+
previous?.focus();
|
|
1286
|
+
};
|
|
1287
|
+
}, []);
|
|
1288
|
+
useEffect3(() => {
|
|
1289
|
+
const ac = new AbortController();
|
|
1290
|
+
setPreview(null);
|
|
1291
|
+
setError(null);
|
|
1292
|
+
setPage(0);
|
|
1293
|
+
setInspection(null);
|
|
1294
|
+
if (validation.error || !Number.isInteger(count) || count < 1 || count > 1e4) {
|
|
1295
|
+
setLoading(false);
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
setLoading(true);
|
|
1299
|
+
const timer = setTimeout(() => {
|
|
1300
|
+
void api.computed.preview(
|
|
1301
|
+
expression,
|
|
1302
|
+
count,
|
|
1303
|
+
ac.signal,
|
|
1304
|
+
editing ? id : void 0,
|
|
1305
|
+
editing
|
|
1306
|
+
).then((result) => {
|
|
1307
|
+
if (!ac.signal.aborted) setPreview(result);
|
|
1308
|
+
}).catch((e) => {
|
|
1309
|
+
if (!ac.signal.aborted)
|
|
1310
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1311
|
+
}).finally(() => {
|
|
1312
|
+
if (!ac.signal.aborted) setLoading(false);
|
|
1313
|
+
});
|
|
1314
|
+
}, 400);
|
|
1315
|
+
return () => {
|
|
1316
|
+
ac.abort();
|
|
1317
|
+
clearTimeout(timer);
|
|
1318
|
+
};
|
|
1319
|
+
}, [expression, count, editing, id, validation, api.computed.preview, retry]);
|
|
1320
|
+
const move = (field, index) => setColumns((cols) => {
|
|
1321
|
+
const next = cols.filter((c) => c.field !== field), item = cols.find((c) => c.field === field);
|
|
1322
|
+
if (item) next.splice(Math.max(0, Math.min(index, next.length)), 0, item);
|
|
1323
|
+
return next;
|
|
1324
|
+
});
|
|
1325
|
+
const toggle = (field) => setColumns(
|
|
1326
|
+
(cols) => cols.some((c) => c.field === field) ? cols.filter((c) => c.field !== field) : [...cols, { field }]
|
|
1327
|
+
);
|
|
1328
|
+
function browse(field) {
|
|
1329
|
+
setActive(field);
|
|
1330
|
+
setEditing(false);
|
|
1331
|
+
setNotice("");
|
|
1332
|
+
}
|
|
1333
|
+
function edit(defId) {
|
|
1334
|
+
const def = api.computed.definitions.find((d) => d.id === defId);
|
|
1335
|
+
setId(def?.id ?? defId ?? crypto.randomUUID());
|
|
1336
|
+
setRevision(def?.revision ?? null);
|
|
1337
|
+
setLabel(def?.label ?? defId ?? "");
|
|
1338
|
+
setSource(
|
|
1339
|
+
def?.expression.source ?? (defId ? "" : active ? fieldExpression(active) : "LEFT([field], 3)")
|
|
1340
|
+
);
|
|
1341
|
+
setEditing(true);
|
|
1342
|
+
setNotice("");
|
|
1343
|
+
}
|
|
1344
|
+
async function save() {
|
|
1345
|
+
setSaving(true);
|
|
1346
|
+
setError(null);
|
|
1347
|
+
try {
|
|
1348
|
+
const def = await api.computed.save(
|
|
1349
|
+
{
|
|
1350
|
+
id,
|
|
1351
|
+
label: label.trim(),
|
|
1352
|
+
expression: { language: "qt-expr", version: 1, source }
|
|
1353
|
+
},
|
|
1354
|
+
revision
|
|
1355
|
+
);
|
|
1356
|
+
setRevision(def.revision);
|
|
1357
|
+
const field = computedFieldName(def.id);
|
|
1358
|
+
setActive(field);
|
|
1359
|
+
setColumns(
|
|
1360
|
+
(cols) => cols.some((c) => c.field === field) ? cols : [...cols, { field }]
|
|
1361
|
+
);
|
|
1362
|
+
setNotice(
|
|
1363
|
+
"Definition saved. All queries referencing this ID now use this definition. Apply columns to update this query\u2019s layout."
|
|
1364
|
+
);
|
|
1365
|
+
} catch (e) {
|
|
1366
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
1367
|
+
} finally {
|
|
1368
|
+
setSaving(false);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
const groups = useMemo2(
|
|
1372
|
+
() => orderPreviewGroups(
|
|
1373
|
+
(preview?.groups ?? []).filter(
|
|
1374
|
+
(g) => filter === "errors" ? !!g.result.error : filter === "nulls" ? !g.result.error && g.result.value === null : true
|
|
1375
|
+
),
|
|
1376
|
+
previewSort
|
|
1377
|
+
),
|
|
1378
|
+
[preview, filter, previewSort]
|
|
1379
|
+
);
|
|
1380
|
+
const shown = groups.slice(page * 50, (page + 1) * 50);
|
|
1381
|
+
const samePreviewSortKey = (a, b) => {
|
|
1382
|
+
if (a.kind !== b.kind) return false;
|
|
1383
|
+
if (a.kind === "input" && b.kind === "input")
|
|
1384
|
+
return a.index === b.index;
|
|
1385
|
+
return true;
|
|
1386
|
+
};
|
|
1387
|
+
const selectPreviewSort = (key, direction) => {
|
|
1388
|
+
setPreviewSort((current) => {
|
|
1389
|
+
const sameKey = !!current && samePreviewSortKey(current.key, key);
|
|
1390
|
+
return sameKey ? { key, direction: current.direction === "asc" ? "desc" : "asc" } : { key, direction };
|
|
1391
|
+
});
|
|
1392
|
+
setPage(0);
|
|
1393
|
+
};
|
|
1394
|
+
const previewSortDirection = (key) => previewSort && samePreviewSortKey(previewSort.key, key) ? previewSort.direction : void 0;
|
|
1395
|
+
const previewAriaSort = (key) => {
|
|
1396
|
+
const direction = previewSortDirection(key);
|
|
1397
|
+
return direction === "asc" ? "ascending" : direction === "desc" ? "descending" : "none";
|
|
1398
|
+
};
|
|
1399
|
+
const previewSortHeader = (label2, key, direction = "asc") => {
|
|
1400
|
+
const currentDirection = previewSortDirection(key);
|
|
1401
|
+
return /* @__PURE__ */ jsxs3(
|
|
1402
|
+
"button",
|
|
1403
|
+
{
|
|
1404
|
+
type: "button",
|
|
1405
|
+
className: "qt-preview-sort",
|
|
1406
|
+
onClick: () => selectPreviewSort(key, direction),
|
|
1407
|
+
title: `Sort by ${label2}`,
|
|
1408
|
+
children: [
|
|
1409
|
+
label2,
|
|
1410
|
+
currentDirection && (currentDirection === "asc" ? " \u2191" : " \u2193")
|
|
1411
|
+
]
|
|
1412
|
+
}
|
|
1413
|
+
);
|
|
1414
|
+
};
|
|
1415
|
+
return /* @__PURE__ */ jsx3("div", { className: "qt-modal-backdrop qt-select-editor-backdrop", children: /* @__PURE__ */ jsxs3(
|
|
1416
|
+
"div",
|
|
1417
|
+
{
|
|
1418
|
+
className: "qt-select-editor",
|
|
1419
|
+
role: "dialog",
|
|
1420
|
+
"aria-modal": "true",
|
|
1421
|
+
"aria-labelledby": titleId,
|
|
1422
|
+
ref: dialog,
|
|
1423
|
+
tabIndex: -1,
|
|
1424
|
+
children: [
|
|
1425
|
+
/* @__PURE__ */ jsxs3("header", { className: "qt-select-editor-header", children: [
|
|
1426
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
1427
|
+
/* @__PURE__ */ jsx3("h2", { id: titleId, children: "Select columns" }),
|
|
1428
|
+
/* @__PURE__ */ jsx3("p", { children: "Arrange your view, explore values, and create reusable computed columns." })
|
|
1429
|
+
] }),
|
|
1430
|
+
/* @__PURE__ */ jsx3(
|
|
1431
|
+
"button",
|
|
1432
|
+
{
|
|
1433
|
+
type: "button",
|
|
1434
|
+
className: "qt-btn",
|
|
1435
|
+
onClick: onClose,
|
|
1436
|
+
"aria-label": "Close column editor",
|
|
1437
|
+
children: "\u2715"
|
|
1438
|
+
}
|
|
1439
|
+
)
|
|
1440
|
+
] }),
|
|
1441
|
+
/* @__PURE__ */ jsxs3("div", { className: "qt-select-editor-body", children: [
|
|
1442
|
+
/* @__PURE__ */ jsxs3("aside", { className: "qt-selected-panel", children: [
|
|
1443
|
+
/* @__PURE__ */ jsxs3("h3", { children: [
|
|
1444
|
+
"Selected ",
|
|
1445
|
+
/* @__PURE__ */ jsx3("span", { className: "qt-muted", children: columns.length })
|
|
1446
|
+
] }),
|
|
1447
|
+
/* @__PURE__ */ jsx3("p", { className: "qt-muted", children: "Drag to reorder, or use the arrow buttons." }),
|
|
1448
|
+
/* @__PURE__ */ jsx3("ol", { className: "qt-editor-columns", children: columns.map((c, i) => {
|
|
1449
|
+
const f = catalogue.find((f2) => f2.name === c.field);
|
|
1450
|
+
return /* @__PURE__ */ jsxs3(
|
|
1451
|
+
"li",
|
|
1452
|
+
{
|
|
1453
|
+
draggable: true,
|
|
1454
|
+
onDragStart: (e) => {
|
|
1455
|
+
drag.current = c.field;
|
|
1456
|
+
e.dataTransfer.setData("text/plain", c.field);
|
|
1457
|
+
},
|
|
1458
|
+
onDragOver: (e) => e.preventDefault(),
|
|
1459
|
+
onDrop: (e) => {
|
|
1460
|
+
e.preventDefault();
|
|
1461
|
+
if (drag.current) move(drag.current, i);
|
|
1462
|
+
drag.current = null;
|
|
1463
|
+
},
|
|
1464
|
+
onDragEnd: () => {
|
|
1465
|
+
drag.current = null;
|
|
1466
|
+
},
|
|
1467
|
+
children: [
|
|
1468
|
+
/* @__PURE__ */ jsx3("span", { "aria-hidden": "true", children: "\u283F" }),
|
|
1469
|
+
/* @__PURE__ */ jsx3(
|
|
1470
|
+
"button",
|
|
1471
|
+
{
|
|
1472
|
+
type: "button",
|
|
1473
|
+
className: "qt-column-name",
|
|
1474
|
+
onClick: () => browse(c.field),
|
|
1475
|
+
children: f?.label ?? c.field
|
|
1476
|
+
}
|
|
1477
|
+
),
|
|
1478
|
+
/* @__PURE__ */ jsx3(
|
|
1479
|
+
"button",
|
|
1480
|
+
{
|
|
1481
|
+
type: "button",
|
|
1482
|
+
className: "qt-icon-btn",
|
|
1483
|
+
disabled: i === 0,
|
|
1484
|
+
onClick: () => move(c.field, i - 1),
|
|
1485
|
+
"aria-label": `Move ${f?.label ?? c.field} up`,
|
|
1486
|
+
children: "\u2191"
|
|
1487
|
+
}
|
|
1488
|
+
),
|
|
1489
|
+
/* @__PURE__ */ jsx3(
|
|
1490
|
+
"button",
|
|
1491
|
+
{
|
|
1492
|
+
type: "button",
|
|
1493
|
+
className: "qt-icon-btn",
|
|
1494
|
+
disabled: i === columns.length - 1,
|
|
1495
|
+
onClick: () => move(c.field, i + 1),
|
|
1496
|
+
"aria-label": `Move ${f?.label ?? c.field} down`,
|
|
1497
|
+
children: "\u2193"
|
|
1498
|
+
}
|
|
1499
|
+
),
|
|
1500
|
+
/* @__PURE__ */ jsx3(
|
|
1501
|
+
"button",
|
|
1502
|
+
{
|
|
1503
|
+
type: "button",
|
|
1504
|
+
className: "qt-icon-btn",
|
|
1505
|
+
onClick: () => toggle(c.field),
|
|
1506
|
+
"aria-label": `Remove ${f?.label ?? c.field}`,
|
|
1507
|
+
children: "\xD7"
|
|
1508
|
+
}
|
|
1509
|
+
)
|
|
1510
|
+
]
|
|
1511
|
+
},
|
|
1512
|
+
c.field
|
|
1513
|
+
);
|
|
1514
|
+
}) }),
|
|
1515
|
+
/* @__PURE__ */ jsx3(
|
|
1516
|
+
"button",
|
|
1517
|
+
{
|
|
1518
|
+
type: "button",
|
|
1519
|
+
className: "qt-link-btn",
|
|
1520
|
+
onClick: () => setColumns(
|
|
1521
|
+
api.defaults.select.length ? api.defaults.select : catalogue.filter((f) => f.select?.default).map((f) => ({ field: f.name }))
|
|
1522
|
+
),
|
|
1523
|
+
children: "Use default columns"
|
|
1524
|
+
}
|
|
1525
|
+
)
|
|
1526
|
+
] }),
|
|
1527
|
+
/* @__PURE__ */ jsxs3("aside", { className: "qt-catalogue-panel", children: [
|
|
1528
|
+
/* @__PURE__ */ jsx3("h3", { children: "Column catalogue" }),
|
|
1529
|
+
/* @__PURE__ */ jsx3(
|
|
1530
|
+
"input",
|
|
1531
|
+
{
|
|
1532
|
+
className: "qt-input",
|
|
1533
|
+
"aria-label": "Search columns",
|
|
1534
|
+
placeholder: "Search name or type\u2026",
|
|
1535
|
+
value: search,
|
|
1536
|
+
onChange: (e) => setSearch(e.target.value)
|
|
1537
|
+
}
|
|
1538
|
+
),
|
|
1539
|
+
/* @__PURE__ */ jsx3(
|
|
1540
|
+
"button",
|
|
1541
|
+
{
|
|
1542
|
+
type: "button",
|
|
1543
|
+
className: "qt-btn qt-create-computed",
|
|
1544
|
+
onClick: () => edit(),
|
|
1545
|
+
children: "\uFF0B Computed column"
|
|
1546
|
+
}
|
|
1547
|
+
),
|
|
1548
|
+
api.computed.loading && /* @__PURE__ */ jsx3("p", { role: "status", children: "Loading definitions\u2026" }),
|
|
1549
|
+
api.computed.error && /* @__PURE__ */ jsx3("p", { role: "alert", children: api.computed.error }),
|
|
1550
|
+
/* @__PURE__ */ jsx3("div", { className: "qt-catalogue-list", children: catalogue.filter(
|
|
1551
|
+
(f) => `${f.label} ${f.name} ${f.type} ${f.group ?? ""}`.toLowerCase().includes(search.toLowerCase())
|
|
1552
|
+
).map((f) => /* @__PURE__ */ jsxs3(
|
|
1553
|
+
"div",
|
|
1554
|
+
{
|
|
1555
|
+
className: `qt-catalogue-item ${active === f.name && !editing ? "is-active" : ""}`,
|
|
1556
|
+
children: [
|
|
1557
|
+
/* @__PURE__ */ jsx3(
|
|
1558
|
+
"input",
|
|
1559
|
+
{
|
|
1560
|
+
type: "checkbox",
|
|
1561
|
+
"aria-label": `Include ${f.label}`,
|
|
1562
|
+
checked: columns.some((c) => c.field === f.name),
|
|
1563
|
+
onChange: () => toggle(f.name)
|
|
1564
|
+
}
|
|
1565
|
+
),
|
|
1566
|
+
/* @__PURE__ */ jsxs3("button", { type: "button", onClick: () => browse(f.name), children: [
|
|
1567
|
+
/* @__PURE__ */ jsx3("strong", { children: f.label }),
|
|
1568
|
+
/* @__PURE__ */ jsxs3("small", { children: [
|
|
1569
|
+
isComputedField(f.name) ? "\u0192 \xB7 " : "",
|
|
1570
|
+
f.type,
|
|
1571
|
+
" \xB7 ",
|
|
1572
|
+
f.group ?? f.name
|
|
1573
|
+
] })
|
|
1574
|
+
] })
|
|
1575
|
+
]
|
|
1576
|
+
},
|
|
1577
|
+
f.name
|
|
1578
|
+
)) }),
|
|
1579
|
+
/* @__PURE__ */ jsx3(
|
|
1580
|
+
"button",
|
|
1581
|
+
{
|
|
1582
|
+
type: "button",
|
|
1583
|
+
className: "qt-link-btn",
|
|
1584
|
+
onClick: () => void api.computed.reload(),
|
|
1585
|
+
children: "Reload shared definitions"
|
|
1586
|
+
}
|
|
1587
|
+
)
|
|
1588
|
+
] }),
|
|
1589
|
+
/* @__PURE__ */ jsxs3("section", { className: "qt-column-workbench", children: [
|
|
1590
|
+
editing ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1591
|
+
/* @__PURE__ */ jsxs3("div", { className: "qt-editor-title", children: [
|
|
1592
|
+
/* @__PURE__ */ jsx3("h3", { children: revision ? "Edit shared computed column" : "New computed column" }),
|
|
1593
|
+
/* @__PURE__ */ jsx3("span", { className: "qt-type-badge", children: validation.plan?.type ?? "Check formula" })
|
|
1594
|
+
] }),
|
|
1595
|
+
/* @__PURE__ */ jsxs3("label", { className: "qt-editor-label", children: [
|
|
1596
|
+
"Column name",
|
|
1597
|
+
/* @__PURE__ */ jsx3(
|
|
1598
|
+
"input",
|
|
1599
|
+
{
|
|
1600
|
+
className: "qt-input",
|
|
1601
|
+
value: label,
|
|
1602
|
+
maxLength: 200,
|
|
1603
|
+
onChange: (e) => setLabel(e.target.value),
|
|
1604
|
+
placeholder: "e.g. Email domain"
|
|
1605
|
+
}
|
|
1606
|
+
)
|
|
1607
|
+
] }),
|
|
1608
|
+
/* @__PURE__ */ jsx3(
|
|
1609
|
+
Suspense,
|
|
1610
|
+
{
|
|
1611
|
+
fallback: /* @__PURE__ */ jsx3(
|
|
1612
|
+
"textarea",
|
|
1613
|
+
{
|
|
1614
|
+
"aria-label": "Computed column formula",
|
|
1615
|
+
value: source,
|
|
1616
|
+
onChange: (e) => setSource(e.target.value)
|
|
1617
|
+
}
|
|
1618
|
+
),
|
|
1619
|
+
children: /* @__PURE__ */ jsx3(
|
|
1620
|
+
FormulaEditor,
|
|
1621
|
+
{
|
|
1622
|
+
value: source,
|
|
1623
|
+
onChange: setSource,
|
|
1624
|
+
fields: api.computed.catalogue.filter(
|
|
1625
|
+
(f) => f.name !== computedFieldName(id)
|
|
1626
|
+
),
|
|
1627
|
+
compile: (value) => api.computed.compile(value, id)
|
|
1628
|
+
}
|
|
1629
|
+
)
|
|
1630
|
+
}
|
|
1631
|
+
),
|
|
1632
|
+
/* @__PURE__ */ jsxs3("details", { className: "qt-function-library", children: [
|
|
1633
|
+
/* @__PURE__ */ jsx3("summary", { children: "Functions & examples" }),
|
|
1634
|
+
/* @__PURE__ */ jsx3(
|
|
1635
|
+
"input",
|
|
1636
|
+
{
|
|
1637
|
+
className: "qt-input",
|
|
1638
|
+
"aria-label": "Search functions",
|
|
1639
|
+
placeholder: "Search functions\u2026",
|
|
1640
|
+
value: functionSearch,
|
|
1641
|
+
onChange: (e) => setFunctionSearch(e.target.value)
|
|
1642
|
+
}
|
|
1643
|
+
),
|
|
1644
|
+
/* @__PURE__ */ jsx3("div", { className: "qt-function-list", children: FORMULA_FUNCTIONS.filter(
|
|
1645
|
+
(f) => `${f.name} ${f.description}`.toLowerCase().includes(functionSearch.toLowerCase())
|
|
1646
|
+
).map((f) => /* @__PURE__ */ jsxs3(
|
|
1647
|
+
"button",
|
|
1648
|
+
{
|
|
1649
|
+
type: "button",
|
|
1650
|
+
title: f.description,
|
|
1651
|
+
onClick: () => setSource(`${f.name}(${source})`),
|
|
1652
|
+
children: [
|
|
1653
|
+
/* @__PURE__ */ jsx3("code", { children: f.signature }),
|
|
1654
|
+
/* @__PURE__ */ jsx3("small", { children: f.description })
|
|
1655
|
+
]
|
|
1656
|
+
},
|
|
1657
|
+
f.name
|
|
1658
|
+
)) }),
|
|
1659
|
+
/* @__PURE__ */ jsxs3("p", { children: [
|
|
1660
|
+
"Examples: ",
|
|
1661
|
+
/* @__PURE__ */ jsx3("code", { children: "LEFT([name], 3)" }),
|
|
1662
|
+
" \xB7",
|
|
1663
|
+
" ",
|
|
1664
|
+
/* @__PURE__ */ jsx3("code", { children: 'IF([amount] > 100, "High", "Low")' }),
|
|
1665
|
+
" \xB7",
|
|
1666
|
+
" ",
|
|
1667
|
+
/* @__PURE__ */ jsx3("code", { children: 'REGEX_EXTRACT([email], "@(.+)$", 1)' })
|
|
1668
|
+
] })
|
|
1669
|
+
] }),
|
|
1670
|
+
/* @__PURE__ */ jsxs3("div", { className: "qt-definition-actions", children: [
|
|
1671
|
+
/* @__PURE__ */ jsx3(
|
|
1672
|
+
"button",
|
|
1673
|
+
{
|
|
1674
|
+
type: "button",
|
|
1675
|
+
className: "qt-btn",
|
|
1676
|
+
disabled: saving || !!validation.error || !label.trim(),
|
|
1677
|
+
onClick: () => void save(),
|
|
1678
|
+
children: saving ? "Saving\u2026" : revision ? "Save shared definition" : "Save definition & add column"
|
|
1679
|
+
}
|
|
1680
|
+
),
|
|
1681
|
+
revision && /* @__PURE__ */ jsx3(
|
|
1682
|
+
"button",
|
|
1683
|
+
{
|
|
1684
|
+
type: "button",
|
|
1685
|
+
className: "qt-link-btn",
|
|
1686
|
+
onClick: () => edit(id),
|
|
1687
|
+
children: "Reload editor from catalogue"
|
|
1688
|
+
}
|
|
1689
|
+
)
|
|
1690
|
+
] }),
|
|
1691
|
+
/* @__PURE__ */ jsx3("p", { className: "qt-muted", children: "Saving changes this definition across all queries. Cancel below only discards column layout changes." })
|
|
1692
|
+
] }) : /* @__PURE__ */ jsxs3("div", { className: "qt-editor-title", children: [
|
|
1693
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
1694
|
+
/* @__PURE__ */ jsx3("h3", { children: chosen?.label ?? "Explore a column" }),
|
|
1695
|
+
/* @__PURE__ */ jsxs3("p", { className: "qt-muted", children: [
|
|
1696
|
+
chosen?.name,
|
|
1697
|
+
" ",
|
|
1698
|
+
chosen ? `\xB7 ${chosen.type}` : ""
|
|
1699
|
+
] })
|
|
1700
|
+
] }),
|
|
1701
|
+
chosen && isComputedField(chosen.name) && /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1702
|
+
/* @__PURE__ */ jsx3(
|
|
1703
|
+
"button",
|
|
1704
|
+
{
|
|
1705
|
+
type: "button",
|
|
1706
|
+
className: "qt-btn",
|
|
1707
|
+
onClick: () => edit(chosen.name.slice(10)),
|
|
1708
|
+
children: "Edit definition"
|
|
1709
|
+
}
|
|
1710
|
+
),
|
|
1711
|
+
/* @__PURE__ */ jsx3(
|
|
1712
|
+
"button",
|
|
1713
|
+
{
|
|
1714
|
+
type: "button",
|
|
1715
|
+
className: "qt-btn",
|
|
1716
|
+
onClick: () => {
|
|
1717
|
+
const def = api.computed.definitions.find(
|
|
1718
|
+
(d) => computedFieldName(d.id) === chosen.name
|
|
1719
|
+
);
|
|
1720
|
+
edit();
|
|
1721
|
+
if (def) {
|
|
1722
|
+
setLabel(`${def.label} copy`);
|
|
1723
|
+
setSource(def.expression.source);
|
|
1724
|
+
}
|
|
1725
|
+
},
|
|
1726
|
+
children: "Duplicate"
|
|
1727
|
+
}
|
|
1728
|
+
)
|
|
1729
|
+
] })
|
|
1730
|
+
] }),
|
|
1731
|
+
/* @__PURE__ */ jsxs3("div", { className: "qt-preview-controls", children: [
|
|
1732
|
+
/* @__PURE__ */ jsxs3("label", { children: [
|
|
1733
|
+
"Rows to process",
|
|
1734
|
+
" ",
|
|
1735
|
+
/* @__PURE__ */ jsx3(
|
|
1736
|
+
"input",
|
|
1737
|
+
{
|
|
1738
|
+
className: "qt-input",
|
|
1739
|
+
type: "number",
|
|
1740
|
+
min: 1,
|
|
1741
|
+
max: 1e4,
|
|
1742
|
+
step: 1,
|
|
1743
|
+
value: Number.isNaN(count) ? "" : count,
|
|
1744
|
+
onChange: (e) => setCount(e.target.valueAsNumber)
|
|
1745
|
+
}
|
|
1746
|
+
)
|
|
1747
|
+
] }),
|
|
1748
|
+
/* @__PURE__ */ jsx3(
|
|
1749
|
+
"button",
|
|
1750
|
+
{
|
|
1751
|
+
type: "button",
|
|
1752
|
+
className: "qt-btn",
|
|
1753
|
+
onClick: () => setRetry((n) => n + 1),
|
|
1754
|
+
children: "Refresh preview"
|
|
1755
|
+
}
|
|
1756
|
+
),
|
|
1757
|
+
/* @__PURE__ */ jsxs3("label", { children: [
|
|
1758
|
+
"Show",
|
|
1759
|
+
" ",
|
|
1760
|
+
/* @__PURE__ */ jsxs3(
|
|
1761
|
+
"select",
|
|
1762
|
+
{
|
|
1763
|
+
value: filter,
|
|
1764
|
+
onChange: (e) => {
|
|
1765
|
+
setFilter(e.target.value);
|
|
1766
|
+
setPage(0);
|
|
1767
|
+
},
|
|
1768
|
+
children: [
|
|
1769
|
+
/* @__PURE__ */ jsx3("option", { value: "all", children: "All results" }),
|
|
1770
|
+
/* @__PURE__ */ jsx3("option", { value: "nulls", children: "Null results" }),
|
|
1771
|
+
/* @__PURE__ */ jsx3("option", { value: "errors", children: "Errors" })
|
|
1772
|
+
]
|
|
1773
|
+
}
|
|
1774
|
+
)
|
|
1775
|
+
] })
|
|
1776
|
+
] }),
|
|
1777
|
+
/* @__PURE__ */ jsx3("p", { className: "qt-muted", children: "First matching rows in current sort order, starting at row 1. Counts describe the processed sample. Maximum 10,000 rows." }),
|
|
1778
|
+
(!Number.isInteger(count) || count < 1 || count > 1e4) && /* @__PURE__ */ jsx3("p", { role: "alert", children: "Choose a whole number between 1 and 10,000." }),
|
|
1779
|
+
validation.error && /* @__PURE__ */ jsx3("p", { className: "qt-formula-error", role: "alert", children: validation.error }),
|
|
1780
|
+
error && /* @__PURE__ */ jsx3("p", { className: "qt-formula-error", role: "alert", children: error }),
|
|
1781
|
+
notice && /* @__PURE__ */ jsx3("p", { className: "qt-editor-notice", role: "status", children: notice }),
|
|
1782
|
+
loading && /* @__PURE__ */ jsx3("p", { role: "status", children: "Processing preview\u2026" }),
|
|
1783
|
+
preview && /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1784
|
+
/* @__PURE__ */ jsxs3("p", { className: "qt-preview-summary", role: "status", children: [
|
|
1785
|
+
/* @__PURE__ */ jsx3("strong", { children: preview.processed.toLocaleString() }),
|
|
1786
|
+
" of",
|
|
1787
|
+
" ",
|
|
1788
|
+
preview.total.toLocaleString(),
|
|
1789
|
+
" matching rows \xB7",
|
|
1790
|
+
" ",
|
|
1791
|
+
/* @__PURE__ */ jsx3("strong", { children: preview.groups.length.toLocaleString() }),
|
|
1792
|
+
" ",
|
|
1793
|
+
"distinct combinations \xB7 ",
|
|
1794
|
+
preview.nulls.toLocaleString(),
|
|
1795
|
+
" null \xB7 ",
|
|
1796
|
+
preview.errors.toLocaleString(),
|
|
1797
|
+
" errors",
|
|
1798
|
+
preview.processed === preview.total ? " \xB7 complete filtered set" : " \xB7 sample"
|
|
1799
|
+
] }),
|
|
1800
|
+
/* @__PURE__ */ jsx3("div", { className: "qt-preview-table-wrap", children: /* @__PURE__ */ jsxs3("table", { className: "qt-preview-table", children: [
|
|
1801
|
+
/* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsxs3("tr", { children: [
|
|
1802
|
+
editing && preview.dependencies.map((name, index) => /* @__PURE__ */ jsx3(
|
|
1803
|
+
"th",
|
|
1804
|
+
{
|
|
1805
|
+
"aria-sort": previewAriaSort({
|
|
1806
|
+
kind: "input",
|
|
1807
|
+
index
|
|
1808
|
+
}),
|
|
1809
|
+
children: previewSortHeader(
|
|
1810
|
+
api.computed.catalogue.find(
|
|
1811
|
+
(f) => f.name === name
|
|
1812
|
+
)?.label ?? name,
|
|
1813
|
+
{ kind: "input", index }
|
|
1814
|
+
)
|
|
1815
|
+
},
|
|
1816
|
+
name
|
|
1817
|
+
)),
|
|
1818
|
+
/* @__PURE__ */ jsx3("th", { "aria-sort": previewAriaSort({ kind: "result" }), children: previewSortHeader(editing ? "Result" : "Value", {
|
|
1819
|
+
kind: "result"
|
|
1820
|
+
}) }),
|
|
1821
|
+
/* @__PURE__ */ jsx3("th", { "aria-sort": previewAriaSort({ kind: "count" }), children: previewSortHeader("Count", { kind: "count" }, "desc") }),
|
|
1822
|
+
/* @__PURE__ */ jsx3("th", { "aria-sort": previewAriaSort({ kind: "percentage" }), children: previewSortHeader("%", { kind: "percentage" }, "desc") })
|
|
1823
|
+
] }) }),
|
|
1824
|
+
/* @__PURE__ */ jsx3("tbody", { children: shown.map((group, i) => /* @__PURE__ */ jsxs3("tr", { children: [
|
|
1825
|
+
editing && group.inputs.map((value, j) => /* @__PURE__ */ jsx3("td", { title: display(value), children: display(value) }, j)),
|
|
1826
|
+
/* @__PURE__ */ jsxs3(
|
|
1827
|
+
"td",
|
|
1828
|
+
{
|
|
1829
|
+
className: group.result.error ? "qt-formula-error" : "",
|
|
1830
|
+
title: group.result.error ?? display(group.result.value),
|
|
1831
|
+
children: [
|
|
1832
|
+
group.result.error ?? display(group.result.value),
|
|
1833
|
+
group.result.regex && /* @__PURE__ */ jsx3(
|
|
1834
|
+
"button",
|
|
1835
|
+
{
|
|
1836
|
+
type: "button",
|
|
1837
|
+
className: "qt-link-btn",
|
|
1838
|
+
onClick: () => setInspection(group.result.regex),
|
|
1839
|
+
children: "Inspect match"
|
|
1840
|
+
}
|
|
1841
|
+
)
|
|
1842
|
+
]
|
|
1843
|
+
}
|
|
1844
|
+
),
|
|
1845
|
+
/* @__PURE__ */ jsx3("td", { children: group.count.toLocaleString() }),
|
|
1846
|
+
/* @__PURE__ */ jsxs3("td", { children: [
|
|
1847
|
+
(100 * group.count / preview.processed).toFixed(
|
|
1848
|
+
1
|
|
1849
|
+
),
|
|
1850
|
+
"%"
|
|
1851
|
+
] })
|
|
1852
|
+
] }, i)) })
|
|
1853
|
+
] }) }),
|
|
1854
|
+
inspection && /* @__PURE__ */ jsxs3("div", { className: "qt-regex-inspector", children: [
|
|
1855
|
+
/* @__PURE__ */ jsx3("h4", { children: "Regex match" }),
|
|
1856
|
+
/* @__PURE__ */ jsxs3("pre", { children: [
|
|
1857
|
+
inspection.input.slice(0, inspection.start),
|
|
1858
|
+
/* @__PURE__ */ jsx3("mark", { children: inspection.input.slice(
|
|
1859
|
+
inspection.start,
|
|
1860
|
+
inspection.end
|
|
1861
|
+
) }),
|
|
1862
|
+
inspection.input.slice(inspection.end)
|
|
1863
|
+
] }),
|
|
1864
|
+
/* @__PURE__ */ jsx3("dl", { children: inspection.groups.map((value, i) => /* @__PURE__ */ jsxs3("div", { children: [
|
|
1865
|
+
/* @__PURE__ */ jsxs3("dt", { children: [
|
|
1866
|
+
"Group ",
|
|
1867
|
+
i,
|
|
1868
|
+
i === 0 ? " (whole match)" : ""
|
|
1869
|
+
] }),
|
|
1870
|
+
/* @__PURE__ */ jsx3("dd", { children: display(value) })
|
|
1871
|
+
] }, i)) })
|
|
1872
|
+
] }),
|
|
1873
|
+
!groups.length && /* @__PURE__ */ jsx3("p", { children: "No matching results in this sample." }),
|
|
1874
|
+
groups.length > 50 && /* @__PURE__ */ jsxs3("div", { className: "qt-preview-pages", children: [
|
|
1875
|
+
/* @__PURE__ */ jsx3(
|
|
1876
|
+
"button",
|
|
1877
|
+
{
|
|
1878
|
+
type: "button",
|
|
1879
|
+
className: "qt-btn",
|
|
1880
|
+
disabled: page === 0,
|
|
1881
|
+
onClick: () => setPage((n) => n - 1),
|
|
1882
|
+
children: "Previous"
|
|
1883
|
+
}
|
|
1884
|
+
),
|
|
1885
|
+
/* @__PURE__ */ jsxs3("span", { children: [
|
|
1886
|
+
"Combinations ",
|
|
1887
|
+
page * 50 + 1,
|
|
1888
|
+
"\u2013",
|
|
1889
|
+
Math.min((page + 1) * 50, groups.length),
|
|
1890
|
+
" of",
|
|
1891
|
+
" ",
|
|
1892
|
+
groups.length
|
|
1893
|
+
] }),
|
|
1894
|
+
/* @__PURE__ */ jsx3(
|
|
1895
|
+
"button",
|
|
1896
|
+
{
|
|
1897
|
+
type: "button",
|
|
1898
|
+
className: "qt-btn",
|
|
1899
|
+
disabled: (page + 1) * 50 >= groups.length,
|
|
1900
|
+
onClick: () => setPage((n) => n + 1),
|
|
1901
|
+
children: "Next"
|
|
1902
|
+
}
|
|
1903
|
+
)
|
|
1904
|
+
] })
|
|
1905
|
+
] })
|
|
1906
|
+
] })
|
|
1907
|
+
] }),
|
|
1908
|
+
/* @__PURE__ */ jsxs3("footer", { className: "qt-select-editor-footer", children: [
|
|
1909
|
+
/* @__PURE__ */ jsx3("span", { className: "qt-muted", children: "Computed columns are display-only. Select at least one column." }),
|
|
1910
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "qt-btn", onClick: onClose, children: "Cancel layout changes" }),
|
|
1911
|
+
/* @__PURE__ */ jsx3(
|
|
1912
|
+
"button",
|
|
1913
|
+
{
|
|
1914
|
+
type: "button",
|
|
1915
|
+
className: "qt-btn qt-primary",
|
|
1916
|
+
disabled: !columns.length || columns.length > 200,
|
|
1917
|
+
onClick: () => {
|
|
1918
|
+
api.setQuery((q) => ({ ...q, select: columns }));
|
|
1919
|
+
onClose();
|
|
1920
|
+
},
|
|
1921
|
+
children: "Apply columns"
|
|
1922
|
+
}
|
|
1923
|
+
)
|
|
1924
|
+
] })
|
|
1925
|
+
]
|
|
1926
|
+
}
|
|
1927
|
+
) });
|
|
1928
|
+
}
|
|
1929
|
+
|
|
976
1930
|
// src/QueryBuilder.tsx
|
|
977
|
-
import { useEffect as
|
|
1931
|
+
import { Fragment as Fragment5, useEffect as useEffect5, useId as useId2, useMemo as useMemo4, useRef as useRef3, useState as useState5 } from "react";
|
|
978
1932
|
import {
|
|
979
1933
|
NULLARY_OPS as NULLARY_OPS2,
|
|
980
1934
|
aggOpNeedsField,
|
|
981
1935
|
aggOpsForField,
|
|
1936
|
+
encodeQuery,
|
|
982
1937
|
filterValues as filterValuesFor,
|
|
983
1938
|
isGroupable,
|
|
984
1939
|
isMeasurable,
|
|
1940
|
+
isOrGroup,
|
|
1941
|
+
opPairsForField as opPairsForField2,
|
|
1942
|
+
predicatesOf,
|
|
985
1943
|
queriesEqual,
|
|
986
|
-
isFilterable,
|
|
987
|
-
isSelectable,
|
|
1944
|
+
isFilterable as isFilterable2,
|
|
1945
|
+
isSelectable as isSelectable2,
|
|
988
1946
|
isSortable as isSortable2,
|
|
989
1947
|
opsForField as opsForField2
|
|
990
1948
|
} from "@pythia-software/query-table-core";
|
|
991
1949
|
|
|
992
1950
|
// src/FieldPicker.tsx
|
|
993
|
-
import { useMemo as
|
|
994
|
-
import { Fragment as
|
|
1951
|
+
import { useMemo as useMemo3, useState as useState4 } from "react";
|
|
1952
|
+
import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
995
1953
|
var UNGROUPED = "other";
|
|
996
1954
|
function FieldPicker({
|
|
997
1955
|
fields,
|
|
@@ -1001,10 +1959,10 @@ function FieldPicker({
|
|
|
1001
1959
|
onPick,
|
|
1002
1960
|
onClose
|
|
1003
1961
|
}) {
|
|
1004
|
-
const [search, setSearch] =
|
|
1962
|
+
const [search, setSearch] = useState4("");
|
|
1005
1963
|
const needle = search.trim().toLowerCase();
|
|
1006
|
-
const excludedSet =
|
|
1007
|
-
const matched =
|
|
1964
|
+
const excludedSet = useMemo3(() => new Set(excluded ?? []), [excluded]);
|
|
1965
|
+
const matched = useMemo3(
|
|
1008
1966
|
() => fields.filter((f) => {
|
|
1009
1967
|
if (excludedSet.has(f.name)) return false;
|
|
1010
1968
|
if (!needle) return true;
|
|
@@ -1012,10 +1970,10 @@ function FieldPicker({
|
|
|
1012
1970
|
}),
|
|
1013
1971
|
[fields, excludedSet, needle]
|
|
1014
1972
|
);
|
|
1015
|
-
const grouped =
|
|
1973
|
+
const grouped = useMemo3(() => groupByGroup(matched), [matched]);
|
|
1016
1974
|
const firstEnabled = matched.find((f) => isEnabled(f, stats, gateOnDistinct));
|
|
1017
|
-
return /* @__PURE__ */
|
|
1018
|
-
/* @__PURE__ */
|
|
1975
|
+
return /* @__PURE__ */ jsxs4("div", { className: "qt-picker", children: [
|
|
1976
|
+
/* @__PURE__ */ jsx4(
|
|
1019
1977
|
"input",
|
|
1020
1978
|
{
|
|
1021
1979
|
autoFocus: true,
|
|
@@ -1034,40 +1992,40 @@ function FieldPicker({
|
|
|
1034
1992
|
onBlur: () => setTimeout(() => onClose?.(), 150)
|
|
1035
1993
|
}
|
|
1036
1994
|
),
|
|
1037
|
-
/* @__PURE__ */
|
|
1038
|
-
/* @__PURE__ */
|
|
1995
|
+
/* @__PURE__ */ jsx4("div", { className: "qt-picker-list", children: matched.length === 0 ? /* @__PURE__ */ jsx4("div", { className: "qt-picker-empty", children: "no matches" }) : grouped.map(([group, fs]) => /* @__PURE__ */ jsxs4("div", { className: "qt-picker-group", children: [
|
|
1996
|
+
/* @__PURE__ */ jsx4("div", { className: "qt-picker-group-label", children: group }),
|
|
1039
1997
|
fs.map((f) => {
|
|
1040
1998
|
const enabled = isEnabled(f, stats, gateOnDistinct);
|
|
1041
1999
|
const s = stats?.[f.name];
|
|
1042
2000
|
const aliasHit = aliasMatch(f, needle);
|
|
1043
|
-
return /* @__PURE__ */
|
|
2001
|
+
return /* @__PURE__ */ jsxs4(
|
|
1044
2002
|
"button",
|
|
1045
2003
|
{
|
|
1046
2004
|
type: "button",
|
|
1047
2005
|
disabled: !enabled,
|
|
1048
2006
|
className: enabled ? "qt-picker-item" : "qt-picker-item qt-picker-item--disabled",
|
|
1049
2007
|
title: enabled ? f.name : "not useful as a filter (\u22641 distinct value)",
|
|
1050
|
-
|
|
2008
|
+
onPointerDown: (e) => {
|
|
1051
2009
|
if (!enabled) return;
|
|
1052
2010
|
e.preventDefault();
|
|
1053
2011
|
onPick(f);
|
|
1054
2012
|
setSearch("");
|
|
1055
2013
|
},
|
|
1056
2014
|
children: [
|
|
1057
|
-
/* @__PURE__ */
|
|
1058
|
-
/* @__PURE__ */
|
|
1059
|
-
/* @__PURE__ */
|
|
2015
|
+
/* @__PURE__ */ jsxs4("span", { className: "qt-picker-row", children: [
|
|
2016
|
+
/* @__PURE__ */ jsx4("span", { className: "qt-picker-label", children: f.label }),
|
|
2017
|
+
/* @__PURE__ */ jsx4("span", { className: "qt-picker-name", children: f.name })
|
|
1060
2018
|
] }),
|
|
1061
|
-
aliasHit && /* @__PURE__ */
|
|
2019
|
+
aliasHit && /* @__PURE__ */ jsxs4("span", { className: "qt-picker-alias", children: [
|
|
1062
2020
|
"matches: ",
|
|
1063
2021
|
aliasHit
|
|
1064
2022
|
] }),
|
|
1065
|
-
s && /* @__PURE__ */
|
|
1066
|
-
s.distinct != null && /* @__PURE__ */
|
|
2023
|
+
s && /* @__PURE__ */ jsxs4("span", { className: "qt-picker-stats", children: [
|
|
2024
|
+
s.distinct != null && /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1067
2025
|
s.distinct,
|
|
1068
2026
|
" distinct"
|
|
1069
2027
|
] }),
|
|
1070
|
-
s.min != null && s.max != null && /* @__PURE__ */
|
|
2028
|
+
s.min != null && s.max != null && /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1071
2029
|
" ",
|
|
1072
2030
|
"\xB7 ",
|
|
1073
2031
|
fmtStat(s.min),
|
|
@@ -1121,86 +2079,97 @@ function fmtStat(v) {
|
|
|
1121
2079
|
}
|
|
1122
2080
|
|
|
1123
2081
|
// src/SavedQueriesModal.tsx
|
|
1124
|
-
import { useEffect as
|
|
1125
|
-
import { jsx as
|
|
2082
|
+
import { useEffect as useEffect4 } from "react";
|
|
2083
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1126
2084
|
function SavedQueriesModal({ saved, onClose, onLoad }) {
|
|
1127
|
-
|
|
2085
|
+
useEffect4(() => {
|
|
1128
2086
|
function onKey(e) {
|
|
1129
2087
|
if (e.key === "Escape") onClose();
|
|
1130
2088
|
}
|
|
1131
|
-
window.addEventListener("keydown", onKey);
|
|
1132
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
1133
|
-
}, [onClose]);
|
|
1134
|
-
return /* @__PURE__ */
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
2089
|
+
window.addEventListener("keydown", onKey);
|
|
2090
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
2091
|
+
}, [onClose]);
|
|
2092
|
+
return /* @__PURE__ */ jsx5("div", { className: "qt-modal-backdrop", onClick: onClose, children: /* @__PURE__ */ jsxs5(
|
|
2093
|
+
"div",
|
|
2094
|
+
{
|
|
2095
|
+
className: "qt-modal",
|
|
2096
|
+
onClick: (e) => e.stopPropagation(),
|
|
2097
|
+
role: "dialog",
|
|
2098
|
+
"aria-modal": "true",
|
|
2099
|
+
"aria-label": "Saved queries",
|
|
2100
|
+
children: [
|
|
2101
|
+
/* @__PURE__ */ jsx5("h3", { className: "qt-modal-title", children: "Saved queries" }),
|
|
2102
|
+
saved.loading ? /* @__PURE__ */ jsx5("p", { className: "qt-muted", children: "Loading\u2026" }) : saved.items.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "qt-muted", children: "None saved yet. Save the current query from the toolbar." }) : /* @__PURE__ */ jsx5("ul", { className: "qt-saved-list", children: saved.items.map((s) => {
|
|
2103
|
+
const filters = s.query.where.length;
|
|
2104
|
+
const isDefault = saved.defaultId === s.id;
|
|
2105
|
+
return /* @__PURE__ */ jsxs5("li", { className: "qt-saved-item", children: [
|
|
2106
|
+
/* @__PURE__ */ jsxs5(
|
|
2107
|
+
"button",
|
|
2108
|
+
{
|
|
2109
|
+
type: "button",
|
|
2110
|
+
className: "qt-saved-pick",
|
|
2111
|
+
onClick: () => {
|
|
2112
|
+
onLoad?.(s.id);
|
|
2113
|
+
saved.load(s.id);
|
|
2114
|
+
onClose();
|
|
2115
|
+
},
|
|
2116
|
+
children: [
|
|
2117
|
+
/* @__PURE__ */ jsx5("span", { className: "qt-saved-name", children: s.name }),
|
|
2118
|
+
/* @__PURE__ */ jsxs5("span", { className: "qt-saved-meta qt-muted", children: [
|
|
2119
|
+
isDefault ? "Default view \xB7 " : "",
|
|
2120
|
+
new Date(s.savedAt).toLocaleString(),
|
|
2121
|
+
" \xB7 ",
|
|
2122
|
+
filters,
|
|
2123
|
+
" filter",
|
|
2124
|
+
filters === 1 ? "" : "s"
|
|
2125
|
+
] })
|
|
2126
|
+
]
|
|
2127
|
+
}
|
|
2128
|
+
),
|
|
2129
|
+
isDefault ? /* @__PURE__ */ jsx5(
|
|
2130
|
+
"button",
|
|
2131
|
+
{
|
|
2132
|
+
type: "button",
|
|
2133
|
+
className: "qt-btn qt-saved-default",
|
|
2134
|
+
title: "Clear default view",
|
|
2135
|
+
onClick: () => void saved.clearDefault(),
|
|
2136
|
+
children: "default"
|
|
2137
|
+
}
|
|
2138
|
+
) : /* @__PURE__ */ jsx5(
|
|
2139
|
+
"button",
|
|
2140
|
+
{
|
|
2141
|
+
type: "button",
|
|
2142
|
+
className: "qt-btn qt-saved-default",
|
|
2143
|
+
title: "Use as default view",
|
|
2144
|
+
onClick: () => void saved.setDefault(s.id),
|
|
2145
|
+
children: "make default"
|
|
2146
|
+
}
|
|
2147
|
+
),
|
|
2148
|
+
/* @__PURE__ */ jsx5(
|
|
2149
|
+
"button",
|
|
2150
|
+
{
|
|
2151
|
+
type: "button",
|
|
2152
|
+
className: "qt-btn qt-saved-delete",
|
|
2153
|
+
title: "Delete saved query",
|
|
2154
|
+
onClick: () => void saved.remove(s.id),
|
|
2155
|
+
children: "delete"
|
|
2156
|
+
}
|
|
2157
|
+
)
|
|
2158
|
+
] }, s.id);
|
|
2159
|
+
}) }),
|
|
2160
|
+
/* @__PURE__ */ jsx5("div", { className: "qt-modal-footer", children: /* @__PURE__ */ jsx5("button", { type: "button", className: "qt-btn", onClick: onClose, children: "close" }) })
|
|
2161
|
+
]
|
|
2162
|
+
}
|
|
2163
|
+
) });
|
|
1196
2164
|
}
|
|
1197
2165
|
|
|
1198
2166
|
// src/QueryBuilder.tsx
|
|
1199
|
-
import { jsx as
|
|
2167
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1200
2168
|
var cx3 = (...parts) => parts.filter((p) => Boolean(p)).join(" ");
|
|
1201
2169
|
var MAX_AUTO_REFRESH_POLLS = 1e3;
|
|
1202
2170
|
var DEFAULT_AUTO_REFRESH_FREQUENCY_MS = 2e4;
|
|
1203
2171
|
var DEFAULT_AUTO_REFRESH_TURN_OFF_AFTER_MS = 5 * 6e4;
|
|
2172
|
+
var SHARE_FEEDBACK_MS = 2e3;
|
|
1204
2173
|
var AUTO_REFRESH_FREQUENCIES = [
|
|
1205
2174
|
{ label: "Every 5s", value: 5e3 },
|
|
1206
2175
|
{ label: "Every 10s", value: 1e4 },
|
|
@@ -1239,8 +2208,8 @@ function formatStatusTime(ms) {
|
|
|
1239
2208
|
return new Date(ms).toLocaleTimeString([], { hour: "numeric", minute: "2-digit", second: "2-digit" });
|
|
1240
2209
|
}
|
|
1241
2210
|
function useFieldHasNull(api, fieldName) {
|
|
1242
|
-
const [hasNull, setHasNull] =
|
|
1243
|
-
|
|
2211
|
+
const [hasNull, setHasNull] = useState5(void 0);
|
|
2212
|
+
useEffect5(() => {
|
|
1244
2213
|
if (!fieldName) return;
|
|
1245
2214
|
let cancelled = false;
|
|
1246
2215
|
void api.filterValues(fieldName, "").then((r) => {
|
|
@@ -1256,17 +2225,27 @@ function useFieldHasNull(api, fieldName) {
|
|
|
1256
2225
|
}, [api, fieldName]);
|
|
1257
2226
|
return hasNull;
|
|
1258
2227
|
}
|
|
1259
|
-
|
|
2228
|
+
var CONVERGED_NEGATIVE_OPS = /* @__PURE__ */ new Set(["!=", "is_not_null", "not_matches_regex"]);
|
|
2229
|
+
function positiveOpsForField(field) {
|
|
2230
|
+
return opsForField2(field).filter((op) => !CONVERGED_NEGATIVE_OPS.has(op));
|
|
2231
|
+
}
|
|
2232
|
+
function predicateAsText(clause, byName) {
|
|
1260
2233
|
const field = byName.get(clause.field)?.label ?? clause.field;
|
|
1261
2234
|
const op = clause.op.replace(/_/g, " ");
|
|
1262
|
-
|
|
2235
|
+
const not = clause.negated ? "not " : "";
|
|
2236
|
+
if (NULLARY_OPS2.has(clause.op)) return `${field} ${not}${op}`;
|
|
1263
2237
|
const value = clause.value === "" ? "''" : clause.value;
|
|
1264
|
-
return `${field} ${op} ${value}`;
|
|
2238
|
+
return `${field} ${not}${op} ${value}`;
|
|
2239
|
+
}
|
|
2240
|
+
function whereTermAsText(term, byName) {
|
|
2241
|
+
if (isOrGroup(term)) return `(${term.any.map((c) => predicateAsText(c, byName)).join(" or ")})`;
|
|
2242
|
+
return predicateAsText(term, byName);
|
|
1265
2243
|
}
|
|
1266
2244
|
function orderByAsText(term, byName) {
|
|
1267
2245
|
const field = byName.get(term.field)?.label ?? term.field;
|
|
2246
|
+
const expression = term.extract ? `regex_extract(${field}, /${term.extract.regex}/)` : field;
|
|
1268
2247
|
const nulls = term.nulls ? ` nulls ${term.nulls}` : "";
|
|
1269
|
-
return `${
|
|
2248
|
+
return `${expression} ${term.dir}${nulls}`;
|
|
1270
2249
|
}
|
|
1271
2250
|
function chipFieldWidthChars(text, fallback = 5, max = 30) {
|
|
1272
2251
|
return Math.min(max, Math.max(fallback, (text || "").length + 1));
|
|
@@ -1279,12 +2258,12 @@ function chipFieldWidthForSelect(text, fallback = 3, max = 30) {
|
|
|
1279
2258
|
return chipFieldWidthChars(text, fallback, max) + 2;
|
|
1280
2259
|
}
|
|
1281
2260
|
function buildCollapsedSummary(where, orderBy, byName) {
|
|
1282
|
-
const whereText = where
|
|
2261
|
+
const whereText = whereClauseSummaryText(where, byName);
|
|
1283
2262
|
const orderText = orderBy.length === 0 ? "(default)" : orderBy.map((term) => orderByAsText(term, byName)).join(", ");
|
|
1284
2263
|
return `WHERE ${whereText} ORDER BY ${orderText}`;
|
|
1285
2264
|
}
|
|
1286
2265
|
function whereClauseSummaryText(where, byName) {
|
|
1287
|
-
return where.length === 0 ? "all rows" : where.map((
|
|
2266
|
+
return where.length === 0 ? "all rows" : where.map((t) => whereTermAsText(t, byName)).join(" and ");
|
|
1288
2267
|
}
|
|
1289
2268
|
function orderBySummaryText(orderBy, byName) {
|
|
1290
2269
|
return orderBy.length === 0 ? "(default)" : orderBy.map((term) => orderByAsText(term, byName)).join(", ");
|
|
@@ -1300,14 +2279,15 @@ function QueryBuilder({
|
|
|
1300
2279
|
defaultCollapsed = false
|
|
1301
2280
|
}) {
|
|
1302
2281
|
const UNSAVED_QUERY_EDIT_ID = "__qt-unsaved-query__";
|
|
1303
|
-
const [showSaved, setShowSaved] =
|
|
1304
|
-
const [
|
|
1305
|
-
const [
|
|
1306
|
-
const [
|
|
1307
|
-
const [
|
|
1308
|
-
const [
|
|
1309
|
-
const [
|
|
1310
|
-
const [
|
|
2282
|
+
const [showSaved, setShowSaved] = useState5(false);
|
|
2283
|
+
const [shareCopied, setShareCopied] = useState5(false);
|
|
2284
|
+
const [showAutoRefresh, setShowAutoRefresh] = useState5(false);
|
|
2285
|
+
const [autoRefreshFrequencyMs, setAutoRefreshFrequencyMs] = useState5(DEFAULT_AUTO_REFRESH_FREQUENCY_MS);
|
|
2286
|
+
const [autoRefreshTurnOffAfterMs, setAutoRefreshTurnOffAfterMs] = useState5(DEFAULT_AUTO_REFRESH_TURN_OFF_AFTER_MS);
|
|
2287
|
+
const [editingSavedId, setEditingSavedId] = useState5(null);
|
|
2288
|
+
const [editingSavedName, setEditingSavedName] = useState5("");
|
|
2289
|
+
const [lastSavedId, setLastSavedId] = useState5(null);
|
|
2290
|
+
const [collapsedFallback, setCollapsedFallback] = useState5(defaultCollapsed);
|
|
1311
2291
|
const collapsedControlled = typeof collapsed !== "undefined";
|
|
1312
2292
|
const isCollapsed = collapsedControlled ? collapsed : collapsedFallback;
|
|
1313
2293
|
const setCollapsed = (next) => {
|
|
@@ -1317,31 +2297,31 @@ function QueryBuilder({
|
|
|
1317
2297
|
}
|
|
1318
2298
|
setCollapsedFallback(next);
|
|
1319
2299
|
};
|
|
1320
|
-
const [lastUpdatedAt, setLastUpdatedAt] =
|
|
1321
|
-
const [lastFailureAt, setLastFailureAt] =
|
|
1322
|
-
const byName =
|
|
1323
|
-
const activeSavedQuery =
|
|
2300
|
+
const [lastUpdatedAt, setLastUpdatedAt] = useState5(null);
|
|
2301
|
+
const [lastFailureAt, setLastFailureAt] = useState5(null);
|
|
2302
|
+
const byName = useMemo4(() => new Map(fields.map((f) => [f.name, f])), [fields]);
|
|
2303
|
+
const activeSavedQuery = useMemo4(
|
|
1324
2304
|
() => api.saved.items.find((item) => queriesEqual(item.query, api.query)),
|
|
1325
2305
|
[api.query, api.saved.items]
|
|
1326
2306
|
);
|
|
1327
|
-
const lastSavedQuery =
|
|
2307
|
+
const lastSavedQuery = useMemo4(
|
|
1328
2308
|
() => api.saved.items.find((item) => item.id === lastSavedId) ?? null,
|
|
1329
2309
|
[api.saved.items, lastSavedId]
|
|
1330
2310
|
);
|
|
1331
2311
|
const isSavedQuery = activeSavedQuery != null;
|
|
1332
2312
|
const isEditingUnsaved = editingSavedId === UNSAVED_QUERY_EDIT_ID;
|
|
1333
|
-
const bodyId =
|
|
2313
|
+
const bodyId = useId2();
|
|
1334
2314
|
const isEditingSaved = editingSavedId != null && activeSavedQuery?.id === editingSavedId;
|
|
1335
2315
|
const showUpdateButton = Boolean(lastSavedQuery) && !activeSavedQuery;
|
|
1336
|
-
const collapsedSummary =
|
|
2316
|
+
const collapsedSummary = useMemo4(
|
|
1337
2317
|
() => buildCollapsedSummary(api.query.where, api.query.orderBy, byName),
|
|
1338
2318
|
[api.query.where, api.query.orderBy, byName]
|
|
1339
2319
|
);
|
|
1340
|
-
const collapsedWhereText =
|
|
2320
|
+
const collapsedWhereText = useMemo4(
|
|
1341
2321
|
() => whereClauseSummaryText(api.query.where, byName),
|
|
1342
2322
|
[api.query.where, byName]
|
|
1343
2323
|
);
|
|
1344
|
-
const collapsedOrderText =
|
|
2324
|
+
const collapsedOrderText = useMemo4(
|
|
1345
2325
|
() => orderBySummaryText(api.query.orderBy, byName),
|
|
1346
2326
|
[api.query.orderBy, byName]
|
|
1347
2327
|
);
|
|
@@ -1353,13 +2333,27 @@ function QueryBuilder({
|
|
|
1353
2333
|
const lastUpdatedText = formatStatusTime(lastUpdatedAt);
|
|
1354
2334
|
const failureText = api.error ? `Error: ${api.error.message}` : lastFailureAt ? `Last failure at ${formatStatusTime(lastFailureAt)}` : null;
|
|
1355
2335
|
const isBusy = Boolean(running) || api.loading;
|
|
1356
|
-
const lastRowsRef =
|
|
1357
|
-
const lastTotalRef =
|
|
1358
|
-
const previousLoadingRef =
|
|
1359
|
-
const autoRefreshRef =
|
|
1360
|
-
const autoRefreshPopoverRef =
|
|
1361
|
-
const [autoRefreshPopoverAlignRight, setAutoRefreshPopoverAlignRight] =
|
|
1362
|
-
|
|
2336
|
+
const lastRowsRef = useRef3(null);
|
|
2337
|
+
const lastTotalRef = useRef3(null);
|
|
2338
|
+
const previousLoadingRef = useRef3(api.loading);
|
|
2339
|
+
const autoRefreshRef = useRef3(null);
|
|
2340
|
+
const autoRefreshPopoverRef = useRef3(null);
|
|
2341
|
+
const [autoRefreshPopoverAlignRight, setAutoRefreshPopoverAlignRight] = useState5(false);
|
|
2342
|
+
const shareFeedbackTimerRef = useRef3(null);
|
|
2343
|
+
useEffect5(
|
|
2344
|
+
() => () => {
|
|
2345
|
+
if (shareFeedbackTimerRef.current) clearTimeout(shareFeedbackTimerRef.current);
|
|
2346
|
+
},
|
|
2347
|
+
[]
|
|
2348
|
+
);
|
|
2349
|
+
useEffect5(() => {
|
|
2350
|
+
setShareCopied(false);
|
|
2351
|
+
if (shareFeedbackTimerRef.current) {
|
|
2352
|
+
clearTimeout(shareFeedbackTimerRef.current);
|
|
2353
|
+
shareFeedbackTimerRef.current = null;
|
|
2354
|
+
}
|
|
2355
|
+
}, [api.query]);
|
|
2356
|
+
useEffect5(() => {
|
|
1363
2357
|
if (activeSavedQuery) {
|
|
1364
2358
|
setLastSavedId(activeSavedQuery.id);
|
|
1365
2359
|
if (!isEditingSaved) {
|
|
@@ -1373,11 +2367,11 @@ function QueryBuilder({
|
|
|
1373
2367
|
setEditingSavedName("");
|
|
1374
2368
|
}
|
|
1375
2369
|
}, [activeSavedQuery, isEditingSaved, isEditingUnsaved]);
|
|
1376
|
-
|
|
2370
|
+
useEffect5(() => {
|
|
1377
2371
|
if (!lastSavedId || api.saved.items.some((item) => item.id === lastSavedId)) return;
|
|
1378
2372
|
setLastSavedId(null);
|
|
1379
2373
|
}, [api.saved.items, lastSavedId]);
|
|
1380
|
-
|
|
2374
|
+
useEffect5(() => {
|
|
1381
2375
|
const hasData = api.rows.length > 0 || total != null;
|
|
1382
2376
|
const rowsChanged = lastRowsRef.current !== api.rows;
|
|
1383
2377
|
const totalChanged = lastTotalRef.current !== total;
|
|
@@ -1472,15 +2466,15 @@ function QueryBuilder({
|
|
|
1472
2466
|
return;
|
|
1473
2467
|
}
|
|
1474
2468
|
}
|
|
1475
|
-
|
|
2469
|
+
useEffect5(() => {
|
|
1476
2470
|
if (isCollapsed) setShowSaved(false);
|
|
1477
2471
|
}, [isCollapsed]);
|
|
1478
|
-
|
|
2472
|
+
useEffect5(() => {
|
|
1479
2473
|
if (!showAutoRefresh || !autoRefreshStatus) return;
|
|
1480
2474
|
setAutoRefreshFrequencyMs(autoRefreshStatus.frequencyMs);
|
|
1481
2475
|
setAutoRefreshTurnOffAfterMs(autoRefreshStatus.turnOffAfterMs);
|
|
1482
2476
|
}, [showAutoRefresh, autoRefreshStatus]);
|
|
1483
|
-
|
|
2477
|
+
useEffect5(() => {
|
|
1484
2478
|
if (!showAutoRefresh) return;
|
|
1485
2479
|
function onDocumentClick(event) {
|
|
1486
2480
|
const wrapper = autoRefreshRef.current;
|
|
@@ -1523,11 +2517,27 @@ function QueryBuilder({
|
|
|
1523
2517
|
saveFailed(error);
|
|
1524
2518
|
}
|
|
1525
2519
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
2520
|
+
async function shareQuery() {
|
|
2521
|
+
if (typeof window === "undefined") return;
|
|
2522
|
+
const url = new URL(window.location.href);
|
|
2523
|
+
url.searchParams.set("q", encodeQuery(api.query));
|
|
2524
|
+
try {
|
|
2525
|
+
await navigator.clipboard.writeText(url.toString());
|
|
2526
|
+
setShareCopied(true);
|
|
2527
|
+
if (shareFeedbackTimerRef.current) clearTimeout(shareFeedbackTimerRef.current);
|
|
2528
|
+
shareFeedbackTimerRef.current = setTimeout(() => {
|
|
2529
|
+
setShareCopied(false);
|
|
2530
|
+
shareFeedbackTimerRef.current = null;
|
|
2531
|
+
}, SHARE_FEEDBACK_MS);
|
|
2532
|
+
} catch {
|
|
2533
|
+
window.prompt("Copy this query URL", url.toString());
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
return /* @__PURE__ */ jsxs6("div", { className: cx3("qt-qb", classNames?.root), children: [
|
|
2537
|
+
/* @__PURE__ */ jsxs6("div", { className: "qt-qb-bar qt-qb-state-bar qt-qb-row", children: [
|
|
2538
|
+
/* @__PURE__ */ jsxs6("span", { className: "qt-qb-state-main", children: [
|
|
2539
|
+
/* @__PURE__ */ jsxs6("span", { className: "qt-qb-saved", children: [
|
|
2540
|
+
/* @__PURE__ */ jsx6(
|
|
1531
2541
|
"button",
|
|
1532
2542
|
{
|
|
1533
2543
|
type: "button",
|
|
@@ -1538,8 +2548,8 @@ function QueryBuilder({
|
|
|
1538
2548
|
children: isSavedQuery ? "\u2605" : "\u2606"
|
|
1539
2549
|
}
|
|
1540
2550
|
),
|
|
1541
|
-
isEditingSaved || isEditingUnsaved ? /* @__PURE__ */
|
|
1542
|
-
/* @__PURE__ */
|
|
2551
|
+
isEditingSaved || isEditingUnsaved ? /* @__PURE__ */ jsxs6("span", { className: "qt-qb-saved-input-wrap", children: [
|
|
2552
|
+
/* @__PURE__ */ jsx6(
|
|
1543
2553
|
"input",
|
|
1544
2554
|
{
|
|
1545
2555
|
type: "text",
|
|
@@ -1562,7 +2572,7 @@ function QueryBuilder({
|
|
|
1562
2572
|
onClick: (e) => e.stopPropagation()
|
|
1563
2573
|
}
|
|
1564
2574
|
),
|
|
1565
|
-
/* @__PURE__ */
|
|
2575
|
+
/* @__PURE__ */ jsx6(
|
|
1566
2576
|
"button",
|
|
1567
2577
|
{
|
|
1568
2578
|
type: "button",
|
|
@@ -1576,7 +2586,7 @@ function QueryBuilder({
|
|
|
1576
2586
|
children: "\u2715"
|
|
1577
2587
|
}
|
|
1578
2588
|
)
|
|
1579
|
-
] }) : /* @__PURE__ */
|
|
2589
|
+
] }) : /* @__PURE__ */ jsx6(
|
|
1580
2590
|
"button",
|
|
1581
2591
|
{
|
|
1582
2592
|
type: "button",
|
|
@@ -1586,17 +2596,17 @@ function QueryBuilder({
|
|
|
1586
2596
|
}
|
|
1587
2597
|
)
|
|
1588
2598
|
] }),
|
|
1589
|
-
isCollapsed ? /* @__PURE__ */
|
|
1590
|
-
/* @__PURE__ */
|
|
2599
|
+
isCollapsed ? /* @__PURE__ */ jsxs6("span", { className: "qt-qb-summary qt-truncate", title: collapsedSummary, children: [
|
|
2600
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-qb-summary-kw", children: "WHERE" }),
|
|
1591
2601
|
" ",
|
|
1592
|
-
/* @__PURE__ */
|
|
2602
|
+
/* @__PURE__ */ jsx6("strong", { children: collapsedWhereText }),
|
|
1593
2603
|
" ",
|
|
1594
|
-
/* @__PURE__ */
|
|
2604
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-qb-summary-kw", children: "ORDER BY" }),
|
|
1595
2605
|
" ",
|
|
1596
|
-
/* @__PURE__ */
|
|
2606
|
+
/* @__PURE__ */ jsx6("strong", { children: collapsedOrderText })
|
|
1597
2607
|
] }) : null
|
|
1598
2608
|
] }),
|
|
1599
|
-
showUpdateButton ? /* @__PURE__ */
|
|
2609
|
+
showUpdateButton ? /* @__PURE__ */ jsx6(
|
|
1600
2610
|
"button",
|
|
1601
2611
|
{
|
|
1602
2612
|
type: "button",
|
|
@@ -1606,11 +2616,22 @@ function QueryBuilder({
|
|
|
1606
2616
|
children: `Update Query '${lastSavedQuery?.name ?? "query"}'`
|
|
1607
2617
|
}
|
|
1608
2618
|
) : null,
|
|
1609
|
-
/* @__PURE__ */
|
|
2619
|
+
/* @__PURE__ */ jsx6(
|
|
2620
|
+
"button",
|
|
2621
|
+
{
|
|
2622
|
+
type: "button",
|
|
2623
|
+
className: cx3("qt-btn", classNames?.button),
|
|
2624
|
+
onClick: () => void shareQuery(),
|
|
2625
|
+
title: "Copy a URL for this query",
|
|
2626
|
+
"aria-live": "polite",
|
|
2627
|
+
children: shareCopied ? "Copied!" : "Share"
|
|
2628
|
+
}
|
|
2629
|
+
),
|
|
2630
|
+
/* @__PURE__ */ jsxs6("button", { type: "button", className: cx3("qt-btn", classNames?.button), onClick: () => setShowSaved(true), children: [
|
|
1610
2631
|
"Saved",
|
|
1611
2632
|
api.saved.items.length > 0 ? ` (${api.saved.items.length})` : ""
|
|
1612
2633
|
] }),
|
|
1613
|
-
/* @__PURE__ */
|
|
2634
|
+
/* @__PURE__ */ jsxs6(
|
|
1614
2635
|
"button",
|
|
1615
2636
|
{
|
|
1616
2637
|
type: "button",
|
|
@@ -1620,7 +2641,7 @@ function QueryBuilder({
|
|
|
1620
2641
|
"aria-controls": bodyId,
|
|
1621
2642
|
title: isCollapsed ? "Expand query builder" : "Collapse query builder",
|
|
1622
2643
|
children: [
|
|
1623
|
-
/* @__PURE__ */
|
|
2644
|
+
/* @__PURE__ */ jsx6(
|
|
1624
2645
|
"span",
|
|
1625
2646
|
{
|
|
1626
2647
|
className: cx3("qt-qb-collapse-caret", isCollapsed && "qt-qb-collapse-caret--collapsed"),
|
|
@@ -1628,38 +2649,38 @@ function QueryBuilder({
|
|
|
1628
2649
|
children: "\u25BE"
|
|
1629
2650
|
}
|
|
1630
2651
|
),
|
|
1631
|
-
/* @__PURE__ */
|
|
2652
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-sr-only", children: isCollapsed ? "Expand query builder" : "Collapse query builder" })
|
|
1632
2653
|
]
|
|
1633
2654
|
}
|
|
1634
2655
|
)
|
|
1635
2656
|
] }),
|
|
1636
|
-
!isCollapsed ? /* @__PURE__ */
|
|
1637
|
-
/* @__PURE__ */
|
|
1638
|
-
/* @__PURE__ */
|
|
1639
|
-
/* @__PURE__ */
|
|
1640
|
-
/* @__PURE__ */
|
|
1641
|
-
/* @__PURE__ */
|
|
1642
|
-
/* @__PURE__ */
|
|
2657
|
+
!isCollapsed ? /* @__PURE__ */ jsxs6("div", { className: "qt-qb-bar qt-qb-editor-bar", children: [
|
|
2658
|
+
/* @__PURE__ */ jsxs6("div", { id: bodyId, className: "qt-qb-body", children: [
|
|
2659
|
+
/* @__PURE__ */ jsx6(SelectRow, { api, fields, classNames, disabled: isBusy }),
|
|
2660
|
+
/* @__PURE__ */ jsx6(WhereRow, { api, byName, fields, classNames, disabled: isBusy }),
|
|
2661
|
+
/* @__PURE__ */ jsx6(OrderRow, { api, fields, classNames, disabled: isBusy }),
|
|
2662
|
+
/* @__PURE__ */ jsx6(WindowRow, { api, classNames, disabled: isBusy }),
|
|
2663
|
+
/* @__PURE__ */ jsx6(MetricsRow, { api, fields, classNames, disabled: isBusy })
|
|
1643
2664
|
] }),
|
|
1644
|
-
/* @__PURE__ */
|
|
1645
|
-
/* @__PURE__ */
|
|
1646
|
-
/* @__PURE__ */
|
|
1647
|
-
/* @__PURE__ */
|
|
2665
|
+
/* @__PURE__ */ jsxs6("div", { className: "qt-qb-editor-stack", children: [
|
|
2666
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: cx3("qt-btn", classNames?.button), onClick: api.undo, disabled: !api.canUndo || isBusy, children: "\u21B6 Undo" }),
|
|
2667
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: cx3("qt-btn", classNames?.button), onClick: api.redo, disabled: !api.canRedo || isBusy, children: "\u21B7 Redo" }),
|
|
2668
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: cx3("qt-btn", classNames?.button), onClick: api.resetAll, disabled: isBusy, children: "Reset All" })
|
|
1648
2669
|
] })
|
|
1649
2670
|
] }) : null,
|
|
1650
|
-
/* @__PURE__ */
|
|
1651
|
-
/* @__PURE__ */
|
|
1652
|
-
/* @__PURE__ */
|
|
2671
|
+
/* @__PURE__ */ jsxs6("div", { className: "qt-qb-bar qt-qb-run-bar qt-qb-row", children: [
|
|
2672
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-qb-run-metric qt-qb-run-metric--strong", children: api.loading ? "loading\u2026" : `${rowSummary} rows` }),
|
|
2673
|
+
/* @__PURE__ */ jsxs6("span", { className: "qt-qb-run-metric", children: [
|
|
1653
2674
|
"Last updated ",
|
|
1654
2675
|
lastUpdatedText
|
|
1655
2676
|
] }),
|
|
1656
|
-
failureText ? /* @__PURE__ */
|
|
1657
|
-
/* @__PURE__ */
|
|
1658
|
-
autoRefreshStatus ? /* @__PURE__ */
|
|
2677
|
+
failureText ? /* @__PURE__ */ jsx6("span", { className: "qt-qb-run-error", children: failureText }) : null,
|
|
2678
|
+
/* @__PURE__ */ jsxs6("span", { className: "qt-qb-run-actions", children: [
|
|
2679
|
+
autoRefreshStatus ? /* @__PURE__ */ jsx6("span", { className: "qt-qb-auto-status", children: `Every ${formatDuration2(autoRefreshStatus.frequencyMs)}; clear after ${formatDuration2(
|
|
1659
2680
|
autoRefreshStatus.turnOffAfterMs
|
|
1660
2681
|
)} (${autoRefreshStatus.pollCount} polls run)` }) : null,
|
|
1661
|
-
/* @__PURE__ */
|
|
1662
|
-
/* @__PURE__ */
|
|
2682
|
+
/* @__PURE__ */ jsxs6("span", { className: "qt-auto-refresh", ref: autoRefreshRef, children: [
|
|
2683
|
+
/* @__PURE__ */ jsx6(
|
|
1663
2684
|
"button",
|
|
1664
2685
|
{
|
|
1665
2686
|
type: "button",
|
|
@@ -1670,7 +2691,7 @@ function QueryBuilder({
|
|
|
1670
2691
|
children: autoRefreshButtonText
|
|
1671
2692
|
}
|
|
1672
2693
|
),
|
|
1673
|
-
showAutoRefresh ? /* @__PURE__ */
|
|
2694
|
+
showAutoRefresh ? /* @__PURE__ */ jsxs6(
|
|
1674
2695
|
"span",
|
|
1675
2696
|
{
|
|
1676
2697
|
ref: autoRefreshPopoverRef,
|
|
@@ -1678,27 +2699,27 @@ function QueryBuilder({
|
|
|
1678
2699
|
role: "dialog",
|
|
1679
2700
|
"aria-label": "Auto-refresh settings",
|
|
1680
2701
|
children: [
|
|
1681
|
-
/* @__PURE__ */
|
|
1682
|
-
/* @__PURE__ */
|
|
1683
|
-
/* @__PURE__ */
|
|
2702
|
+
/* @__PURE__ */ jsxs6("label", { className: "qt-auto-refresh-field", children: [
|
|
2703
|
+
/* @__PURE__ */ jsx6("span", { children: "Frequency" }),
|
|
2704
|
+
/* @__PURE__ */ jsx6(
|
|
1684
2705
|
"select",
|
|
1685
2706
|
{
|
|
1686
2707
|
className: "qt-auto-refresh-select",
|
|
1687
2708
|
value: autoRefreshFrequencyMs,
|
|
1688
2709
|
onChange: (e) => setAutoRefreshFrequencyMs(Number(e.target.value)),
|
|
1689
|
-
children: AUTO_REFRESH_FREQUENCIES.map((option) => /* @__PURE__ */
|
|
2710
|
+
children: AUTO_REFRESH_FREQUENCIES.map((option) => /* @__PURE__ */ jsx6("option", { value: option.value, children: option.label }, option.value))
|
|
1690
2711
|
}
|
|
1691
2712
|
)
|
|
1692
2713
|
] }),
|
|
1693
|
-
/* @__PURE__ */
|
|
1694
|
-
/* @__PURE__ */
|
|
1695
|
-
/* @__PURE__ */
|
|
2714
|
+
/* @__PURE__ */ jsxs6("label", { className: "qt-auto-refresh-field", children: [
|
|
2715
|
+
/* @__PURE__ */ jsx6("span", { children: "Turn off after" }),
|
|
2716
|
+
/* @__PURE__ */ jsx6(
|
|
1696
2717
|
"select",
|
|
1697
2718
|
{
|
|
1698
2719
|
className: "qt-auto-refresh-select",
|
|
1699
2720
|
value: autoRefreshTurnOffAfterMs,
|
|
1700
2721
|
onChange: (e) => setAutoRefreshTurnOffAfterMs(Number(e.target.value)),
|
|
1701
|
-
children: AUTO_REFRESH_TURN_OFF_AFTER.map((option) => /* @__PURE__ */
|
|
2722
|
+
children: AUTO_REFRESH_TURN_OFF_AFTER.map((option) => /* @__PURE__ */ jsx6(
|
|
1702
2723
|
"option",
|
|
1703
2724
|
{
|
|
1704
2725
|
value: option.value,
|
|
@@ -1710,8 +2731,8 @@ function QueryBuilder({
|
|
|
1710
2731
|
}
|
|
1711
2732
|
)
|
|
1712
2733
|
] }),
|
|
1713
|
-
/* @__PURE__ */
|
|
1714
|
-
autoRefreshStatus ? /* @__PURE__ */
|
|
2734
|
+
/* @__PURE__ */ jsx6("span", { className: cx3("qt-auto-refresh-status", !canSubmitAutoRefresh && "qt-auto-refresh-status--error"), children: canSubmitAutoRefresh ? `${selectedAutoRefreshPolls} polls maximum` : `Choose between 1 and 1000 polls` }),
|
|
2735
|
+
autoRefreshStatus ? /* @__PURE__ */ jsxs6("span", { className: "qt-auto-refresh-status", children: [
|
|
1715
2736
|
"Active: every ",
|
|
1716
2737
|
formatDuration2(autoRefreshStatus.frequencyMs),
|
|
1717
2738
|
", clears after",
|
|
@@ -1721,8 +2742,8 @@ function QueryBuilder({
|
|
|
1721
2742
|
autoRefreshStatus.pollCount,
|
|
1722
2743
|
" polls run."
|
|
1723
2744
|
] }) : null,
|
|
1724
|
-
/* @__PURE__ */
|
|
1725
|
-
/* @__PURE__ */
|
|
2745
|
+
/* @__PURE__ */ jsxs6("span", { className: "qt-auto-refresh-actions", children: [
|
|
2746
|
+
/* @__PURE__ */ jsx6(
|
|
1726
2747
|
"button",
|
|
1727
2748
|
{
|
|
1728
2749
|
type: "button",
|
|
@@ -1732,7 +2753,7 @@ function QueryBuilder({
|
|
|
1732
2753
|
children: autoRefreshStatus ? "Update" : "Start"
|
|
1733
2754
|
}
|
|
1734
2755
|
),
|
|
1735
|
-
autoRefreshStatus ? /* @__PURE__ */
|
|
2756
|
+
autoRefreshStatus ? /* @__PURE__ */ jsx6(
|
|
1736
2757
|
"button",
|
|
1737
2758
|
{
|
|
1738
2759
|
type: "button",
|
|
@@ -1749,7 +2770,7 @@ function QueryBuilder({
|
|
|
1749
2770
|
}
|
|
1750
2771
|
) : null
|
|
1751
2772
|
] }),
|
|
1752
|
-
/* @__PURE__ */
|
|
2773
|
+
/* @__PURE__ */ jsx6(
|
|
1753
2774
|
"button",
|
|
1754
2775
|
{
|
|
1755
2776
|
type: "button",
|
|
@@ -1761,7 +2782,42 @@ function QueryBuilder({
|
|
|
1761
2782
|
)
|
|
1762
2783
|
] })
|
|
1763
2784
|
] }),
|
|
1764
|
-
!isCollapsed && showSaved ? /* @__PURE__ */
|
|
2785
|
+
!isCollapsed && showSaved ? /* @__PURE__ */ jsx6(SavedQueriesModal, { saved: api.saved, onClose: () => setShowSaved(false), onLoad: setLastSavedId }) : null
|
|
2786
|
+
] });
|
|
2787
|
+
}
|
|
2788
|
+
function MoveButtons({
|
|
2789
|
+
label,
|
|
2790
|
+
position,
|
|
2791
|
+
count,
|
|
2792
|
+
disabled,
|
|
2793
|
+
onMove
|
|
2794
|
+
}) {
|
|
2795
|
+
if (count < 2) return null;
|
|
2796
|
+
return /* @__PURE__ */ jsxs6("span", { className: "qt-chip-move-buttons", children: [
|
|
2797
|
+
/* @__PURE__ */ jsx6(
|
|
2798
|
+
"button",
|
|
2799
|
+
{
|
|
2800
|
+
type: "button",
|
|
2801
|
+
className: "qt-chip-move",
|
|
2802
|
+
disabled: disabled || position === 0,
|
|
2803
|
+
onClick: () => onMove(position - 1),
|
|
2804
|
+
"aria-label": `Move ${label} earlier`,
|
|
2805
|
+
title: "Move earlier",
|
|
2806
|
+
children: "\u2190"
|
|
2807
|
+
}
|
|
2808
|
+
),
|
|
2809
|
+
/* @__PURE__ */ jsx6(
|
|
2810
|
+
"button",
|
|
2811
|
+
{
|
|
2812
|
+
type: "button",
|
|
2813
|
+
className: "qt-chip-move",
|
|
2814
|
+
disabled: disabled || position === count - 1,
|
|
2815
|
+
onClick: () => onMove(position + 1),
|
|
2816
|
+
"aria-label": `Move ${label} later`,
|
|
2817
|
+
title: "Move later",
|
|
2818
|
+
children: "\u2192"
|
|
2819
|
+
}
|
|
2820
|
+
)
|
|
1765
2821
|
] });
|
|
1766
2822
|
}
|
|
1767
2823
|
function SelectRow({
|
|
@@ -1771,19 +2827,22 @@ function SelectRow({
|
|
|
1771
2827
|
disabled
|
|
1772
2828
|
}) {
|
|
1773
2829
|
const { select, columnDrag } = api;
|
|
1774
|
-
const [
|
|
1775
|
-
const
|
|
2830
|
+
const [editingColumns, setEditingColumns] = useState5(false);
|
|
2831
|
+
const [adding, setAdding] = useState5(false);
|
|
2832
|
+
const fieldLabelByName = useMemo4(() => new Map(select.fields.map((f) => [f.name, f.label])), [select.fields]);
|
|
1776
2833
|
function reorderByIndex(from, overIndex) {
|
|
1777
2834
|
select.move(from, overIndex);
|
|
1778
2835
|
}
|
|
1779
2836
|
const fieldNames = select.visible.map((c) => c.field);
|
|
1780
2837
|
const dragSource = columnDrag.source;
|
|
1781
2838
|
const rendered = columnDrag.preview(fieldNames);
|
|
1782
|
-
return /* @__PURE__ */
|
|
1783
|
-
/* @__PURE__ */
|
|
1784
|
-
|
|
2839
|
+
return /* @__PURE__ */ jsxs6("div", { className: "qt-qb-row", children: [
|
|
2840
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-qb-kw", children: "select" }),
|
|
2841
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "qt-btn", disabled, onClick: () => setEditingColumns(true), children: "Edit columns" }),
|
|
2842
|
+
editingColumns && /* @__PURE__ */ jsx6(SelectColumnEditor, { api, onClose: () => setEditingColumns(false) }),
|
|
2843
|
+
rendered.map((name, position) => {
|
|
1785
2844
|
const label = fieldLabelByName.get(name) ?? name;
|
|
1786
|
-
return /* @__PURE__ */
|
|
2845
|
+
return /* @__PURE__ */ jsxs6(
|
|
1787
2846
|
"span",
|
|
1788
2847
|
{
|
|
1789
2848
|
className: cx3(
|
|
@@ -1822,25 +2881,35 @@ function SelectRow({
|
|
|
1822
2881
|
onDragEnd: () => columnDrag.end(),
|
|
1823
2882
|
title: "drag to reorder",
|
|
1824
2883
|
children: [
|
|
1825
|
-
/* @__PURE__ */
|
|
1826
|
-
/* @__PURE__ */
|
|
1827
|
-
/* @__PURE__ */
|
|
2884
|
+
/* @__PURE__ */ jsx6("span", { "aria-hidden": true, className: "qt-chip-grip", children: "\u22EE\u22EE" }),
|
|
2885
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-chip-field", children: label }),
|
|
2886
|
+
/* @__PURE__ */ jsx6(
|
|
2887
|
+
MoveButtons,
|
|
2888
|
+
{
|
|
2889
|
+
label,
|
|
2890
|
+
position,
|
|
2891
|
+
count: rendered.length,
|
|
2892
|
+
disabled,
|
|
2893
|
+
onMove: (nextPosition) => select.move(name, nextPosition)
|
|
2894
|
+
}
|
|
2895
|
+
),
|
|
2896
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "qt-chip-x", onClick: () => select.hide(name), disabled, children: "\u2715" })
|
|
1828
2897
|
]
|
|
1829
2898
|
},
|
|
1830
2899
|
name
|
|
1831
2900
|
);
|
|
1832
2901
|
}),
|
|
1833
|
-
adding ? /* @__PURE__ */
|
|
2902
|
+
adding ? /* @__PURE__ */ jsx6(
|
|
1834
2903
|
FieldPicker,
|
|
1835
2904
|
{
|
|
1836
|
-
fields: select.hidden.filter(
|
|
2905
|
+
fields: select.hidden.filter(isSelectable2),
|
|
1837
2906
|
onPick: (f) => {
|
|
1838
2907
|
select.show(f.name);
|
|
1839
2908
|
setAdding(false);
|
|
1840
2909
|
},
|
|
1841
2910
|
onClose: () => setAdding(false)
|
|
1842
2911
|
}
|
|
1843
|
-
) : /* @__PURE__ */
|
|
2912
|
+
) : /* @__PURE__ */ jsx6(
|
|
1844
2913
|
"button",
|
|
1845
2914
|
{
|
|
1846
2915
|
type: "button",
|
|
@@ -1850,9 +2919,16 @@ function SelectRow({
|
|
|
1850
2919
|
children: "+ add column"
|
|
1851
2920
|
}
|
|
1852
2921
|
),
|
|
1853
|
-
api.query.select.length > 0 && /* @__PURE__ */
|
|
2922
|
+
api.query.select.length > 0 && /* @__PURE__ */ jsx6("button", { type: "button", className: "qt-link-btn", onClick: select.reset, disabled, title: "Reset to default columns", children: "reset" })
|
|
1854
2923
|
] });
|
|
1855
2924
|
}
|
|
2925
|
+
function dropModeFor(e) {
|
|
2926
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
2927
|
+
const x = e.clientX - rect.left;
|
|
2928
|
+
if (x < rect.width * 0.3) return "before";
|
|
2929
|
+
if (x > rect.width * 0.7) return "after";
|
|
2930
|
+
return "merge";
|
|
2931
|
+
}
|
|
1856
2932
|
function WhereRow({
|
|
1857
2933
|
api,
|
|
1858
2934
|
byName,
|
|
@@ -1860,120 +2936,295 @@ function WhereRow({
|
|
|
1860
2936
|
classNames,
|
|
1861
2937
|
disabled
|
|
1862
2938
|
}) {
|
|
1863
|
-
const [adding, setAdding] =
|
|
2939
|
+
const [adding, setAdding] = useState5(false);
|
|
2940
|
+
const [drag, setDrag] = useState5(null);
|
|
2941
|
+
const where = api.query.where;
|
|
2942
|
+
const hasOr = where.some(isOrGroup);
|
|
1864
2943
|
function addClause(f) {
|
|
1865
|
-
const op = opsForField2(f)[0] ?? "=";
|
|
2944
|
+
const op = positiveOpsForField(f)[0] ?? opsForField2(f)[0] ?? "=";
|
|
1866
2945
|
api.addFilter({ field: f.name, op, value: "" });
|
|
1867
2946
|
setAdding(false);
|
|
1868
2947
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
)
|
|
1896
|
-
|
|
2948
|
+
function handleOver(e, index) {
|
|
2949
|
+
if (!drag) return;
|
|
2950
|
+
e.preventDefault();
|
|
2951
|
+
e.dataTransfer.dropEffect = "move";
|
|
2952
|
+
const mode = dropModeFor(e);
|
|
2953
|
+
setDrag((d) => d && (d.over?.index !== index || d.over?.mode !== mode) ? { from: d.from, over: { index, mode } } : d);
|
|
2954
|
+
}
|
|
2955
|
+
function handleDrop() {
|
|
2956
|
+
if (!drag || !drag.over) {
|
|
2957
|
+
setDrag(null);
|
|
2958
|
+
return;
|
|
2959
|
+
}
|
|
2960
|
+
const { from } = drag;
|
|
2961
|
+
const { index: target, mode } = drag.over;
|
|
2962
|
+
if (mode === "merge") {
|
|
2963
|
+
if (target !== from) api.mergeFilters(from, target);
|
|
2964
|
+
} else {
|
|
2965
|
+
const tPrime = target < from ? target : target - 1;
|
|
2966
|
+
const insertAt = mode === "after" ? tPrime + 1 : tPrime;
|
|
2967
|
+
if (insertAt !== from) api.reorderFilters(from, insertAt);
|
|
2968
|
+
}
|
|
2969
|
+
setDrag(null);
|
|
2970
|
+
}
|
|
2971
|
+
return /* @__PURE__ */ jsxs6("div", { className: "qt-qb-row", children: [
|
|
2972
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-qb-kw", children: "where" }),
|
|
2973
|
+
where.length === 0 && !adding && /* @__PURE__ */ jsx6("span", { className: "qt-qb-hint", children: "all rows" }),
|
|
2974
|
+
where.map((term, index) => /* @__PURE__ */ jsxs6("span", { className: "qt-where-term-wrap", children: [
|
|
2975
|
+
index > 0 && hasOr && /* @__PURE__ */ jsx6("span", { className: "qt-where-and", children: "AND" }),
|
|
2976
|
+
/* @__PURE__ */ jsx6(
|
|
2977
|
+
TermChip,
|
|
2978
|
+
{
|
|
2979
|
+
api,
|
|
2980
|
+
term,
|
|
2981
|
+
index,
|
|
2982
|
+
byName,
|
|
2983
|
+
classNames,
|
|
2984
|
+
disabled,
|
|
2985
|
+
isDragSource: drag?.from === index,
|
|
2986
|
+
dropHint: drag?.over?.index === index ? drag.over.mode : null,
|
|
2987
|
+
onDragStart: () => setDrag({ from: index, over: null }),
|
|
2988
|
+
onDragOver: (e) => handleOver(e, index),
|
|
2989
|
+
onDrop: handleDrop,
|
|
2990
|
+
onDragEnd: () => setDrag(null)
|
|
2991
|
+
}
|
|
2992
|
+
)
|
|
2993
|
+
] }, index)),
|
|
2994
|
+
adding ? /* @__PURE__ */ jsx6(
|
|
1897
2995
|
FieldPicker,
|
|
1898
2996
|
{
|
|
1899
|
-
fields: fields.filter(
|
|
2997
|
+
fields: fields.filter(isFilterable2),
|
|
1900
2998
|
gateOnDistinct: true,
|
|
1901
2999
|
onPick: addClause,
|
|
1902
3000
|
onClose: () => setAdding(false)
|
|
1903
3001
|
}
|
|
1904
|
-
) : /* @__PURE__ */
|
|
1905
|
-
|
|
3002
|
+
) : /* @__PURE__ */ jsx6("button", { type: "button", className: "qt-add", onClick: () => setAdding(true), disabled, children: "+ add filter" }),
|
|
3003
|
+
where.length > 0 && /* @__PURE__ */ jsx6("button", { type: "button", className: "qt-link-btn", onClick: api.clearFilters, disabled, title: "Reset filters", children: "reset" })
|
|
1906
3004
|
] });
|
|
1907
3005
|
}
|
|
1908
|
-
function
|
|
3006
|
+
function TermChip({
|
|
1909
3007
|
api,
|
|
1910
|
-
|
|
1911
|
-
|
|
3008
|
+
term,
|
|
3009
|
+
index,
|
|
3010
|
+
byName,
|
|
1912
3011
|
classNames,
|
|
1913
3012
|
disabled,
|
|
1914
|
-
|
|
1915
|
-
|
|
3013
|
+
isDragSource,
|
|
3014
|
+
dropHint,
|
|
3015
|
+
onDragStart,
|
|
3016
|
+
onDragOver,
|
|
3017
|
+
onDrop,
|
|
3018
|
+
onDragEnd
|
|
1916
3019
|
}) {
|
|
1917
|
-
const
|
|
1918
|
-
const
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
3020
|
+
const group = isOrGroup(term);
|
|
3021
|
+
const predicates = predicatesOf(term);
|
|
3022
|
+
const grip = /* @__PURE__ */ jsx6(
|
|
3023
|
+
"span",
|
|
3024
|
+
{
|
|
3025
|
+
className: "qt-chip-grip",
|
|
3026
|
+
"aria-hidden": true,
|
|
3027
|
+
draggable: !disabled,
|
|
3028
|
+
onDragStart: (e) => {
|
|
3029
|
+
onDragStart();
|
|
3030
|
+
e.dataTransfer.effectAllowed = "move";
|
|
3031
|
+
e.dataTransfer.setData("text/plain", String(index));
|
|
3032
|
+
},
|
|
3033
|
+
onDragEnd,
|
|
3034
|
+
title: "drag to reorder, or drop onto another filter to make an OR",
|
|
3035
|
+
children: "\u22EE\u22EE"
|
|
3036
|
+
}
|
|
3037
|
+
);
|
|
3038
|
+
return /* @__PURE__ */ jsxs6(
|
|
3039
|
+
"span",
|
|
3040
|
+
{
|
|
3041
|
+
className: cx3(
|
|
3042
|
+
"qt-chip",
|
|
3043
|
+
"qt-chip--where",
|
|
3044
|
+
group && "qt-chip--or",
|
|
3045
|
+
isDragSource && "qt-chip--dragging",
|
|
3046
|
+
dropHint ? `qt-chip--drop-${dropHint}` : void 0,
|
|
3047
|
+
classNames?.chip
|
|
3048
|
+
),
|
|
3049
|
+
onDragOver,
|
|
3050
|
+
onDrop: (e) => {
|
|
3051
|
+
e.preventDefault();
|
|
3052
|
+
onDrop();
|
|
3053
|
+
},
|
|
3054
|
+
children: [
|
|
3055
|
+
grip,
|
|
3056
|
+
group ? /* @__PURE__ */ jsx6("span", { className: "qt-or-group", children: predicates.map((clause, predIndex) => /* @__PURE__ */ jsxs6("span", { className: "qt-or-member", children: [
|
|
3057
|
+
predIndex > 0 && /* @__PURE__ */ jsx6("span", { className: "qt-chip-and qt-chip-or", children: "OR" }),
|
|
3058
|
+
/* @__PURE__ */ jsx6(
|
|
3059
|
+
PredicateEditor,
|
|
3060
|
+
{
|
|
3061
|
+
api,
|
|
3062
|
+
field: byName.get(clause.field),
|
|
3063
|
+
clause,
|
|
3064
|
+
classNames,
|
|
3065
|
+
disabled,
|
|
3066
|
+
onChange: (next) => api.updatePredicate(index, predIndex, next),
|
|
3067
|
+
onRemove: () => api.removePredicate(index, predIndex)
|
|
3068
|
+
}
|
|
3069
|
+
)
|
|
3070
|
+
] }, predIndex)) }) : /* @__PURE__ */ jsx6(
|
|
3071
|
+
PredicateEditor,
|
|
1932
3072
|
{
|
|
1933
|
-
|
|
1934
|
-
|
|
3073
|
+
api,
|
|
3074
|
+
field: byName.get(term.field),
|
|
3075
|
+
clause: term,
|
|
3076
|
+
classNames,
|
|
1935
3077
|
disabled,
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
children: clauseOps.map((op) => /* @__PURE__ */ jsx5("option", { value: op, children: op }, op))
|
|
3078
|
+
onChange: (next) => api.updatePredicate(index, 0, next),
|
|
3079
|
+
onRemove: () => api.removeFilter(index)
|
|
1939
3080
|
}
|
|
1940
|
-
)
|
|
1941
|
-
|
|
1942
|
-
|
|
3081
|
+
)
|
|
3082
|
+
]
|
|
3083
|
+
}
|
|
3084
|
+
);
|
|
3085
|
+
}
|
|
3086
|
+
var OP_LABEL = {
|
|
3087
|
+
"=": "=",
|
|
3088
|
+
"!=": "\u2260",
|
|
3089
|
+
">": ">",
|
|
3090
|
+
">=": "\u2265",
|
|
3091
|
+
"<": "<",
|
|
3092
|
+
"<=": "\u2264",
|
|
3093
|
+
contains: "contains",
|
|
3094
|
+
starts_with: "starts with",
|
|
3095
|
+
ends_with: "ends with",
|
|
3096
|
+
matches_regex: "matches",
|
|
3097
|
+
not_matches_regex: "not matches",
|
|
3098
|
+
includes: "includes",
|
|
3099
|
+
is_null: "is null",
|
|
3100
|
+
is_not_null: "is not null"
|
|
3101
|
+
};
|
|
3102
|
+
function choiceLabel(op, negated) {
|
|
3103
|
+
return negated ? `not ${OP_LABEL[op]}` : OP_LABEL[op];
|
|
3104
|
+
}
|
|
3105
|
+
function OpPicker({
|
|
3106
|
+
field,
|
|
3107
|
+
clause,
|
|
3108
|
+
disabled,
|
|
3109
|
+
classNames,
|
|
3110
|
+
onPick
|
|
3111
|
+
}) {
|
|
3112
|
+
const [open, setOpen] = useState5(false);
|
|
3113
|
+
const wrapRef = useRef3(null);
|
|
3114
|
+
useEffect5(() => {
|
|
3115
|
+
if (!open) return;
|
|
3116
|
+
function onDocClick(e) {
|
|
3117
|
+
if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
|
|
3118
|
+
}
|
|
3119
|
+
function onKey(e) {
|
|
3120
|
+
if (e.key === "Escape") setOpen(false);
|
|
3121
|
+
}
|
|
3122
|
+
const t = setTimeout(() => window.addEventListener("click", onDocClick), 0);
|
|
3123
|
+
window.addEventListener("keydown", onKey);
|
|
3124
|
+
return () => {
|
|
3125
|
+
clearTimeout(t);
|
|
3126
|
+
window.removeEventListener("click", onDocClick);
|
|
3127
|
+
window.removeEventListener("keydown", onKey);
|
|
3128
|
+
};
|
|
3129
|
+
}, [open]);
|
|
3130
|
+
const pairs = useMemo4(() => field ? opPairsForField2(field) : [], [field]);
|
|
3131
|
+
const isActive = (choice) => choice.op === clause.op && Boolean(choice.negated) === Boolean(clause.negated);
|
|
3132
|
+
return /* @__PURE__ */ jsxs6("span", { className: "qt-op-picker", ref: wrapRef, children: [
|
|
3133
|
+
/* @__PURE__ */ jsxs6(
|
|
3134
|
+
"button",
|
|
3135
|
+
{
|
|
3136
|
+
type: "button",
|
|
3137
|
+
className: cx3("qt-chip-op-btn", "qt-op-trigger", classNames?.select),
|
|
3138
|
+
disabled,
|
|
3139
|
+
"aria-haspopup": "dialog",
|
|
3140
|
+
"aria-expanded": open,
|
|
3141
|
+
onClick: () => setOpen((o) => !o),
|
|
3142
|
+
children: [
|
|
3143
|
+
choiceLabel(clause.op, Boolean(clause.negated)),
|
|
3144
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-op-caret", "aria-hidden": true, children: "\u25BE" })
|
|
3145
|
+
]
|
|
3146
|
+
}
|
|
3147
|
+
),
|
|
3148
|
+
open && pairs.length > 0 && /* @__PURE__ */ jsxs6("span", { className: "qt-op-pop", role: "dialog", "aria-label": "Choose condition", children: [
|
|
3149
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-cm-col-head", children: "keep" }),
|
|
3150
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-cm-col-head qt-cm-col-head--neg", children: "exclude" }),
|
|
3151
|
+
pairs.map((pair, i) => /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
3152
|
+
/* @__PURE__ */ jsx6(
|
|
3153
|
+
"button",
|
|
1943
3154
|
{
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
3155
|
+
type: "button",
|
|
3156
|
+
className: cx3("qt-op-cell", isActive(pair.keep) && "qt-op-cell--on"),
|
|
3157
|
+
onClick: () => {
|
|
3158
|
+
onPick(pair.keep);
|
|
3159
|
+
setOpen(false);
|
|
3160
|
+
},
|
|
3161
|
+
children: choiceLabel(pair.keep.op, pair.keep.negated)
|
|
1949
3162
|
}
|
|
1950
3163
|
),
|
|
1951
|
-
/* @__PURE__ */
|
|
3164
|
+
/* @__PURE__ */ jsx6(
|
|
1952
3165
|
"button",
|
|
1953
3166
|
{
|
|
1954
3167
|
type: "button",
|
|
1955
|
-
className: "qt-
|
|
1956
|
-
onClick: () =>
|
|
1957
|
-
|
|
1958
|
-
|
|
3168
|
+
className: cx3("qt-op-cell", "qt-op-cell--neg", isActive(pair.exclude) && "qt-op-cell--on"),
|
|
3169
|
+
onClick: () => {
|
|
3170
|
+
onPick(pair.exclude);
|
|
3171
|
+
setOpen(false);
|
|
3172
|
+
},
|
|
3173
|
+
children: choiceLabel(pair.exclude.op, pair.exclude.negated)
|
|
1959
3174
|
}
|
|
1960
3175
|
)
|
|
1961
|
-
] },
|
|
1962
|
-
|
|
3176
|
+
] }, i))
|
|
3177
|
+
] })
|
|
3178
|
+
] });
|
|
3179
|
+
}
|
|
3180
|
+
function PredicateEditor({
|
|
3181
|
+
api,
|
|
3182
|
+
field,
|
|
3183
|
+
clause,
|
|
3184
|
+
classNames,
|
|
3185
|
+
disabled,
|
|
3186
|
+
onChange,
|
|
3187
|
+
onRemove
|
|
3188
|
+
}) {
|
|
3189
|
+
const needsValue = !NULLARY_OPS2.has(clause.op);
|
|
3190
|
+
const isRegex = clause.op === "matches_regex" || clause.op === "not_matches_regex";
|
|
3191
|
+
function pickOp(choice) {
|
|
3192
|
+
const next = { field: clause.field, op: choice.op, value: clause.value };
|
|
3193
|
+
if (choice.negated) next.negated = true;
|
|
3194
|
+
onChange(next);
|
|
3195
|
+
}
|
|
3196
|
+
return /* @__PURE__ */ jsxs6("span", { className: "qt-pred", children: [
|
|
3197
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-chip-field", children: field?.label ?? clause.field }),
|
|
3198
|
+
/* @__PURE__ */ jsx6(OpPicker, { field, clause, disabled, classNames, onPick: pickOp }),
|
|
3199
|
+
needsValue && /* @__PURE__ */ jsx6(
|
|
3200
|
+
ValueInput,
|
|
3201
|
+
{
|
|
3202
|
+
api,
|
|
3203
|
+
field,
|
|
3204
|
+
value: clause.value,
|
|
3205
|
+
forceFreeform: isRegex,
|
|
3206
|
+
placeholder: isRegex ? "regex" : "value",
|
|
3207
|
+
classNames,
|
|
3208
|
+
onChange: (v) => onChange({ ...clause, value: v })
|
|
3209
|
+
}
|
|
3210
|
+
),
|
|
3211
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "qt-chip-x", onClick: onRemove, disabled, children: "\u2715" })
|
|
1963
3212
|
] });
|
|
1964
3213
|
}
|
|
1965
3214
|
function ValueInput({
|
|
1966
3215
|
api,
|
|
1967
3216
|
field,
|
|
1968
3217
|
value,
|
|
3218
|
+
forceFreeform,
|
|
3219
|
+
placeholder = "value",
|
|
1969
3220
|
classNames,
|
|
1970
3221
|
onChange
|
|
1971
3222
|
}) {
|
|
1972
3223
|
const strategy = field ? filterValuesFor(field) : { source: "freeform" };
|
|
1973
|
-
if (strategy.source === "static") {
|
|
3224
|
+
if (!forceFreeform && strategy.source === "static") {
|
|
1974
3225
|
const selectedText = value || "\u2014";
|
|
1975
3226
|
const widthChars = chipFieldWidthForSelect(selectedText, 4);
|
|
1976
|
-
return /* @__PURE__ */
|
|
3227
|
+
return /* @__PURE__ */ jsxs6(
|
|
1977
3228
|
"select",
|
|
1978
3229
|
{
|
|
1979
3230
|
className: cx3("qt-chip-val", classNames?.select),
|
|
@@ -1981,27 +3232,27 @@ function ValueInput({
|
|
|
1981
3232
|
style: { width: `${widthChars}ch` },
|
|
1982
3233
|
onChange: (e) => onChange(e.target.value),
|
|
1983
3234
|
children: [
|
|
1984
|
-
/* @__PURE__ */
|
|
1985
|
-
strategy.options.map((o) => /* @__PURE__ */
|
|
3235
|
+
/* @__PURE__ */ jsx6("option", { value: "", children: "\u2014" }),
|
|
3236
|
+
strategy.options.map((o) => /* @__PURE__ */ jsx6("option", { value: o, children: o }, o))
|
|
1986
3237
|
]
|
|
1987
3238
|
}
|
|
1988
3239
|
);
|
|
1989
3240
|
}
|
|
1990
|
-
if (strategy.source === "freeform" || !field) {
|
|
1991
|
-
const widthChars = chipFieldWidthForInput(value ||
|
|
1992
|
-
return /* @__PURE__ */
|
|
3241
|
+
if (forceFreeform || strategy.source === "freeform" || !field) {
|
|
3242
|
+
const widthChars = chipFieldWidthForInput(value || placeholder, field?.type, 5, 4);
|
|
3243
|
+
return /* @__PURE__ */ jsx6(
|
|
1993
3244
|
"input",
|
|
1994
3245
|
{
|
|
1995
3246
|
className: cx3("qt-chip-val", classNames?.input),
|
|
1996
3247
|
value,
|
|
1997
|
-
placeholder
|
|
3248
|
+
placeholder,
|
|
1998
3249
|
size: widthChars,
|
|
1999
3250
|
style: { width: `${widthChars}ch` },
|
|
2000
3251
|
onChange: (e) => onChange(e.target.value)
|
|
2001
3252
|
}
|
|
2002
3253
|
);
|
|
2003
3254
|
}
|
|
2004
|
-
return /* @__PURE__ */
|
|
3255
|
+
return /* @__PURE__ */ jsx6(AutocompleteInput, { api, field, value, classNames, onChange });
|
|
2005
3256
|
}
|
|
2006
3257
|
function AutocompleteInput({
|
|
2007
3258
|
api,
|
|
@@ -2010,9 +3261,9 @@ function AutocompleteInput({
|
|
|
2010
3261
|
classNames,
|
|
2011
3262
|
onChange
|
|
2012
3263
|
}) {
|
|
2013
|
-
const [result, setResult] =
|
|
2014
|
-
const listId =
|
|
2015
|
-
|
|
3264
|
+
const [result, setResult] = useState5({ values: [], hasMore: false });
|
|
3265
|
+
const listId = useId2();
|
|
3266
|
+
useEffect5(() => {
|
|
2016
3267
|
let cancelled = false;
|
|
2017
3268
|
const t = setTimeout(() => {
|
|
2018
3269
|
void api.filterValues(field.name, value).then((r) => {
|
|
@@ -2026,8 +3277,8 @@ function AutocompleteInput({
|
|
|
2026
3277
|
clearTimeout(t);
|
|
2027
3278
|
};
|
|
2028
3279
|
}, [api, field.name, value]);
|
|
2029
|
-
return /* @__PURE__ */
|
|
2030
|
-
/* @__PURE__ */
|
|
3280
|
+
return /* @__PURE__ */ jsxs6("span", { className: "qt-autocomplete", children: [
|
|
3281
|
+
/* @__PURE__ */ jsx6(
|
|
2031
3282
|
"input",
|
|
2032
3283
|
{
|
|
2033
3284
|
className: cx3("qt-chip-val", classNames?.input),
|
|
@@ -2039,8 +3290,8 @@ function AutocompleteInput({
|
|
|
2039
3290
|
onChange: (e) => onChange(e.target.value)
|
|
2040
3291
|
}
|
|
2041
3292
|
),
|
|
2042
|
-
/* @__PURE__ */
|
|
2043
|
-
result.hasMore && /* @__PURE__ */
|
|
3293
|
+
/* @__PURE__ */ jsx6("datalist", { id: listId, children: result.values.map((v) => /* @__PURE__ */ jsx6("option", { value: v }, v)) }),
|
|
3294
|
+
result.hasMore && /* @__PURE__ */ jsx6("span", { className: "qt-autocomplete-hint", children: "keep typing to refine\u2026" })
|
|
2044
3295
|
] });
|
|
2045
3296
|
}
|
|
2046
3297
|
function OrderTermChip({
|
|
@@ -2057,12 +3308,16 @@ function OrderTermChip({
|
|
|
2057
3308
|
onDragEnd,
|
|
2058
3309
|
disabled,
|
|
2059
3310
|
updateTerm,
|
|
3311
|
+
clearExtract,
|
|
2060
3312
|
removeTerm,
|
|
3313
|
+
position,
|
|
3314
|
+
count,
|
|
3315
|
+
moveTerm,
|
|
2061
3316
|
classNames,
|
|
2062
3317
|
label
|
|
2063
3318
|
}) {
|
|
2064
3319
|
const fieldHasNull = useFieldHasNull(api, field?.name);
|
|
2065
|
-
return /* @__PURE__ */
|
|
3320
|
+
return /* @__PURE__ */ jsxs6(
|
|
2066
3321
|
"span",
|
|
2067
3322
|
{
|
|
2068
3323
|
className: cx3("qt-chip", "qt-chip--col", "qt-chip--sort", isDragSource && "qt-chip--dragging", classNames?.chip),
|
|
@@ -2080,10 +3335,10 @@ function OrderTermChip({
|
|
|
2080
3335
|
onDragEnd,
|
|
2081
3336
|
title: "drag to reorder sort priority",
|
|
2082
3337
|
children: [
|
|
2083
|
-
/* @__PURE__ */
|
|
2084
|
-
showPriority && /* @__PURE__ */
|
|
2085
|
-
/* @__PURE__ */
|
|
2086
|
-
/* @__PURE__ */
|
|
3338
|
+
/* @__PURE__ */ jsx6("span", { "aria-hidden": true, className: "qt-chip-grip", children: "\u22EE\u22EE" }),
|
|
3339
|
+
showPriority && /* @__PURE__ */ jsx6("span", { className: "qt-chip-priority", children: priority }),
|
|
3340
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-chip-field", children: label }),
|
|
3341
|
+
/* @__PURE__ */ jsx6(
|
|
2087
3342
|
"button",
|
|
2088
3343
|
{
|
|
2089
3344
|
type: "button",
|
|
@@ -2093,7 +3348,45 @@ function OrderTermChip({
|
|
|
2093
3348
|
children: term.dir === "asc" ? "\u2191 asc" : "\u2193 desc"
|
|
2094
3349
|
}
|
|
2095
3350
|
),
|
|
2096
|
-
|
|
3351
|
+
term.extract ? /* @__PURE__ */ jsxs6("span", { className: "qt-sort-extract", children: [
|
|
3352
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-chip-and", children: "extract" }),
|
|
3353
|
+
/* @__PURE__ */ jsx6(
|
|
3354
|
+
"input",
|
|
3355
|
+
{
|
|
3356
|
+
className: cx3("qt-chip-val", classNames?.input),
|
|
3357
|
+
value: term.extract.regex,
|
|
3358
|
+
placeholder: "regex",
|
|
3359
|
+
"aria-label": `Regex extract for ${label}`,
|
|
3360
|
+
size: chipFieldWidthForInput(term.extract.regex || "regex", "text", 5, 4),
|
|
3361
|
+
style: { width: `${chipFieldWidthForInput(term.extract.regex || "regex", "text", 5, 4)}ch` },
|
|
3362
|
+
disabled,
|
|
3363
|
+
onChange: (e) => updateTerm(dataIndex, { extract: { regex: e.target.value } })
|
|
3364
|
+
}
|
|
3365
|
+
),
|
|
3366
|
+
/* @__PURE__ */ jsx6(
|
|
3367
|
+
"button",
|
|
3368
|
+
{
|
|
3369
|
+
type: "button",
|
|
3370
|
+
className: "qt-chip-x",
|
|
3371
|
+
title: "Remove regex extraction",
|
|
3372
|
+
"aria-label": `Remove regex extract for ${label}`,
|
|
3373
|
+
disabled,
|
|
3374
|
+
onClick: () => clearExtract(dataIndex),
|
|
3375
|
+
children: "\u2715"
|
|
3376
|
+
}
|
|
3377
|
+
)
|
|
3378
|
+
] }) : /* @__PURE__ */ jsx6(
|
|
3379
|
+
"button",
|
|
3380
|
+
{
|
|
3381
|
+
type: "button",
|
|
3382
|
+
className: "qt-chip-op-btn",
|
|
3383
|
+
title: "Sort by text extracted with a regular expression",
|
|
3384
|
+
disabled,
|
|
3385
|
+
onClick: () => updateTerm(dataIndex, { extract: { regex: "" } }),
|
|
3386
|
+
children: "+ regex extract"
|
|
3387
|
+
}
|
|
3388
|
+
),
|
|
3389
|
+
(term.extract != null || fieldHasNull !== false) && /* @__PURE__ */ jsxs6(
|
|
2097
3390
|
"button",
|
|
2098
3391
|
{
|
|
2099
3392
|
type: "button",
|
|
@@ -2107,7 +3400,17 @@ function OrderTermChip({
|
|
|
2107
3400
|
]
|
|
2108
3401
|
}
|
|
2109
3402
|
),
|
|
2110
|
-
/* @__PURE__ */
|
|
3403
|
+
/* @__PURE__ */ jsx6(
|
|
3404
|
+
MoveButtons,
|
|
3405
|
+
{
|
|
3406
|
+
label,
|
|
3407
|
+
position,
|
|
3408
|
+
count,
|
|
3409
|
+
disabled,
|
|
3410
|
+
onMove: moveTerm
|
|
3411
|
+
}
|
|
3412
|
+
),
|
|
3413
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "qt-chip-x", onClick: () => removeTerm(dataIndex), disabled, children: "\u2715" })
|
|
2111
3414
|
]
|
|
2112
3415
|
}
|
|
2113
3416
|
);
|
|
@@ -2119,15 +3422,24 @@ function OrderRow({
|
|
|
2119
3422
|
disabled
|
|
2120
3423
|
}) {
|
|
2121
3424
|
const orderBy = api.query.orderBy;
|
|
2122
|
-
const byName =
|
|
2123
|
-
const [drag, setDrag] =
|
|
2124
|
-
const [adding, setAdding] =
|
|
3425
|
+
const byName = useMemo4(() => new Map(fields.map((f) => [f.name, f])), [fields]);
|
|
3426
|
+
const [drag, setDrag] = useState5(null);
|
|
3427
|
+
const [adding, setAdding] = useState5(false);
|
|
2125
3428
|
function setOrderBy(next) {
|
|
2126
3429
|
api.setSort(next);
|
|
2127
3430
|
}
|
|
2128
3431
|
function updateTerm(i, patch) {
|
|
2129
3432
|
setOrderBy(orderBy.map((o, k) => k === i ? { ...o, ...patch } : o));
|
|
2130
3433
|
}
|
|
3434
|
+
function clearExtract(i) {
|
|
3435
|
+
setOrderBy(
|
|
3436
|
+
orderBy.map((term, index) => {
|
|
3437
|
+
if (index !== i) return term;
|
|
3438
|
+
const { extract: _extract, ...plainTerm } = term;
|
|
3439
|
+
return plainTerm;
|
|
3440
|
+
})
|
|
3441
|
+
);
|
|
3442
|
+
}
|
|
2131
3443
|
function removeTerm(i) {
|
|
2132
3444
|
setOrderBy(orderBy.filter((_, k) => k !== i));
|
|
2133
3445
|
}
|
|
@@ -2153,7 +3465,7 @@ function OrderRow({
|
|
|
2153
3465
|
setAdding(false);
|
|
2154
3466
|
}
|
|
2155
3467
|
const termFields = orderBy.map((o) => o.field);
|
|
2156
|
-
const termByField =
|
|
3468
|
+
const termByField = useMemo4(() => new Map(orderBy.map((o) => [o.field, o])), [orderBy]);
|
|
2157
3469
|
function previewFields() {
|
|
2158
3470
|
if (!drag) return termFields;
|
|
2159
3471
|
const without = termFields.filter((f) => f !== drag.source);
|
|
@@ -2183,14 +3495,14 @@ function OrderRow({
|
|
|
2183
3495
|
const sortable = fields.filter(isSortable2);
|
|
2184
3496
|
const isDefaultOrderBy = orderBy.length === api.defaults.orderBy.length && orderBy.every((term, i) => {
|
|
2185
3497
|
const next = api.defaults.orderBy[i];
|
|
2186
|
-
return term.field === next?.field && term.dir === next?.dir && (term.nulls ?? "last") === (next?.nulls ?? "last");
|
|
3498
|
+
return term.field === next?.field && term.dir === next?.dir && (term.nulls ?? "last") === (next?.nulls ?? "last") && (term.extract?.regex ?? null) === (next?.extract?.regex ?? null);
|
|
2187
3499
|
});
|
|
2188
|
-
return /* @__PURE__ */
|
|
2189
|
-
/* @__PURE__ */
|
|
2190
|
-
orderBy.length === 0 && !adding && /* @__PURE__ */
|
|
3500
|
+
return /* @__PURE__ */ jsxs6("div", { className: "qt-qb-row", children: [
|
|
3501
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-qb-kw", children: "order by" }),
|
|
3502
|
+
orderBy.length === 0 && !adding && /* @__PURE__ */ jsx6("span", { className: "qt-qb-hint", children: "(default)" }),
|
|
2191
3503
|
rendered.map((f, pos) => {
|
|
2192
3504
|
const term = termByField.get(f);
|
|
2193
|
-
return /* @__PURE__ */
|
|
3505
|
+
return /* @__PURE__ */ jsx6(
|
|
2194
3506
|
OrderTermChip,
|
|
2195
3507
|
{
|
|
2196
3508
|
api,
|
|
@@ -2206,15 +3518,26 @@ function OrderRow({
|
|
|
2206
3518
|
onDragEnd: () => setDrag(null),
|
|
2207
3519
|
disabled,
|
|
2208
3520
|
updateTerm,
|
|
3521
|
+
clearExtract,
|
|
2209
3522
|
removeTerm,
|
|
3523
|
+
position: pos,
|
|
3524
|
+
count: rendered.length,
|
|
3525
|
+
moveTerm: (toIndex) => {
|
|
3526
|
+
const next = [...orderBy];
|
|
3527
|
+
const fromIndex = next.findIndex((candidate) => candidate.field === f);
|
|
3528
|
+
if (fromIndex === -1) return;
|
|
3529
|
+
const [moved] = next.splice(fromIndex, 1);
|
|
3530
|
+
next.splice(toIndex, 0, moved);
|
|
3531
|
+
setOrderBy(next);
|
|
3532
|
+
},
|
|
2210
3533
|
classNames,
|
|
2211
3534
|
label: byName.get(f)?.label ?? f
|
|
2212
3535
|
},
|
|
2213
3536
|
f
|
|
2214
3537
|
);
|
|
2215
3538
|
}),
|
|
2216
|
-
adding ? /* @__PURE__ */
|
|
2217
|
-
!isDefaultOrderBy && /* @__PURE__ */
|
|
3539
|
+
adding ? /* @__PURE__ */ jsx6(FieldPicker, { fields: sortable, onPick: addTerm, onClose: () => setAdding(false) }) : /* @__PURE__ */ jsx6("button", { type: "button", className: "qt-add", onClick: () => setAdding(true), disabled, children: "+ add sort" }),
|
|
3540
|
+
!isDefaultOrderBy && /* @__PURE__ */ jsx6("button", { type: "button", className: "qt-link-btn", onClick: () => api.setSort(api.defaults.orderBy), disabled, children: "reset" })
|
|
2218
3541
|
] });
|
|
2219
3542
|
}
|
|
2220
3543
|
function WindowRow({
|
|
@@ -2223,10 +3546,10 @@ function WindowRow({
|
|
|
2223
3546
|
disabled
|
|
2224
3547
|
}) {
|
|
2225
3548
|
const { query } = api;
|
|
2226
|
-
const [limitDraft, setLimitDraft] =
|
|
2227
|
-
const [offsetDraft, setOffsetDraft] =
|
|
2228
|
-
|
|
2229
|
-
|
|
3549
|
+
const [limitDraft, setLimitDraft] = useState5(String(query.limit));
|
|
3550
|
+
const [offsetDraft, setOffsetDraft] = useState5(String(query.offset));
|
|
3551
|
+
useEffect5(() => setLimitDraft(String(query.limit)), [query.limit]);
|
|
3552
|
+
useEffect5(() => setOffsetDraft(String(query.offset)), [query.offset]);
|
|
2230
3553
|
const hasLimitDefault = query.limit === api.defaults.limit;
|
|
2231
3554
|
const hasOffsetDefault = query.offset === api.defaults.offset;
|
|
2232
3555
|
const hasWindowDefault = hasLimitDefault && hasOffsetDefault;
|
|
@@ -2236,36 +3559,40 @@ function WindowRow({
|
|
|
2236
3559
|
function commitOffset() {
|
|
2237
3560
|
api.setOffset(Math.max(0, Number(offsetDraft) || 0));
|
|
2238
3561
|
}
|
|
2239
|
-
return /* @__PURE__ */
|
|
2240
|
-
/* @__PURE__ */
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
{
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
3562
|
+
return /* @__PURE__ */ jsxs6("div", { className: "qt-qb-row qt-qb-pagination", children: [
|
|
3563
|
+
/* @__PURE__ */ jsxs6("label", { className: "qt-qb-window-field", children: [
|
|
3564
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-qb-kw", children: "limit" }),
|
|
3565
|
+
/* @__PURE__ */ jsx6(
|
|
3566
|
+
"input",
|
|
3567
|
+
{
|
|
3568
|
+
className: cx3("qt-qb-num", classNames?.input),
|
|
3569
|
+
type: "number",
|
|
3570
|
+
min: 1,
|
|
3571
|
+
value: limitDraft,
|
|
3572
|
+
disabled,
|
|
3573
|
+
onChange: (e) => setLimitDraft(e.target.value),
|
|
3574
|
+
onBlur: commitLimit,
|
|
3575
|
+
onKeyDown: (e) => e.key === "Enter" && commitLimit()
|
|
3576
|
+
}
|
|
3577
|
+
)
|
|
3578
|
+
] }),
|
|
3579
|
+
/* @__PURE__ */ jsxs6("label", { className: "qt-qb-window-field", children: [
|
|
3580
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-qb-kw", children: "offset" }),
|
|
3581
|
+
/* @__PURE__ */ jsx6(
|
|
3582
|
+
"input",
|
|
3583
|
+
{
|
|
3584
|
+
className: cx3("qt-qb-num", classNames?.input),
|
|
3585
|
+
type: "number",
|
|
3586
|
+
min: 0,
|
|
3587
|
+
value: offsetDraft,
|
|
3588
|
+
disabled,
|
|
3589
|
+
onChange: (e) => setOffsetDraft(e.target.value),
|
|
3590
|
+
onBlur: commitOffset,
|
|
3591
|
+
onKeyDown: (e) => e.key === "Enter" && commitOffset()
|
|
3592
|
+
}
|
|
3593
|
+
)
|
|
3594
|
+
] }),
|
|
3595
|
+
!hasWindowDefault && /* @__PURE__ */ jsx6(
|
|
2269
3596
|
"button",
|
|
2270
3597
|
{
|
|
2271
3598
|
type: "button",
|
|
@@ -2298,11 +3625,11 @@ function MetricsRow({
|
|
|
2298
3625
|
}) {
|
|
2299
3626
|
const { aggregations } = api;
|
|
2300
3627
|
const clauses = aggregations.clauses;
|
|
2301
|
-
const measurable =
|
|
2302
|
-
const groupable =
|
|
2303
|
-
const [drag, setDrag] =
|
|
3628
|
+
const measurable = useMemo4(() => fields.filter(isMeasurable), [fields]);
|
|
3629
|
+
const groupable = useMemo4(() => fields.filter(isGroupable), [fields]);
|
|
3630
|
+
const [drag, setDrag] = useState5(null);
|
|
2304
3631
|
const ids = clauses.map((c) => c.id);
|
|
2305
|
-
const byId =
|
|
3632
|
+
const byId = useMemo4(() => new Map(clauses.map((c) => [c.id, c])), [clauses]);
|
|
2306
3633
|
function previewIds() {
|
|
2307
3634
|
if (!drag) return ids;
|
|
2308
3635
|
const without = ids.filter((i) => i !== drag.source);
|
|
@@ -2328,12 +3655,12 @@ function MetricsRow({
|
|
|
2328
3655
|
setDrag(null);
|
|
2329
3656
|
}
|
|
2330
3657
|
const rendered = previewIds();
|
|
2331
|
-
return /* @__PURE__ */
|
|
2332
|
-
/* @__PURE__ */
|
|
2333
|
-
clauses.length === 0 && /* @__PURE__ */
|
|
2334
|
-
rendered.map((id) => {
|
|
3658
|
+
return /* @__PURE__ */ jsxs6("div", { className: "qt-qb-row qt-qb-row--metrics", children: [
|
|
3659
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-qb-kw", children: "metrics" }),
|
|
3660
|
+
clauses.length === 0 && /* @__PURE__ */ jsx6("span", { className: "qt-qb-hint", children: "none" }),
|
|
3661
|
+
rendered.map((id, position) => {
|
|
2335
3662
|
const clause = byId.get(id);
|
|
2336
|
-
return /* @__PURE__ */
|
|
3663
|
+
return /* @__PURE__ */ jsx6(
|
|
2337
3664
|
MetricChip,
|
|
2338
3665
|
{
|
|
2339
3666
|
clause,
|
|
@@ -2350,13 +3677,16 @@ function MetricsRow({
|
|
|
2350
3677
|
onChangeField: (field) => aggregations.update(id, { field }),
|
|
2351
3678
|
onAddGroup: (field) => aggregations.update(id, { groupBy: [...clause.groupBy, field] }),
|
|
2352
3679
|
onRemoveGroup: (field) => aggregations.update(id, { groupBy: clause.groupBy.filter((g) => g !== field) }),
|
|
2353
|
-
onRemove: () => aggregations.remove(id)
|
|
3680
|
+
onRemove: () => aggregations.remove(id),
|
|
3681
|
+
position,
|
|
3682
|
+
count: rendered.length,
|
|
3683
|
+
onMove: (toIndex) => aggregations.move(id, toIndex)
|
|
2354
3684
|
},
|
|
2355
3685
|
id
|
|
2356
3686
|
);
|
|
2357
3687
|
}),
|
|
2358
|
-
/* @__PURE__ */
|
|
2359
|
-
clauses.length > 0 && /* @__PURE__ */
|
|
3688
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "qt-add", onClick: () => aggregations.add(), disabled, children: "+ add metric" }),
|
|
3689
|
+
clauses.length > 0 && /* @__PURE__ */ jsx6("button", { type: "button", className: "qt-link-btn", onClick: aggregations.clear, disabled, title: "Remove all metrics", children: "reset" })
|
|
2360
3690
|
] });
|
|
2361
3691
|
}
|
|
2362
3692
|
function changeMetricOp(aggregations, clause, measurable, op) {
|
|
@@ -2380,13 +3710,16 @@ function MetricChip({
|
|
|
2380
3710
|
onChangeField,
|
|
2381
3711
|
onAddGroup,
|
|
2382
3712
|
onRemoveGroup,
|
|
2383
|
-
onRemove
|
|
3713
|
+
onRemove,
|
|
3714
|
+
position,
|
|
3715
|
+
count,
|
|
3716
|
+
onMove
|
|
2384
3717
|
}) {
|
|
2385
|
-
const [addingGroup, setAddingGroup] =
|
|
2386
|
-
const byName =
|
|
3718
|
+
const [addingGroup, setAddingGroup] = useState5(false);
|
|
3719
|
+
const byName = useMemo4(() => new Map(groupable.map((f) => [f.name, f])), [groupable]);
|
|
2387
3720
|
const needsField = aggOpNeedsField(clause.op);
|
|
2388
3721
|
const measureOptions = measurable.filter((f) => aggOpsForField(f).includes(clause.op));
|
|
2389
|
-
return /* @__PURE__ */
|
|
3722
|
+
return /* @__PURE__ */ jsxs6(
|
|
2390
3723
|
"span",
|
|
2391
3724
|
{
|
|
2392
3725
|
className: cx3("qt-chip", "qt-chip--agg", isDragSource && "qt-chip--dragging", classNames?.chip),
|
|
@@ -2404,18 +3737,18 @@ function MetricChip({
|
|
|
2404
3737
|
onDragEnd,
|
|
2405
3738
|
title: "drag to reorder metrics",
|
|
2406
3739
|
children: [
|
|
2407
|
-
/* @__PURE__ */
|
|
2408
|
-
/* @__PURE__ */
|
|
3740
|
+
/* @__PURE__ */ jsx6("span", { "aria-hidden": true, className: "qt-chip-grip", children: "\u22EE\u22EE" }),
|
|
3741
|
+
/* @__PURE__ */ jsx6(
|
|
2409
3742
|
"select",
|
|
2410
3743
|
{
|
|
2411
3744
|
className: cx3("qt-chip-op", classNames?.select),
|
|
2412
3745
|
value: clause.op,
|
|
2413
3746
|
disabled,
|
|
2414
3747
|
onChange: (e) => onChangeOp(e.target.value),
|
|
2415
|
-
children: AGG_OPS.map((op) => /* @__PURE__ */
|
|
3748
|
+
children: AGG_OPS.map((op) => /* @__PURE__ */ jsx6("option", { value: op, children: AGG_OP_LABELS[op] }, op))
|
|
2416
3749
|
}
|
|
2417
3750
|
),
|
|
2418
|
-
/* @__PURE__ */
|
|
3751
|
+
/* @__PURE__ */ jsxs6(
|
|
2419
3752
|
"select",
|
|
2420
3753
|
{
|
|
2421
3754
|
className: cx3("qt-chip-val", classNames?.select),
|
|
@@ -2423,18 +3756,18 @@ function MetricChip({
|
|
|
2423
3756
|
disabled,
|
|
2424
3757
|
onChange: (e) => onChangeField(e.target.value || void 0),
|
|
2425
3758
|
children: [
|
|
2426
|
-
!needsField && /* @__PURE__ */
|
|
2427
|
-
needsField && clause.field == null && /* @__PURE__ */
|
|
2428
|
-
measureOptions.map((f) => /* @__PURE__ */
|
|
3759
|
+
!needsField && /* @__PURE__ */ jsx6("option", { value: "", children: "(all rows)" }),
|
|
3760
|
+
needsField && clause.field == null && /* @__PURE__ */ jsx6("option", { value: "", children: "measure\u2026" }),
|
|
3761
|
+
measureOptions.map((f) => /* @__PURE__ */ jsx6("option", { value: f.name, children: f.label }, f.name))
|
|
2429
3762
|
]
|
|
2430
3763
|
}
|
|
2431
3764
|
),
|
|
2432
|
-
/* @__PURE__ */
|
|
2433
|
-
clause.groupBy.map((g) => /* @__PURE__ */
|
|
2434
|
-
/* @__PURE__ */
|
|
2435
|
-
/* @__PURE__ */
|
|
3765
|
+
/* @__PURE__ */ jsxs6("span", { className: "qt-chip-agg-by", children: [
|
|
3766
|
+
clause.groupBy.map((g) => /* @__PURE__ */ jsxs6("span", { className: "qt-chip-agg-group", children: [
|
|
3767
|
+
/* @__PURE__ */ jsx6("span", { className: "qt-chip-agg-group-label", children: byName.get(g)?.label ?? g }),
|
|
3768
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "qt-chip-x", onClick: () => onRemoveGroup(g), disabled, children: "\u2715" })
|
|
2436
3769
|
] }, g)),
|
|
2437
|
-
addingGroup ? /* @__PURE__ */
|
|
3770
|
+
addingGroup ? /* @__PURE__ */ jsx6(
|
|
2438
3771
|
FieldPicker,
|
|
2439
3772
|
{
|
|
2440
3773
|
fields: groupable,
|
|
@@ -2445,7 +3778,7 @@ function MetricChip({
|
|
|
2445
3778
|
},
|
|
2446
3779
|
onClose: () => setAddingGroup(false)
|
|
2447
3780
|
}
|
|
2448
|
-
) : /* @__PURE__ */
|
|
3781
|
+
) : /* @__PURE__ */ jsx6(
|
|
2449
3782
|
"button",
|
|
2450
3783
|
{
|
|
2451
3784
|
type: "button",
|
|
@@ -2457,16 +3790,26 @@ function MetricChip({
|
|
|
2457
3790
|
}
|
|
2458
3791
|
)
|
|
2459
3792
|
] }),
|
|
2460
|
-
/* @__PURE__ */
|
|
3793
|
+
/* @__PURE__ */ jsx6(
|
|
3794
|
+
MoveButtons,
|
|
3795
|
+
{
|
|
3796
|
+
label: clause.label ?? `${AGG_OP_LABELS[clause.op]} metric`,
|
|
3797
|
+
position,
|
|
3798
|
+
count,
|
|
3799
|
+
disabled,
|
|
3800
|
+
onMove
|
|
3801
|
+
}
|
|
3802
|
+
),
|
|
3803
|
+
/* @__PURE__ */ jsx6("button", { type: "button", className: "qt-chip-x", onClick: onRemove, disabled, title: "Remove metric", children: "\u2715" })
|
|
2461
3804
|
]
|
|
2462
3805
|
}
|
|
2463
3806
|
);
|
|
2464
3807
|
}
|
|
2465
3808
|
|
|
2466
3809
|
// src/MetricsPanel.tsx
|
|
2467
|
-
import { useMemo as
|
|
3810
|
+
import { useMemo as useMemo5 } from "react";
|
|
2468
3811
|
import { EMPTY_QUERY } from "@pythia-software/query-table-core";
|
|
2469
|
-
import { jsx as
|
|
3812
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2470
3813
|
var cx4 = (...parts) => parts.filter((p) => Boolean(p)).join(" ");
|
|
2471
3814
|
var OP_LABELS = {
|
|
2472
3815
|
count: "Count",
|
|
@@ -2484,18 +3827,18 @@ function MetricsPanel({
|
|
|
2484
3827
|
emptyMessage,
|
|
2485
3828
|
classNames
|
|
2486
3829
|
}) {
|
|
2487
|
-
const byName =
|
|
3830
|
+
const byName = useMemo5(() => new Map(fields.map((f) => [f.name, f])), [fields]);
|
|
2488
3831
|
const { clauses, results, loading, error } = aggregations;
|
|
2489
3832
|
if (clauses.length === 0) {
|
|
2490
|
-
return emptyMessage ? /* @__PURE__ */
|
|
3833
|
+
return emptyMessage ? /* @__PURE__ */ jsx7("div", { className: cx4("qt-metrics", classNames?.root), children: emptyMessage }) : null;
|
|
2491
3834
|
}
|
|
2492
3835
|
const resultById = new Map((results?.metrics ?? []).map((m) => [m.id, m.buckets]));
|
|
2493
|
-
return /* @__PURE__ */
|
|
2494
|
-
error ? /* @__PURE__ */
|
|
3836
|
+
return /* @__PURE__ */ jsxs7("div", { className: cx4("qt-metrics", classNames?.root), children: [
|
|
3837
|
+
error ? /* @__PURE__ */ jsxs7("div", { className: "qt-metrics-error", children: [
|
|
2495
3838
|
"Metrics failed: ",
|
|
2496
3839
|
error.message
|
|
2497
3840
|
] }) : null,
|
|
2498
|
-
clauses.map((clause) => /* @__PURE__ */
|
|
3841
|
+
clauses.map((clause) => /* @__PURE__ */ jsx7(
|
|
2499
3842
|
MetricCard,
|
|
2500
3843
|
{
|
|
2501
3844
|
clause,
|
|
@@ -2519,23 +3862,23 @@ function MetricCard({
|
|
|
2519
3862
|
}) {
|
|
2520
3863
|
const measure = clause.field ? byName.get(clause.field) : void 0;
|
|
2521
3864
|
const groupLabels = clause.groupBy.map((g) => byName.get(g)?.label ?? g);
|
|
2522
|
-
const decimals =
|
|
3865
|
+
const decimals = useMemo5(() => cardDecimals(buckets), [buckets]);
|
|
2523
3866
|
const fmt = (v) => formatMetricValue(v, clause.op, measure, renderers, decimals);
|
|
2524
|
-
return /* @__PURE__ */
|
|
2525
|
-
/* @__PURE__ */
|
|
2526
|
-
/* @__PURE__ */
|
|
2527
|
-
groupLabels.length > 0 && /* @__PURE__ */
|
|
3867
|
+
return /* @__PURE__ */ jsxs7("div", { className: cx4("qt-metric", `qt-metric--gb${Math.min(clause.groupBy.length, 3)}`, className), children: [
|
|
3868
|
+
/* @__PURE__ */ jsxs7("div", { className: "qt-metric-head", children: [
|
|
3869
|
+
/* @__PURE__ */ jsx7("span", { className: "qt-metric-title", children: metricTitle(clause, measure) }),
|
|
3870
|
+
groupLabels.length > 0 && /* @__PURE__ */ jsxs7("span", { className: "qt-metric-by", children: [
|
|
2528
3871
|
"by ",
|
|
2529
3872
|
groupLabels.join(" \xD7 ")
|
|
2530
3873
|
] })
|
|
2531
3874
|
] }),
|
|
2532
|
-
loading && buckets.length === 0 ? /* @__PURE__ */
|
|
3875
|
+
loading && buckets.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "qt-metric-loading", children: "\u2026" }) : buckets.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "qt-metric-empty", children: "no data" }) : clause.groupBy.length === 0 ? /* @__PURE__ */ jsx7(BigNumber, { bucket: buckets[0], fmt }) : clause.groupBy.length === 1 ? /* @__PURE__ */ jsx7(BarList, { buckets, fmt }) : clause.groupBy.length === 2 ? /* @__PURE__ */ jsx7(Pivot, { buckets, fmt, rowLabel: groupLabels[0], colLabel: groupLabels[1] }) : /* @__PURE__ */ jsx7(FlatTable, { buckets, fmt, groupLabels, valueLabel: metricTitle(clause, measure) })
|
|
2533
3876
|
] });
|
|
2534
3877
|
}
|
|
2535
3878
|
function BigNumber({ bucket, fmt }) {
|
|
2536
|
-
return /* @__PURE__ */
|
|
2537
|
-
/* @__PURE__ */
|
|
2538
|
-
/* @__PURE__ */
|
|
3879
|
+
return /* @__PURE__ */ jsxs7("div", { className: "qt-metric-big", children: [
|
|
3880
|
+
/* @__PURE__ */ jsx7("span", { className: "qt-metric-big-value", children: fmt(bucket.value) }),
|
|
3881
|
+
/* @__PURE__ */ jsxs7("span", { className: "qt-metric-big-sub", children: [
|
|
2539
3882
|
bucket.count.toLocaleString(),
|
|
2540
3883
|
" rows"
|
|
2541
3884
|
] })
|
|
@@ -2543,12 +3886,12 @@ function BigNumber({ bucket, fmt }) {
|
|
|
2543
3886
|
}
|
|
2544
3887
|
function BarList({ buckets, fmt }) {
|
|
2545
3888
|
const max = Math.max(...buckets.map((b) => barMagnitude(b)), 0);
|
|
2546
|
-
return /* @__PURE__ */
|
|
3889
|
+
return /* @__PURE__ */ jsx7("div", { className: "qt-metric-bars", children: buckets.map((b, i) => {
|
|
2547
3890
|
const pct = max > 0 ? barMagnitude(b) / max * 100 : 0;
|
|
2548
|
-
return /* @__PURE__ */
|
|
2549
|
-
/* @__PURE__ */
|
|
2550
|
-
/* @__PURE__ */
|
|
2551
|
-
/* @__PURE__ */
|
|
3891
|
+
return /* @__PURE__ */ jsxs7("div", { className: "qt-metric-bar-row", children: [
|
|
3892
|
+
/* @__PURE__ */ jsx7("span", { className: "qt-metric-bar-label", title: keyText(b.keys[0]), children: keyNode(b.keys[0]) }),
|
|
3893
|
+
/* @__PURE__ */ jsx7("span", { className: "qt-metric-bar-track", children: /* @__PURE__ */ jsx7("span", { className: "qt-metric-bar-fill", style: { width: `${pct}%` } }) }),
|
|
3894
|
+
/* @__PURE__ */ jsx7("span", { className: "qt-metric-bar-value", children: fmt(b.value) })
|
|
2552
3895
|
] }, `${keyText(b.keys[0])}-${i}`);
|
|
2553
3896
|
}) });
|
|
2554
3897
|
}
|
|
@@ -2562,22 +3905,22 @@ function Pivot({
|
|
|
2562
3905
|
const colKeys = distinctKeys(buckets, 1);
|
|
2563
3906
|
const cell = /* @__PURE__ */ new Map();
|
|
2564
3907
|
for (const b of buckets) cell.set(`${keyText(b.keys[0])}\0${keyText(b.keys[1])}`, b.value);
|
|
2565
|
-
return /* @__PURE__ */
|
|
2566
|
-
/* @__PURE__ */
|
|
2567
|
-
/* @__PURE__ */
|
|
3908
|
+
return /* @__PURE__ */ jsx7("div", { className: "qt-metric-pivot-wrap", children: /* @__PURE__ */ jsxs7("table", { className: "qt-metric-pivot", children: [
|
|
3909
|
+
/* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsxs7("tr", { children: [
|
|
3910
|
+
/* @__PURE__ */ jsx7("th", { className: "qt-metric-pivot-corner", children: /* @__PURE__ */ jsxs7("span", { className: "qt-metric-pivot-axes", children: [
|
|
2568
3911
|
rowLabel,
|
|
2569
3912
|
" ",
|
|
2570
|
-
/* @__PURE__ */
|
|
3913
|
+
/* @__PURE__ */ jsx7("span", { className: "qt-muted", children: "\\" }),
|
|
2571
3914
|
" ",
|
|
2572
3915
|
colLabel
|
|
2573
3916
|
] }) }),
|
|
2574
|
-
colKeys.map((c) => /* @__PURE__ */
|
|
3917
|
+
colKeys.map((c) => /* @__PURE__ */ jsx7("th", { className: "qt-metric-pivot-col", children: keyNode(c) }, keyText(c)))
|
|
2575
3918
|
] }) }),
|
|
2576
|
-
/* @__PURE__ */
|
|
2577
|
-
/* @__PURE__ */
|
|
3919
|
+
/* @__PURE__ */ jsx7("tbody", { children: rowKeys.map((r) => /* @__PURE__ */ jsxs7("tr", { children: [
|
|
3920
|
+
/* @__PURE__ */ jsx7("th", { className: "qt-metric-pivot-row", children: keyNode(r) }),
|
|
2578
3921
|
colKeys.map((c) => {
|
|
2579
3922
|
const v = cell.get(`${keyText(r)}\0${keyText(c)}`);
|
|
2580
|
-
return /* @__PURE__ */
|
|
3923
|
+
return /* @__PURE__ */ jsx7("td", { className: "qt-metric-pivot-cell", children: v === void 0 ? /* @__PURE__ */ jsx7("span", { className: "qt-muted", children: "\xB7" }) : fmt(v) }, keyText(c));
|
|
2581
3924
|
})
|
|
2582
3925
|
] }, keyText(r))) })
|
|
2583
3926
|
] }) });
|
|
@@ -2586,16 +3929,16 @@ function FlatTable({
|
|
|
2586
3929
|
buckets,
|
|
2587
3930
|
fmt,
|
|
2588
3931
|
groupLabels,
|
|
2589
|
-
valueLabel
|
|
3932
|
+
valueLabel: valueLabel2
|
|
2590
3933
|
}) {
|
|
2591
|
-
return /* @__PURE__ */
|
|
2592
|
-
/* @__PURE__ */
|
|
2593
|
-
groupLabels.map((g) => /* @__PURE__ */
|
|
2594
|
-
/* @__PURE__ */
|
|
3934
|
+
return /* @__PURE__ */ jsx7("div", { className: "qt-metric-pivot-wrap", children: /* @__PURE__ */ jsxs7("table", { className: "qt-metric-flat", children: [
|
|
3935
|
+
/* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsxs7("tr", { children: [
|
|
3936
|
+
groupLabels.map((g) => /* @__PURE__ */ jsx7("th", { children: g }, g)),
|
|
3937
|
+
/* @__PURE__ */ jsx7("th", { className: "qt-metric-flat-value", children: valueLabel2 })
|
|
2595
3938
|
] }) }),
|
|
2596
|
-
/* @__PURE__ */
|
|
2597
|
-
groupLabels.map((_, j) => /* @__PURE__ */
|
|
2598
|
-
/* @__PURE__ */
|
|
3939
|
+
/* @__PURE__ */ jsx7("tbody", { children: buckets.map((b, i) => /* @__PURE__ */ jsxs7("tr", { children: [
|
|
3940
|
+
groupLabels.map((_, j) => /* @__PURE__ */ jsx7("td", { children: keyNode(b.keys[j]) }, j)),
|
|
3941
|
+
/* @__PURE__ */ jsx7("td", { className: "qt-metric-flat-value", children: fmt(b.value) })
|
|
2599
3942
|
] }, i)) })
|
|
2600
3943
|
] }) });
|
|
2601
3944
|
}
|
|
@@ -2606,7 +3949,7 @@ function metricTitle(clause, measure) {
|
|
|
2606
3949
|
}
|
|
2607
3950
|
var UNIT_RENDER_KEYS = /* @__PURE__ */ new Set(["duration_ms", "byte_size"]);
|
|
2608
3951
|
function formatMetricValue(value, op, measure, renderers, decimals) {
|
|
2609
|
-
if (value == null) return /* @__PURE__ */
|
|
3952
|
+
if (value == null) return /* @__PURE__ */ jsx7("span", { className: "qt-muted", children: "\u2014" });
|
|
2610
3953
|
if (op === "count" || op === "count_distinct") {
|
|
2611
3954
|
return typeof value === "number" ? formatNumber(value, 0) : String(value);
|
|
2612
3955
|
}
|
|
@@ -2669,21 +4012,21 @@ function keyText(k) {
|
|
|
2669
4012
|
return k == null ? NULL_KEY : String(k);
|
|
2670
4013
|
}
|
|
2671
4014
|
function keyNode(k) {
|
|
2672
|
-
return k == null ? /* @__PURE__ */
|
|
4015
|
+
return k == null ? /* @__PURE__ */ jsx7("span", { className: "qt-muted", children: NULL_KEY }) : String(k);
|
|
2673
4016
|
}
|
|
2674
4017
|
|
|
2675
4018
|
// src/SelectionToolbar.tsx
|
|
2676
|
-
import { jsx as
|
|
4019
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2677
4020
|
var cx5 = (...parts) => parts.filter(Boolean).join(" ");
|
|
2678
4021
|
function SelectionToolbar({ selection, actions, alwaysShow, className }) {
|
|
2679
4022
|
if (selection.count === 0 && !alwaysShow) return null;
|
|
2680
4023
|
const selectedIds = [...selection.selected];
|
|
2681
|
-
return /* @__PURE__ */
|
|
2682
|
-
/* @__PURE__ */
|
|
4024
|
+
return /* @__PURE__ */ jsxs8("div", { className: cx5("qt-selection-toolbar", className), role: "toolbar", children: [
|
|
4025
|
+
/* @__PURE__ */ jsxs8("span", { className: "qt-selection-count", children: [
|
|
2683
4026
|
selection.count,
|
|
2684
4027
|
" selected"
|
|
2685
4028
|
] }),
|
|
2686
|
-
/* @__PURE__ */
|
|
4029
|
+
/* @__PURE__ */ jsx8(
|
|
2687
4030
|
"button",
|
|
2688
4031
|
{
|
|
2689
4032
|
type: "button",
|
|
@@ -2693,7 +4036,7 @@ function SelectionToolbar({ selection, actions, alwaysShow, className }) {
|
|
|
2693
4036
|
children: "clear"
|
|
2694
4037
|
}
|
|
2695
4038
|
),
|
|
2696
|
-
/* @__PURE__ */
|
|
4039
|
+
/* @__PURE__ */ jsx8("span", { className: "qt-selection-actions", children: actions(selectedIds, selection) })
|
|
2697
4040
|
] });
|
|
2698
4041
|
}
|
|
2699
4042
|
export {
|
|
@@ -2703,6 +4046,7 @@ export {
|
|
|
2703
4046
|
MetricsPanel,
|
|
2704
4047
|
QueryBuilder,
|
|
2705
4048
|
SavedQueriesModal,
|
|
4049
|
+
SelectColumnEditor,
|
|
2706
4050
|
SelectionToolbar,
|
|
2707
4051
|
defaultRenderers,
|
|
2708
4052
|
resolveRenderer,
|