@reopt-ai/opt-ui 1.6.0 → 1.7.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/COMPONENT_CATALOG.md +95 -12
- package/dist/app.css +24 -0
- package/dist/core/index.cjs +3 -3
- package/dist/core/index.d.cts +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +2 -2
- package/dist/docs/02-components/03-shells.md +104 -11
- package/dist/docs/02-components/index.md +3 -3
- package/dist/docs/05-migration/01-breaking-changes.md +18 -1
- package/dist/field-sidebar-BTZCBviA.d.cts +1031 -0
- package/dist/field-sidebar-BTZCBviA.d.cts.map +1 -0
- package/dist/field-sidebar-CB8vjvUM.d.ts +1031 -0
- package/dist/field-sidebar-CB8vjvUM.d.ts.map +1 -0
- package/dist/{workflow-canvas-BGBAcjf-.js → field-sidebar-Cs-J-5VE.js} +1882 -52
- package/dist/field-sidebar-Cs-J-5VE.js.map +1 -0
- package/dist/{workflow-canvas-3Jeevkgy.cjs → field-sidebar-CzIQX70-.cjs} +2010 -48
- package/dist/id-registry.cjs +41 -1
- package/dist/id-registry.d.cts +1 -1
- package/dist/id-registry.d.cts.map +1 -1
- package/dist/id-registry.d.ts +1 -1
- package/dist/id-registry.d.ts.map +1 -1
- package/dist/id-registry.js +41 -1
- package/dist/id-registry.js.map +1 -1
- package/dist/id-registry.json +85 -0
- package/dist/index.cjs +142 -56
- package/dist/index.d.cts +149 -80
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +149 -80
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -11
- package/dist/index.js.map +1 -1
- package/dist/meta.cjs +292 -0
- package/dist/meta.d.cts.map +1 -1
- package/dist/meta.d.ts.map +1 -1
- package/dist/meta.js +292 -0
- package/dist/meta.js.map +1 -1
- package/dist/{pagination-By7Ozv3U.js → pagination-DWbvACuN.js} +93 -31
- package/dist/pagination-DWbvACuN.js.map +1 -0
- package/dist/{pagination-Uv3CnGJz.cjs → pagination-DjfxBcjX.cjs} +98 -36
- package/dist/query.cjs +386 -0
- package/dist/query.d.cts +141 -0
- package/dist/query.d.cts.map +1 -0
- package/dist/query.d.ts +141 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +371 -0
- package/dist/query.js.map +1 -0
- package/dist/{shader-surface-BWhO2xkk.js → shader-surface-BUPZ0P-n.js} +22 -83
- package/dist/shader-surface-BUPZ0P-n.js.map +1 -0
- package/dist/{shader-surface-DIVWtpFl.d.ts → shader-surface-BfTP9c1Q.d.ts} +139 -139
- package/dist/shader-surface-BfTP9c1Q.d.ts.map +1 -0
- package/dist/{shader-surface-BK1GMt5_.d.cts → shader-surface-DRrcLrWf.d.cts} +139 -139
- package/dist/shader-surface-DRrcLrWf.d.cts.map +1 -0
- package/dist/{shader-surface-DxLLfrE6.cjs → shader-surface-Fav5K9UC.cjs} +20 -87
- package/dist/shells/index.cjs +31 -24
- package/dist/shells/index.d.cts +2 -2
- package/dist/shells/index.d.ts +2 -2
- package/dist/shells/index.js +3 -2
- package/dist/theme/presets/default.css +2 -2
- package/dist/theme/presets/minimal.css +2 -2
- package/dist/theme/presets/mono-dark.css +2 -2
- package/dist/theme/presets/natural.css +1 -1
- package/dist/theme/presets/pro.css +2 -2
- package/package.json +17 -6
- package/dist/pagination-By7Ozv3U.js.map +0 -1
- package/dist/shader-surface-BK1GMt5_.d.cts.map +0 -1
- package/dist/shader-surface-BWhO2xkk.js.map +0 -1
- package/dist/shader-surface-DIVWtpFl.d.ts.map +0 -1
- package/dist/workflow-canvas-BGBAcjf-.js.map +0 -1
- package/dist/workflow-canvas-Bar_O2SQ.d.ts +0 -494
- package/dist/workflow-canvas-Bar_O2SQ.d.ts.map +0 -1
- package/dist/workflow-canvas-DghDRtHn.d.cts +0 -494
- package/dist/workflow-canvas-DghDRtHn.d.cts.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
const require_pagination = require("./pagination-
|
|
2
|
+
const require_pagination = require("./pagination-DjfxBcjX.cjs");
|
|
3
3
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
4
|
let react = require("react");
|
|
5
5
|
react = require_pagination.__toESM(react, 1);
|
|
6
6
|
let _reopt_ai_opt_ui_primitives = require("@reopt-ai/opt-ui-primitives");
|
|
7
7
|
let lucide_react = require("lucide-react");
|
|
8
|
+
let _reopt_ai_opt_charts = require("@reopt-ai/opt-charts");
|
|
8
9
|
let match_sorter = require("match-sorter");
|
|
9
10
|
let next_link = require("next/link");
|
|
10
11
|
next_link = require_pagination.__toESM(next_link, 1);
|
|
@@ -228,7 +229,6 @@ function CommandPalette({ commands, open, onOpenChange, onSelect, inline = false
|
|
|
228
229
|
//#region src/shells/command-palette-trigger.tsx
|
|
229
230
|
/** Renders the command palette trigger component. */
|
|
230
231
|
function CommandPaletteTrigger({ commands, onSelect, labels }) {
|
|
231
|
-
if (commands.length === 0) return null;
|
|
232
232
|
const [open, setOpen] = (0, react.useState)(false);
|
|
233
233
|
const handleKeyDown = (0, react.useCallback)((e) => {
|
|
234
234
|
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
@@ -237,9 +237,11 @@ function CommandPaletteTrigger({ commands, onSelect, labels }) {
|
|
|
237
237
|
}
|
|
238
238
|
}, []);
|
|
239
239
|
(0, react.useEffect)(() => {
|
|
240
|
+
if (commands.length === 0) return;
|
|
240
241
|
document.addEventListener("keydown", handleKeyDown);
|
|
241
242
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
242
|
-
}, [handleKeyDown]);
|
|
243
|
+
}, [commands.length, handleKeyDown]);
|
|
244
|
+
if (commands.length === 0) return null;
|
|
243
245
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandPalette, {
|
|
244
246
|
commands,
|
|
245
247
|
open,
|
|
@@ -252,7 +254,7 @@ function CommandPaletteTrigger({ commands, onSelect, labels }) {
|
|
|
252
254
|
}
|
|
253
255
|
//#endregion
|
|
254
256
|
//#region src/shells/dashboard-grid.tsx
|
|
255
|
-
const defaultLabels$
|
|
257
|
+
const defaultLabels$18 = { ariaLabel: "대시보드 통계" };
|
|
256
258
|
const MIN_DASHBOARD_COLUMNS = 1;
|
|
257
259
|
const MAX_DASHBOARD_COLUMNS = 12;
|
|
258
260
|
function normalizeDashboardColumns(columns) {
|
|
@@ -261,7 +263,7 @@ function normalizeDashboardColumns(columns) {
|
|
|
261
263
|
}
|
|
262
264
|
/** Renders the dashboard grid component. */
|
|
263
265
|
function DashboardGrid({ stats, columns = 3, labels: customLabels }) {
|
|
264
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
266
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$18), customLabels);
|
|
265
267
|
const columnCount = normalizeDashboardColumns(columns);
|
|
266
268
|
const rowStyle = { "--opt-dashboard-grid-columns": `repeat(${columnCount}, minmax(0, 1fr))` };
|
|
267
269
|
const rows = [];
|
|
@@ -289,13 +291,13 @@ function DashboardGrid({ stats, columns = 3, labels: customLabels }) {
|
|
|
289
291
|
}
|
|
290
292
|
//#endregion
|
|
291
293
|
//#region src/shells/search-combobox.tsx
|
|
292
|
-
const defaultLabels$
|
|
294
|
+
const defaultLabels$17 = {
|
|
293
295
|
noResults: "결과가 없습니다.",
|
|
294
296
|
loading: "Loading…"
|
|
295
297
|
};
|
|
296
298
|
/** Renders the search combobox component. */
|
|
297
299
|
function SearchCombobox({ items, placeholder = "검색...", label = "검색", onSelect, labels: customLabels, async: isAsync = false, onSearch, debounceMs = 300 }) {
|
|
298
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
300
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$17), customLabels);
|
|
299
301
|
const inputId = (0, react.useId)();
|
|
300
302
|
const [query, setQuery] = (0, react.useState)("");
|
|
301
303
|
const [asyncResults, setAsyncResults] = (0, react.useState)([]);
|
|
@@ -393,14 +395,14 @@ function SearchCombobox({ items, placeholder = "검색...", label = "검색", on
|
|
|
393
395
|
}
|
|
394
396
|
//#endregion
|
|
395
397
|
//#region src/shells/status-select.tsx
|
|
396
|
-
const defaultLabels$
|
|
398
|
+
const defaultLabels$16 = {
|
|
397
399
|
selectLabel: "상태",
|
|
398
400
|
emptyMessage: "사용 가능한 상태 옵션이 없습니다."
|
|
399
401
|
};
|
|
400
402
|
/** Renders the status select component. */
|
|
401
403
|
function StatusSelect({ options, value: controlledValue, onChange, label, labels: customLabels }) {
|
|
402
404
|
var _options$0$value, _options$;
|
|
403
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
405
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$16), customLabels);
|
|
404
406
|
const resolvedLabel = label !== null && label !== void 0 ? label : labels.selectLabel;
|
|
405
407
|
const [internalValue, setInternalValue] = (0, react.useState)((_options$0$value = (_options$ = options[0]) === null || _options$ === void 0 ? void 0 : _options$.value) !== null && _options$0$value !== void 0 ? _options$0$value : "");
|
|
406
408
|
const value = controlledValue !== null && controlledValue !== void 0 ? controlledValue : internalValue;
|
|
@@ -467,7 +469,7 @@ function StatusSelect({ options, value: controlledValue, onChange, label, labels
|
|
|
467
469
|
}
|
|
468
470
|
//#endregion
|
|
469
471
|
//#region src/shells/app-menubar.tsx
|
|
470
|
-
const defaultLabels$
|
|
472
|
+
const defaultLabels$15 = { ariaLabel: "Application menu" };
|
|
471
473
|
function MenuSection({ menu }) {
|
|
472
474
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.MenuRoot, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.MenuTrigger, { children: [menu.label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenuButtonArrow, {})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenuPopover, { children: menu.items.map((item, i) => {
|
|
473
475
|
if (item.type === "separator") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenuSeparator, {}, `sep-${i}`);
|
|
@@ -511,7 +513,7 @@ function MenuSection({ menu }) {
|
|
|
511
513
|
}
|
|
512
514
|
/** Renders the app menubar component. */
|
|
513
515
|
function AppMenubar({ menus, labels: customLabels }) {
|
|
514
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
516
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$15), customLabels);
|
|
515
517
|
if (menus.length === 0) return null;
|
|
516
518
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.MenubarRoot, {
|
|
517
519
|
"data-opt-id": "BGKHW",
|
|
@@ -550,11 +552,10 @@ const sectionStyles = {
|
|
|
550
552
|
Templates: "text-text-tertiary"
|
|
551
553
|
};
|
|
552
554
|
const defaultSectionStyle = "text-text-secondary";
|
|
553
|
-
const defaultLabels$
|
|
555
|
+
const defaultLabels$14 = { ariaLabel: "문서 네비게이션" };
|
|
554
556
|
/** Renders the sidebar nav component. */
|
|
555
557
|
function SidebarNav({ items, labels: customLabels }) {
|
|
556
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
557
|
-
if (items.length === 0) return null;
|
|
558
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$14), customLabels);
|
|
558
559
|
const pathname = (0, next_navigation.usePathname)();
|
|
559
560
|
const activeItem = items.find((item) => item.href === pathname);
|
|
560
561
|
const [activeId, setActiveId] = (0, react.useState)(activeItem ? domId(activeItem.id) : void 0);
|
|
@@ -562,6 +563,7 @@ function SidebarNav({ items, labels: customLabels }) {
|
|
|
562
563
|
const match = items.find((item) => item.href === pathname);
|
|
563
564
|
if (match) setActiveId(domId(match.id));
|
|
564
565
|
}, [pathname, items]);
|
|
566
|
+
if (items.length === 0) return null;
|
|
565
567
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_ui_primitives.CompositeProvider, {
|
|
566
568
|
focusLoop: true,
|
|
567
569
|
orientation: "vertical",
|
|
@@ -601,7 +603,7 @@ function SidebarNav({ items, labels: customLabels }) {
|
|
|
601
603
|
}
|
|
602
604
|
//#endregion
|
|
603
605
|
//#region src/shells/editor-toolbar.tsx
|
|
604
|
-
const defaultLabels$
|
|
606
|
+
const defaultLabels$13 = { ariaLabel: "텍스트 편집기 툴바" };
|
|
605
607
|
function ToolbarButtonWithTooltip({ button, active, onAction }) {
|
|
606
608
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.TooltipProvider, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.TooltipAnchor, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.ToolbarButton, {
|
|
607
609
|
className: `text-text-primary data-[active-item]:bg-bg-subtle data-[focus-visible]:ring-ring rounded-md p-2 transition-colors outline-none data-[focus-visible]:ring-2 ${active ? "bg-bg-muted" : "hover:bg-bg-subtle"}`,
|
|
@@ -611,7 +613,7 @@ function ToolbarButtonWithTooltip({ button, active, onAction }) {
|
|
|
611
613
|
}
|
|
612
614
|
/** Renders the editor toolbar component. */
|
|
613
615
|
function EditorToolbar({ buttons, onAction, activeButtons = [], labels: customLabels }) {
|
|
614
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
616
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$13), customLabels);
|
|
615
617
|
const groups = [];
|
|
616
618
|
let current = [];
|
|
617
619
|
for (const btn of buttons) {
|
|
@@ -644,21 +646,21 @@ function EditorToolbar({ buttons, onAction, activeButtons = [], labels: customLa
|
|
|
644
646
|
}
|
|
645
647
|
//#endregion
|
|
646
648
|
//#region src/shells/content-tabs.tsx
|
|
647
|
-
const defaultLabels$
|
|
649
|
+
const defaultLabels$12 = {
|
|
648
650
|
ariaLabel: "콘텐츠 탭",
|
|
649
651
|
emptyMessage: "표시할 탭이 없습니다."
|
|
650
652
|
};
|
|
651
653
|
/** Renders the content tabs component. */
|
|
652
654
|
function ContentTabs({ tabs, defaultTabId, labels: customLabels }) {
|
|
653
655
|
var _tabs$, _tabs$find;
|
|
654
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
656
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$12), customLabels);
|
|
657
|
+
const [selectedTabId, setSelectedTabId] = (0, react.useState)(defaultTabId !== null && defaultTabId !== void 0 ? defaultTabId : (_tabs$ = tabs[0]) === null || _tabs$ === void 0 ? void 0 : _tabs$.id);
|
|
655
658
|
if (tabs.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
656
659
|
className: "text-text-secondary text-sm",
|
|
657
660
|
"data-opt-id": "9W97K",
|
|
658
661
|
"data-opt-slug": "content-tabs.ContentTabs",
|
|
659
662
|
children: labels.emptyMessage
|
|
660
663
|
});
|
|
661
|
-
const [selectedTabId, setSelectedTabId] = (0, react.useState)(defaultTabId !== null && defaultTabId !== void 0 ? defaultTabId : (_tabs$ = tabs[0]) === null || _tabs$ === void 0 ? void 0 : _tabs$.id);
|
|
662
664
|
const handleSelectedTabId = (nextId) => {
|
|
663
665
|
var _tabs$2;
|
|
664
666
|
setSelectedTabId(nextId !== null && nextId !== void 0 ? nextId : (_tabs$2 = tabs[0]) === null || _tabs$2 === void 0 ? void 0 : _tabs$2.id);
|
|
@@ -683,13 +685,13 @@ function ContentTabs({ tabs, defaultTabId, labels: customLabels }) {
|
|
|
683
685
|
}
|
|
684
686
|
//#endregion
|
|
685
687
|
//#region src/shells/faq-accordion.tsx
|
|
686
|
-
const defaultLabels$
|
|
688
|
+
const defaultLabels$11 = {
|
|
687
689
|
ariaLabel: "자주 묻는 질문",
|
|
688
690
|
emptyMessage: "등록된 FAQ가 없습니다."
|
|
689
691
|
};
|
|
690
692
|
/** Renders the faq accordion component. */
|
|
691
693
|
function FaqAccordion({ items, labels: customLabels }) {
|
|
692
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
694
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$11), customLabels);
|
|
693
695
|
if (items.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
694
696
|
className: "text-text-secondary text-sm",
|
|
695
697
|
"data-opt-id": "QRBHV",
|
|
@@ -729,21 +731,15 @@ function FaqAccordion({ items, labels: customLabels }) {
|
|
|
729
731
|
}
|
|
730
732
|
//#endregion
|
|
731
733
|
//#region src/shells/settings-form.tsx
|
|
732
|
-
const defaultLabels$
|
|
734
|
+
const defaultLabels$10 = {
|
|
733
735
|
formAriaLabel: "설정 폼",
|
|
734
736
|
submitButton: "저장",
|
|
735
737
|
successMessage: "설정이 저장되었습니다."
|
|
736
738
|
};
|
|
737
739
|
/** Renders the settings form component. */
|
|
738
740
|
function SettingsForm({ fields, onSubmit, labels: customLabels }) {
|
|
739
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
741
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$10), customLabels);
|
|
740
742
|
const [submitted, setSubmitted] = (0, react.useState)(false);
|
|
741
|
-
if (fields.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.EmptyState, {
|
|
742
|
-
title: "설정 항목이 없습니다.",
|
|
743
|
-
description: "표시할 설정 필드를 추가하면 이 폼이 렌더링됩니다.",
|
|
744
|
-
"data-opt-id": "9TEEJ",
|
|
745
|
-
"data-opt-slug": "settings-form.SettingsForm"
|
|
746
|
-
});
|
|
747
743
|
const defaultValues = {};
|
|
748
744
|
for (const field of fields) {
|
|
749
745
|
var _field$defaultValue;
|
|
@@ -774,6 +770,12 @@ function SettingsForm({ fields, onSubmit, labels: customLabels }) {
|
|
|
774
770
|
onSubmit === null || onSubmit === void 0 || onSubmit(values);
|
|
775
771
|
}
|
|
776
772
|
});
|
|
773
|
+
if (fields.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.EmptyState, {
|
|
774
|
+
title: "설정 항목이 없습니다.",
|
|
775
|
+
description: "표시할 설정 필드를 추가하면 이 폼이 렌더링됩니다.",
|
|
776
|
+
"data-opt-id": "9TEEJ",
|
|
777
|
+
"data-opt-slug": "settings-form.SettingsForm"
|
|
778
|
+
});
|
|
777
779
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
778
780
|
className: "gap-group flex flex-col",
|
|
779
781
|
"data-opt-id": "9TEEJ",
|
|
@@ -1062,7 +1064,7 @@ function ProjectSwitcher({ projects, value, onChange, placeholder = "프로젝
|
|
|
1062
1064
|
ProjectSwitcher.displayName = "ProjectSwitcher";
|
|
1063
1065
|
//#endregion
|
|
1064
1066
|
//#region src/shells/env-panel.tsx
|
|
1065
|
-
const defaultLabels$
|
|
1067
|
+
const defaultLabels$9 = {
|
|
1066
1068
|
varCount: (count) => `${count}개 변수`,
|
|
1067
1069
|
envVarsLabel: (env) => `${env} 환경 변수`,
|
|
1068
1070
|
secretBadge: "secret"
|
|
@@ -1074,7 +1076,7 @@ const envDot = {
|
|
|
1074
1076
|
};
|
|
1075
1077
|
/** Renders the env panel component. */
|
|
1076
1078
|
function EnvPanel({ groups, labels: customLabels }) {
|
|
1077
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
1079
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$9), customLabels);
|
|
1078
1080
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1079
1081
|
className: `rounded-lg ${require_pagination.OPT_BORDER} divide-y ${require_pagination.OPT_DIVIDER} bg-surface`,
|
|
1080
1082
|
"data-opt-id": "ZBDWK",
|
|
@@ -1147,7 +1149,7 @@ function EnvPanel({ groups, labels: customLabels }) {
|
|
|
1147
1149
|
}
|
|
1148
1150
|
//#endregion
|
|
1149
1151
|
//#region src/shells/branch-select.tsx
|
|
1150
|
-
const defaultLabels$
|
|
1152
|
+
const defaultLabels$8 = {
|
|
1151
1153
|
selectLabel: "브랜치",
|
|
1152
1154
|
emptySelectionLabel: "선택...",
|
|
1153
1155
|
emptyMessage: "사용 가능한 브랜치가 없습니다.",
|
|
@@ -1156,7 +1158,7 @@ const defaultLabels$3 = {
|
|
|
1156
1158
|
/** Renders the branch select component. */
|
|
1157
1159
|
function BranchSelect({ branches, value: controlledValue, onChange, label, labels: customLabels }) {
|
|
1158
1160
|
var _ref, _defaultBranch$name, _branches$, _selected$name;
|
|
1159
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
1161
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$8), customLabels);
|
|
1160
1162
|
const resolvedLabel = label !== null && label !== void 0 ? label : labels.selectLabel;
|
|
1161
1163
|
const defaultBranch = branches.find((b) => b.isDefault);
|
|
1162
1164
|
const [internalValue, setInternalValue] = (0, react.useState)((_ref = (_defaultBranch$name = defaultBranch === null || defaultBranch === void 0 ? void 0 : defaultBranch.name) !== null && _defaultBranch$name !== void 0 ? _defaultBranch$name : (_branches$ = branches[0]) === null || _branches$ === void 0 ? void 0 : _branches$.name) !== null && _ref !== void 0 ? _ref : "");
|
|
@@ -1265,7 +1267,7 @@ function BranchSelect({ branches, value: controlledValue, onChange, label, label
|
|
|
1265
1267
|
BranchSelect.displayName = "BranchSelect";
|
|
1266
1268
|
//#endregion
|
|
1267
1269
|
//#region src/shells/domain-table.tsx
|
|
1268
|
-
const defaultLabels$
|
|
1270
|
+
const defaultLabels$7 = {
|
|
1269
1271
|
domain: "Domain",
|
|
1270
1272
|
status: "Status",
|
|
1271
1273
|
ssl: "SSL",
|
|
@@ -1295,7 +1297,7 @@ const sslLabel = {
|
|
|
1295
1297
|
};
|
|
1296
1298
|
/** Renders the domain table component. */
|
|
1297
1299
|
function DomainTable({ domains, title = "도메인", labels: customLabels }) {
|
|
1298
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$
|
|
1300
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$7), customLabels);
|
|
1299
1301
|
if (domains.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1300
1302
|
className: "gap-group flex flex-col",
|
|
1301
1303
|
"data-opt-id": "A7SRM",
|
|
@@ -1901,7 +1903,7 @@ function NotificationToast({ toast, onDismiss, defaultDuration = 5e3 }) {
|
|
|
1901
1903
|
}
|
|
1902
1904
|
//#endregion
|
|
1903
1905
|
//#region src/shells/failure-list.tsx
|
|
1904
|
-
const defaultLabels$
|
|
1906
|
+
const defaultLabels$6 = {
|
|
1905
1907
|
title: "최근 실패",
|
|
1906
1908
|
emptyMessage: "실패 항목이 없습니다",
|
|
1907
1909
|
moreItems: "{count}개 더 보기",
|
|
@@ -2000,20 +2002,20 @@ function FailureItemRow({ failure, isLast, onFailureClick, renderActions, merged
|
|
|
2000
2002
|
function FailureList({ failures, title, maxItems, grouped = false, onFailureClick, onShowMore, labels: customLabels, locale = "ko-KR", loading = false, renderActions, className }) {
|
|
2001
2003
|
var _customLabels$timeAgo, _customLabels$timeAgo2, _customLabels$timeAgo3, _customLabels$timeAgo4, _customLabels$timeAgo5, _customLabels$timeAgo6, _customLabels$timeAgo7, _customLabels$timeAgo8, _customLabels$severit, _customLabels$severit2, _customLabels$severit3, _customLabels$severit4, _customLabels$severit5, _customLabels$severit6, _customLabels$title, _customLabels$emptyMe, _customLabels$moreIte;
|
|
2002
2004
|
const mergedTimeAgo = {
|
|
2003
|
-
justNow: (_customLabels$timeAgo = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo2 = customLabels.timeAgo) === null || _customLabels$timeAgo2 === void 0 ? void 0 : _customLabels$timeAgo2.justNow) !== null && _customLabels$timeAgo !== void 0 ? _customLabels$timeAgo : defaultLabels$
|
|
2004
|
-
minutesAgo: (_customLabels$timeAgo3 = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo4 = customLabels.timeAgo) === null || _customLabels$timeAgo4 === void 0 ? void 0 : _customLabels$timeAgo4.minutesAgo) !== null && _customLabels$timeAgo3 !== void 0 ? _customLabels$timeAgo3 : defaultLabels$
|
|
2005
|
-
hoursAgo: (_customLabels$timeAgo5 = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo6 = customLabels.timeAgo) === null || _customLabels$timeAgo6 === void 0 ? void 0 : _customLabels$timeAgo6.hoursAgo) !== null && _customLabels$timeAgo5 !== void 0 ? _customLabels$timeAgo5 : defaultLabels$
|
|
2006
|
-
daysAgo: (_customLabels$timeAgo7 = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo8 = customLabels.timeAgo) === null || _customLabels$timeAgo8 === void 0 ? void 0 : _customLabels$timeAgo8.daysAgo) !== null && _customLabels$timeAgo7 !== void 0 ? _customLabels$timeAgo7 : defaultLabels$
|
|
2005
|
+
justNow: (_customLabels$timeAgo = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo2 = customLabels.timeAgo) === null || _customLabels$timeAgo2 === void 0 ? void 0 : _customLabels$timeAgo2.justNow) !== null && _customLabels$timeAgo !== void 0 ? _customLabels$timeAgo : defaultLabels$6.timeAgo.justNow,
|
|
2006
|
+
minutesAgo: (_customLabels$timeAgo3 = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo4 = customLabels.timeAgo) === null || _customLabels$timeAgo4 === void 0 ? void 0 : _customLabels$timeAgo4.minutesAgo) !== null && _customLabels$timeAgo3 !== void 0 ? _customLabels$timeAgo3 : defaultLabels$6.timeAgo.minutesAgo,
|
|
2007
|
+
hoursAgo: (_customLabels$timeAgo5 = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo6 = customLabels.timeAgo) === null || _customLabels$timeAgo6 === void 0 ? void 0 : _customLabels$timeAgo6.hoursAgo) !== null && _customLabels$timeAgo5 !== void 0 ? _customLabels$timeAgo5 : defaultLabels$6.timeAgo.hoursAgo,
|
|
2008
|
+
daysAgo: (_customLabels$timeAgo7 = customLabels === null || customLabels === void 0 || (_customLabels$timeAgo8 = customLabels.timeAgo) === null || _customLabels$timeAgo8 === void 0 ? void 0 : _customLabels$timeAgo8.daysAgo) !== null && _customLabels$timeAgo7 !== void 0 ? _customLabels$timeAgo7 : defaultLabels$6.timeAgo.daysAgo
|
|
2007
2009
|
};
|
|
2008
2010
|
const mergedSeverityLabels = {
|
|
2009
|
-
critical: (_customLabels$severit = customLabels === null || customLabels === void 0 || (_customLabels$severit2 = customLabels.severityLabels) === null || _customLabels$severit2 === void 0 ? void 0 : _customLabels$severit2.critical) !== null && _customLabels$severit !== void 0 ? _customLabels$severit : defaultLabels$
|
|
2010
|
-
error: (_customLabels$severit3 = customLabels === null || customLabels === void 0 || (_customLabels$severit4 = customLabels.severityLabels) === null || _customLabels$severit4 === void 0 ? void 0 : _customLabels$severit4.error) !== null && _customLabels$severit3 !== void 0 ? _customLabels$severit3 : defaultLabels$
|
|
2011
|
-
warning: (_customLabels$severit5 = customLabels === null || customLabels === void 0 || (_customLabels$severit6 = customLabels.severityLabels) === null || _customLabels$severit6 === void 0 ? void 0 : _customLabels$severit6.warning) !== null && _customLabels$severit5 !== void 0 ? _customLabels$severit5 : defaultLabels$
|
|
2011
|
+
critical: (_customLabels$severit = customLabels === null || customLabels === void 0 || (_customLabels$severit2 = customLabels.severityLabels) === null || _customLabels$severit2 === void 0 ? void 0 : _customLabels$severit2.critical) !== null && _customLabels$severit !== void 0 ? _customLabels$severit : defaultLabels$6.severityLabels.critical,
|
|
2012
|
+
error: (_customLabels$severit3 = customLabels === null || customLabels === void 0 || (_customLabels$severit4 = customLabels.severityLabels) === null || _customLabels$severit4 === void 0 ? void 0 : _customLabels$severit4.error) !== null && _customLabels$severit3 !== void 0 ? _customLabels$severit3 : defaultLabels$6.severityLabels.error,
|
|
2013
|
+
warning: (_customLabels$severit5 = customLabels === null || customLabels === void 0 || (_customLabels$severit6 = customLabels.severityLabels) === null || _customLabels$severit6 === void 0 ? void 0 : _customLabels$severit6.warning) !== null && _customLabels$severit5 !== void 0 ? _customLabels$severit5 : defaultLabels$6.severityLabels.warning
|
|
2012
2014
|
};
|
|
2013
2015
|
const labels = {
|
|
2014
|
-
title: (_customLabels$title = customLabels === null || customLabels === void 0 ? void 0 : customLabels.title) !== null && _customLabels$title !== void 0 ? _customLabels$title : defaultLabels$
|
|
2015
|
-
emptyMessage: (_customLabels$emptyMe = customLabels === null || customLabels === void 0 ? void 0 : customLabels.emptyMessage) !== null && _customLabels$emptyMe !== void 0 ? _customLabels$emptyMe : defaultLabels$
|
|
2016
|
-
moreItems: (_customLabels$moreIte = customLabels === null || customLabels === void 0 ? void 0 : customLabels.moreItems) !== null && _customLabels$moreIte !== void 0 ? _customLabels$moreIte : defaultLabels$
|
|
2016
|
+
title: (_customLabels$title = customLabels === null || customLabels === void 0 ? void 0 : customLabels.title) !== null && _customLabels$title !== void 0 ? _customLabels$title : defaultLabels$6.title,
|
|
2017
|
+
emptyMessage: (_customLabels$emptyMe = customLabels === null || customLabels === void 0 ? void 0 : customLabels.emptyMessage) !== null && _customLabels$emptyMe !== void 0 ? _customLabels$emptyMe : defaultLabels$6.emptyMessage,
|
|
2018
|
+
moreItems: (_customLabels$moreIte = customLabels === null || customLabels === void 0 ? void 0 : customLabels.moreItems) !== null && _customLabels$moreIte !== void 0 ? _customLabels$moreIte : defaultLabels$6.moreItems
|
|
2017
2019
|
};
|
|
2018
2020
|
const displayTitle = title !== null && title !== void 0 ? title : labels.title;
|
|
2019
2021
|
const allItems = maxItems ? failures.slice(0, maxItems) : failures;
|
|
@@ -2089,7 +2091,7 @@ function renderGrouped(items, remainingCount, rowProps, labels, onShowMore) {
|
|
|
2089
2091
|
}
|
|
2090
2092
|
//#endregion
|
|
2091
2093
|
//#region src/shells/key-value-editor.tsx
|
|
2092
|
-
const defaultLabels = {
|
|
2094
|
+
const defaultLabels$5 = {
|
|
2093
2095
|
keyPlaceholder: "키",
|
|
2094
2096
|
valuePlaceholder: "값",
|
|
2095
2097
|
addButton: "추가",
|
|
@@ -2103,7 +2105,7 @@ function generateId() {
|
|
|
2103
2105
|
}
|
|
2104
2106
|
/** Renders the key value editor component. */
|
|
2105
2107
|
function KeyValueEditor({ value: controlledValue, defaultValue, onChange, showSecretToggle = false, maxEntries, readOnly = false, labels: customLabels, renderActions, validateKey, className }) {
|
|
2106
|
-
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels), customLabels);
|
|
2108
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$5), customLabels);
|
|
2107
2109
|
const [internalEntries, setInternalEntries] = (0, react.useState)(() => defaultValue !== null && defaultValue !== void 0 ? defaultValue : []);
|
|
2108
2110
|
const entries = controlledValue !== null && controlledValue !== void 0 ? controlledValue : internalEntries;
|
|
2109
2111
|
const keyInputRef = (0, react.useRef)(null);
|
|
@@ -2399,6 +2401,1834 @@ function WorkflowCanvas({ steps, connections = [], rows = 9, cols = 9, cellSize
|
|
|
2399
2401
|
}
|
|
2400
2402
|
WorkflowCanvas.displayName = "WorkflowCanvas";
|
|
2401
2403
|
//#endregion
|
|
2404
|
+
//#region src/lib/use-auto-refresh.ts
|
|
2405
|
+
/**
|
|
2406
|
+
* A refresh timer that waits for its own work.
|
|
2407
|
+
*
|
|
2408
|
+
* A plain `setInterval` fires on a schedule regardless of whether the previous
|
|
2409
|
+
* query came back, so a five-second interval over a seven-second query stacks
|
|
2410
|
+
* requests until something gives. Chaining the next wait onto the settled
|
|
2411
|
+
* promise keeps exactly one refresh in flight, and a rejection stops the loop
|
|
2412
|
+
* instead of hammering an endpoint that is already failing.
|
|
2413
|
+
*/
|
|
2414
|
+
function useAutoRefresh({ interval, paused = false, onRefresh }) {
|
|
2415
|
+
const callbackRef = (0, react.useRef)(onRefresh);
|
|
2416
|
+
callbackRef.current = onRefresh;
|
|
2417
|
+
(0, react.useEffect)(() => {
|
|
2418
|
+
if (paused || interval <= 0 || !callbackRef.current) return;
|
|
2419
|
+
let cancelled = false;
|
|
2420
|
+
let timer;
|
|
2421
|
+
const schedule = () => {
|
|
2422
|
+
timer = setTimeout(async () => {
|
|
2423
|
+
if (cancelled) return;
|
|
2424
|
+
try {
|
|
2425
|
+
var _callbackRef$current;
|
|
2426
|
+
await ((_callbackRef$current = callbackRef.current) === null || _callbackRef$current === void 0 ? void 0 : _callbackRef$current.call(callbackRef));
|
|
2427
|
+
} catch (_unused) {
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
if (!cancelled) schedule();
|
|
2431
|
+
}, interval);
|
|
2432
|
+
};
|
|
2433
|
+
schedule();
|
|
2434
|
+
return () => {
|
|
2435
|
+
cancelled = true;
|
|
2436
|
+
if (timer) clearTimeout(timer);
|
|
2437
|
+
};
|
|
2438
|
+
}, [interval, paused]);
|
|
2439
|
+
}
|
|
2440
|
+
//#endregion
|
|
2441
|
+
//#region src/shells/time-range-control.tsx
|
|
2442
|
+
const defaultLabels$4 = {
|
|
2443
|
+
trigger: "Select time range",
|
|
2444
|
+
invalid: "Invalid time range",
|
|
2445
|
+
last: (duration) => `Last ${duration}`,
|
|
2446
|
+
next: (duration) => `Next ${duration}`,
|
|
2447
|
+
quickSelect: "Quick select",
|
|
2448
|
+
apply: "Apply",
|
|
2449
|
+
update: "Update",
|
|
2450
|
+
refresh: "Refresh",
|
|
2451
|
+
commonlyUsed: "Commonly used",
|
|
2452
|
+
recentlyUsed: "Recently used",
|
|
2453
|
+
start: "Start",
|
|
2454
|
+
end: "End",
|
|
2455
|
+
startHint: "Absolute time or date math, e.g. now-7d",
|
|
2456
|
+
autoRefresh: "Auto refresh",
|
|
2457
|
+
autoRefreshOff: "Off",
|
|
2458
|
+
every: (duration) => `Every ${duration}`,
|
|
2459
|
+
previous: "Previous window",
|
|
2460
|
+
nextWindow: "Next window",
|
|
2461
|
+
zoomIn: "Zoom in",
|
|
2462
|
+
zoomOut: "Zoom out",
|
|
2463
|
+
thisUnit: (unit) => `This ${unit}`,
|
|
2464
|
+
units: {
|
|
2465
|
+
s: "seconds",
|
|
2466
|
+
m: "minutes",
|
|
2467
|
+
h: "hours",
|
|
2468
|
+
d: "days",
|
|
2469
|
+
w: "weeks",
|
|
2470
|
+
M: "months",
|
|
2471
|
+
y: "years"
|
|
2472
|
+
}
|
|
2473
|
+
};
|
|
2474
|
+
const QUICK_UNITS = [
|
|
2475
|
+
"m",
|
|
2476
|
+
"h",
|
|
2477
|
+
"d",
|
|
2478
|
+
"w",
|
|
2479
|
+
"M",
|
|
2480
|
+
"y"
|
|
2481
|
+
];
|
|
2482
|
+
const REFRESH_INTERVALS = [
|
|
2483
|
+
0,
|
|
2484
|
+
5e3,
|
|
2485
|
+
1e4,
|
|
2486
|
+
3e4,
|
|
2487
|
+
6e4,
|
|
2488
|
+
3e5
|
|
2489
|
+
];
|
|
2490
|
+
function selectValue(value) {
|
|
2491
|
+
var _value$;
|
|
2492
|
+
return Array.isArray(value) ? (_value$ = value[0]) !== null && _value$ !== void 0 ? _value$ : "" : value;
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
* What the trigger says.
|
|
2496
|
+
*
|
|
2497
|
+
* A window anchored to `now` deserves its intent ("Last 7 days"), not two
|
|
2498
|
+
* timestamps the reader has to subtract. Everything else gets both ends
|
|
2499
|
+
* spelled out. A duration that does not divide into whole minutes is prefixed
|
|
2500
|
+
* with `~` rather than rounded silently.
|
|
2501
|
+
*/
|
|
2502
|
+
function describeRange(range, labels, options) {
|
|
2503
|
+
const window = (0, _reopt_ai_opt_charts.measureTimeWindow)(range, {
|
|
2504
|
+
timeZone: options.timeZone,
|
|
2505
|
+
now: options.now
|
|
2506
|
+
});
|
|
2507
|
+
if (window.isInvalid) return {
|
|
2508
|
+
text: labels.invalid,
|
|
2509
|
+
isInvalid: true
|
|
2510
|
+
};
|
|
2511
|
+
const fromParts = (0, _reopt_ai_opt_charts.parseRelativeParts)(range.from);
|
|
2512
|
+
const toParts = (0, _reopt_ai_opt_charts.parseRelativeParts)(range.to);
|
|
2513
|
+
if ((fromParts === null || fromParts === void 0 ? void 0 : fromParts.roundUnit) && fromParts.count === 0 && (toParts === null || toParts === void 0 ? void 0 : toParts.roundUnit) === fromParts.roundUnit && toParts.count === 0) return {
|
|
2514
|
+
text: labels.thisUnit((0, _reopt_ai_opt_charts.formatCountUnit)(1, fromParts.roundUnit, options.locale).replace(/^1\s*/, "")),
|
|
2515
|
+
isInvalid: false
|
|
2516
|
+
};
|
|
2517
|
+
if ((0, _reopt_ai_opt_charts.isRelativeToNow)(range)) {
|
|
2518
|
+
var _toParts$count;
|
|
2519
|
+
const anchoredAtEnd = ((_toParts$count = toParts === null || toParts === void 0 ? void 0 : toParts.count) !== null && _toParts$count !== void 0 ? _toParts$count : 0) === 0;
|
|
2520
|
+
const parts = anchoredAtEnd ? fromParts : toParts;
|
|
2521
|
+
const bothSnapped = (parts === null || parts === void 0 ? void 0 : parts.roundUnit) === (parts === null || parts === void 0 ? void 0 : parts.unit) && (toParts === null || toParts === void 0 ? void 0 : toParts.roundUnit) === (parts === null || parts === void 0 ? void 0 : parts.unit);
|
|
2522
|
+
const counted = parts && parts.count > 0 ? (0, _reopt_ai_opt_charts.formatCountUnit)(parts.roundUnit && bothSnapped ? parts.count + 1 : parts.count, parts.unit, options.locale) : null;
|
|
2523
|
+
const exact = counted !== null && (!(parts === null || parts === void 0 ? void 0 : parts.roundUnit) || bothSnapped);
|
|
2524
|
+
const duration = exact ? counted : (0, _reopt_ai_opt_charts.formatDuration)(window.duration, options.locale);
|
|
2525
|
+
const prefix = exact || (0, _reopt_ai_opt_charts.isExactMinutes)(window.duration) ? "" : "~";
|
|
2526
|
+
return {
|
|
2527
|
+
text: anchoredAtEnd ? labels.last(`${prefix}${duration}`) : labels.next(`${prefix}${duration}`),
|
|
2528
|
+
isInvalid: false
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
2531
|
+
const dateOnly = (0, _reopt_ai_opt_charts.isDateOnly)(range.from) && (0, _reopt_ai_opt_charts.isDateOnly)(range.to);
|
|
2532
|
+
const from = (0, _reopt_ai_opt_charts.formatInstant)(window.from, require_pagination._objectSpread2(require_pagination._objectSpread2({}, options), {}, { dateOnly }));
|
|
2533
|
+
const to = (0, _reopt_ai_opt_charts.formatInstant)(window.to, require_pagination._objectSpread2(require_pagination._objectSpread2({}, options), {}, { dateOnly }));
|
|
2534
|
+
return {
|
|
2535
|
+
text: from === to ? from : `${from} – ${to}`,
|
|
2536
|
+
isInvalid: false
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
/**
|
|
2540
|
+
* TimeRangeControl — the window as an expression, not two frozen instants.
|
|
2541
|
+
*
|
|
2542
|
+
* Holding `{ from: "now-7d", to: "now" }` instead of a resolved pair is what
|
|
2543
|
+
* makes a saved board, a shared link and an auto-refresh timer all mean the
|
|
2544
|
+
* same thing tomorrow that they mean today. Around that sit the moves people
|
|
2545
|
+
* actually make on a chart: jump to a named window, step the same width back
|
|
2546
|
+
* and forth to compare, widen when the interesting thing is just off screen,
|
|
2547
|
+
* and let it re-query on a timer while they watch.
|
|
2548
|
+
*
|
|
2549
|
+
* @example
|
|
2550
|
+
* ```tsx
|
|
2551
|
+
* const [range, setRange] = useState<TimeRange>({ from: "now-7d", to: "now" });
|
|
2552
|
+
*
|
|
2553
|
+
* <TimeRangeControl
|
|
2554
|
+
* value={range}
|
|
2555
|
+
* onChange={setRange}
|
|
2556
|
+
* timeZone="Asia/Seoul"
|
|
2557
|
+
* showWindowButtons
|
|
2558
|
+
* onRefresh={() => refetch()}
|
|
2559
|
+
* />
|
|
2560
|
+
* ```
|
|
2561
|
+
*/
|
|
2562
|
+
function TimeRangeControl({ value, onChange, presets, recentRanges, timeZone, locale, showUpdateButton = true, showWindowButtons = true, zoomFactor = .5, refresh, onRefreshChange, onRefresh, isLoading = false, disabled = false, className, labels: customLabels, "aria-label": ariaLabel }) {
|
|
2563
|
+
var _refresh$interval, _refresh$paused, _refresh$interval2;
|
|
2564
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$4), customLabels), {}, { units: require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$4.units), customLabels === null || customLabels === void 0 ? void 0 : customLabels.units) });
|
|
2565
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2566
|
+
const [draft, setDraft] = (0, react.useState)(null);
|
|
2567
|
+
const [quickCount, setQuickCount] = (0, react.useState)("15");
|
|
2568
|
+
const [quickUnit, setQuickUnit] = (0, react.useState)("m");
|
|
2569
|
+
const editing = draft !== null && draft !== void 0 ? draft : value;
|
|
2570
|
+
const hasChanges = draft !== null && (draft.from !== value.from || draft.to !== value.to);
|
|
2571
|
+
const description = (0, react.useMemo)(() => describeRange(value, labels, {
|
|
2572
|
+
locale,
|
|
2573
|
+
timeZone
|
|
2574
|
+
}), [
|
|
2575
|
+
value,
|
|
2576
|
+
locale,
|
|
2577
|
+
timeZone,
|
|
2578
|
+
customLabels
|
|
2579
|
+
]);
|
|
2580
|
+
const boundState = (0, react.useCallback)((expression) => (0, _reopt_ai_opt_charts.parseTimeExpression)(expression, { timeZone }) === null ? labels.invalid : void 0, [timeZone, labels]);
|
|
2581
|
+
const commit = (0, react.useCallback)((range) => {
|
|
2582
|
+
setDraft(null);
|
|
2583
|
+
onChange(range);
|
|
2584
|
+
}, [onChange]);
|
|
2585
|
+
const apply = (0, react.useCallback)(() => {
|
|
2586
|
+
if (draft) commit(draft);
|
|
2587
|
+
else onRefresh === null || onRefresh === void 0 || onRefresh();
|
|
2588
|
+
}, [
|
|
2589
|
+
draft,
|
|
2590
|
+
commit,
|
|
2591
|
+
onRefresh
|
|
2592
|
+
]);
|
|
2593
|
+
const commitOnEnter = (0, react.useCallback)((event) => {
|
|
2594
|
+
if (event.key !== "Enter") return;
|
|
2595
|
+
apply();
|
|
2596
|
+
setOpen(false);
|
|
2597
|
+
}, [apply]);
|
|
2598
|
+
const applyQuick = (0, react.useCallback)(() => {
|
|
2599
|
+
const count = Number(quickCount);
|
|
2600
|
+
if (!Number.isFinite(count) || count <= 0) return;
|
|
2601
|
+
commit({
|
|
2602
|
+
from: `now-${Math.round(count)}${quickUnit}`,
|
|
2603
|
+
to: "now"
|
|
2604
|
+
});
|
|
2605
|
+
setOpen(false);
|
|
2606
|
+
}, [
|
|
2607
|
+
quickCount,
|
|
2608
|
+
quickUnit,
|
|
2609
|
+
commit
|
|
2610
|
+
]);
|
|
2611
|
+
const step = (0, react.useCallback)((direction) => {
|
|
2612
|
+
const next = (0, _reopt_ai_opt_charts.shiftTimeRange)(value, direction, { timeZone });
|
|
2613
|
+
if (next) commit(next);
|
|
2614
|
+
}, [
|
|
2615
|
+
value,
|
|
2616
|
+
timeZone,
|
|
2617
|
+
commit
|
|
2618
|
+
]);
|
|
2619
|
+
const zoom = (0, react.useCallback)((factor) => {
|
|
2620
|
+
const next = (0, _reopt_ai_opt_charts.zoomTimeRange)(value, factor, { timeZone });
|
|
2621
|
+
if (next) commit(next);
|
|
2622
|
+
}, [
|
|
2623
|
+
value,
|
|
2624
|
+
timeZone,
|
|
2625
|
+
commit
|
|
2626
|
+
]);
|
|
2627
|
+
useAutoRefresh({
|
|
2628
|
+
interval: (_refresh$interval = refresh === null || refresh === void 0 ? void 0 : refresh.interval) !== null && _refresh$interval !== void 0 ? _refresh$interval : 0,
|
|
2629
|
+
paused: (_refresh$paused = refresh === null || refresh === void 0 ? void 0 : refresh.paused) !== null && _refresh$paused !== void 0 ? _refresh$paused : true,
|
|
2630
|
+
onRefresh
|
|
2631
|
+
});
|
|
2632
|
+
const refreshLabel = !refresh || refresh.paused || refresh.interval === 0 ? labels.autoRefreshOff : labels.every((0, _reopt_ai_opt_charts.formatDuration)(refresh.interval, locale));
|
|
2633
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2634
|
+
className: require_pagination.cn("bg-surface inline-flex items-center rounded-lg border", require_pagination.OPT_BORDER, className),
|
|
2635
|
+
role: "group",
|
|
2636
|
+
"aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : labels.trigger,
|
|
2637
|
+
"data-opt-id": "3WJ8H",
|
|
2638
|
+
"data-opt-slug": "time-range-control.TimeRangeControl",
|
|
2639
|
+
children: [
|
|
2640
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.PopoverRoot, {
|
|
2641
|
+
open,
|
|
2642
|
+
onOpenChange: setOpen,
|
|
2643
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.PopoverTrigger, {
|
|
2644
|
+
className: require_pagination.cn("text-text-primary h-10 gap-2", description.isInvalid && "text-danger-fg"),
|
|
2645
|
+
disabled,
|
|
2646
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Clock, {
|
|
2647
|
+
className: "text-text-tertiary h-4 w-4",
|
|
2648
|
+
"aria-hidden": "true"
|
|
2649
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2650
|
+
className: "truncate text-sm",
|
|
2651
|
+
children: description.text
|
|
2652
|
+
})]
|
|
2653
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.PopoverContent, {
|
|
2654
|
+
className: "gap-group flex w-80 flex-col",
|
|
2655
|
+
children: [
|
|
2656
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2657
|
+
className: "gap-element flex flex-col",
|
|
2658
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2659
|
+
className: "text-text-primary text-xs font-medium",
|
|
2660
|
+
children: labels.quickSelect
|
|
2661
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2662
|
+
className: "flex items-end gap-2",
|
|
2663
|
+
children: [
|
|
2664
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Input, {
|
|
2665
|
+
"aria-label": labels.quickSelect,
|
|
2666
|
+
type: "number",
|
|
2667
|
+
min: 1,
|
|
2668
|
+
size: "sm",
|
|
2669
|
+
className: "w-20",
|
|
2670
|
+
value: quickCount,
|
|
2671
|
+
onChange: (event) => setQuickCount(event.target.value),
|
|
2672
|
+
onKeyDown: (event) => {
|
|
2673
|
+
if (event.key === "Enter") applyQuick();
|
|
2674
|
+
}
|
|
2675
|
+
}),
|
|
2676
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.SelectRoot, {
|
|
2677
|
+
value: quickUnit,
|
|
2678
|
+
setValue: (next) => setQuickUnit(selectValue(next)),
|
|
2679
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectTrigger, {
|
|
2680
|
+
minWidth: 110,
|
|
2681
|
+
"aria-label": labels.quickSelect
|
|
2682
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectPopover, { children: QUICK_UNITS.map((unit) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectItem, {
|
|
2683
|
+
value: unit,
|
|
2684
|
+
children: labels.units[unit]
|
|
2685
|
+
}, unit)) })]
|
|
2686
|
+
}),
|
|
2687
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Button, {
|
|
2688
|
+
variant: "secondary",
|
|
2689
|
+
size: "sm",
|
|
2690
|
+
onClick: applyQuick,
|
|
2691
|
+
children: labels.apply
|
|
2692
|
+
})
|
|
2693
|
+
]
|
|
2694
|
+
})]
|
|
2695
|
+
}),
|
|
2696
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2697
|
+
className: "gap-element flex flex-col",
|
|
2698
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Input, {
|
|
2699
|
+
label: labels.start,
|
|
2700
|
+
size: "sm",
|
|
2701
|
+
value: editing.from,
|
|
2702
|
+
error: boundState(editing.from),
|
|
2703
|
+
hint: labels.startHint,
|
|
2704
|
+
onChange: (event) => setDraft(require_pagination._objectSpread2(require_pagination._objectSpread2({}, editing), {}, { from: event.target.value })),
|
|
2705
|
+
onKeyDown: commitOnEnter
|
|
2706
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Input, {
|
|
2707
|
+
label: labels.end,
|
|
2708
|
+
size: "sm",
|
|
2709
|
+
value: editing.to,
|
|
2710
|
+
error: boundState(editing.to),
|
|
2711
|
+
onChange: (event) => setDraft(require_pagination._objectSpread2(require_pagination._objectSpread2({}, editing), {}, { to: event.target.value })),
|
|
2712
|
+
onKeyDown: commitOnEnter
|
|
2713
|
+
})]
|
|
2714
|
+
}),
|
|
2715
|
+
presets && presets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RangeList, {
|
|
2716
|
+
title: labels.commonlyUsed,
|
|
2717
|
+
items: presets,
|
|
2718
|
+
onPick: (range) => {
|
|
2719
|
+
commit(range);
|
|
2720
|
+
setOpen(false);
|
|
2721
|
+
}
|
|
2722
|
+
}),
|
|
2723
|
+
recentRanges && recentRanges.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RangeList, {
|
|
2724
|
+
title: labels.recentlyUsed,
|
|
2725
|
+
items: recentRanges.slice(0, 5),
|
|
2726
|
+
onPick: (range) => {
|
|
2727
|
+
commit(range);
|
|
2728
|
+
setOpen(false);
|
|
2729
|
+
}
|
|
2730
|
+
}),
|
|
2731
|
+
onRefreshChange && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2732
|
+
className: "gap-element flex flex-col",
|
|
2733
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2734
|
+
className: "text-text-primary text-xs font-medium",
|
|
2735
|
+
children: labels.autoRefresh
|
|
2736
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_pagination.SelectRoot, {
|
|
2737
|
+
value: String((refresh === null || refresh === void 0 ? void 0 : refresh.paused) ? 0 : (_refresh$interval2 = refresh === null || refresh === void 0 ? void 0 : refresh.interval) !== null && _refresh$interval2 !== void 0 ? _refresh$interval2 : 0),
|
|
2738
|
+
setValue: (next) => {
|
|
2739
|
+
const interval = Number(selectValue(next));
|
|
2740
|
+
onRefreshChange({
|
|
2741
|
+
interval,
|
|
2742
|
+
paused: interval === 0
|
|
2743
|
+
});
|
|
2744
|
+
},
|
|
2745
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectTrigger, {
|
|
2746
|
+
minWidth: 140,
|
|
2747
|
+
"aria-label": labels.autoRefresh
|
|
2748
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectPopover, { children: REFRESH_INTERVALS.map((interval) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.SelectItem, {
|
|
2749
|
+
value: String(interval),
|
|
2750
|
+
children: interval === 0 ? labels.autoRefreshOff : labels.every((0, _reopt_ai_opt_charts.formatDuration)(interval, locale))
|
|
2751
|
+
}, interval)) })]
|
|
2752
|
+
})]
|
|
2753
|
+
})
|
|
2754
|
+
]
|
|
2755
|
+
})]
|
|
2756
|
+
}),
|
|
2757
|
+
showWindowButtons && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2758
|
+
className: require_pagination.cn("flex items-center border-l", "border border-border"),
|
|
2759
|
+
role: "group",
|
|
2760
|
+
"aria-label": labels.previous,
|
|
2761
|
+
children: [
|
|
2762
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
|
|
2763
|
+
label: labels.previous,
|
|
2764
|
+
disabled: disabled || description.isInvalid,
|
|
2765
|
+
onClick: () => step(-1),
|
|
2766
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, {
|
|
2767
|
+
className: "h-4 w-4",
|
|
2768
|
+
"aria-hidden": "true"
|
|
2769
|
+
})
|
|
2770
|
+
}),
|
|
2771
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
|
|
2772
|
+
label: labels.zoomIn,
|
|
2773
|
+
disabled: disabled || description.isInvalid,
|
|
2774
|
+
onClick: () => zoom(-zoomFactor),
|
|
2775
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ZoomIn, {
|
|
2776
|
+
className: "h-4 w-4",
|
|
2777
|
+
"aria-hidden": "true"
|
|
2778
|
+
})
|
|
2779
|
+
}),
|
|
2780
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
|
|
2781
|
+
label: labels.zoomOut,
|
|
2782
|
+
disabled: disabled || description.isInvalid,
|
|
2783
|
+
onClick: () => zoom(zoomFactor),
|
|
2784
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ZoomOut, {
|
|
2785
|
+
className: "h-4 w-4",
|
|
2786
|
+
"aria-hidden": "true"
|
|
2787
|
+
})
|
|
2788
|
+
}),
|
|
2789
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
|
|
2790
|
+
label: labels.nextWindow,
|
|
2791
|
+
disabled: disabled || description.isInvalid,
|
|
2792
|
+
onClick: () => step(1),
|
|
2793
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
|
|
2794
|
+
className: "h-4 w-4",
|
|
2795
|
+
"aria-hidden": "true"
|
|
2796
|
+
})
|
|
2797
|
+
})
|
|
2798
|
+
]
|
|
2799
|
+
}),
|
|
2800
|
+
(onRefresh || showUpdateButton) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2801
|
+
className: require_pagination.cn("border-l pl-2 pr-2", "border border-border"),
|
|
2802
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Button, {
|
|
2803
|
+
variant: hasChanges ? "primary" : "secondary",
|
|
2804
|
+
size: "sm",
|
|
2805
|
+
loading: isLoading,
|
|
2806
|
+
disabled: disabled || description.isInvalid,
|
|
2807
|
+
onClick: apply,
|
|
2808
|
+
leftIcon: hasChanges ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.RefreshCw, {
|
|
2809
|
+
className: "h-3.5 w-3.5",
|
|
2810
|
+
"aria-hidden": "true"
|
|
2811
|
+
}),
|
|
2812
|
+
title: refreshLabel,
|
|
2813
|
+
children: hasChanges ? labels.update : labels.refresh
|
|
2814
|
+
})
|
|
2815
|
+
})
|
|
2816
|
+
]
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
TimeRangeControl.displayName = "TimeRangeControl";
|
|
2820
|
+
function IconButton({ label, disabled, onClick, children }) {
|
|
2821
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Button, {
|
|
2822
|
+
variant: "ghost",
|
|
2823
|
+
size: "sm",
|
|
2824
|
+
"aria-label": label,
|
|
2825
|
+
title: label,
|
|
2826
|
+
disabled,
|
|
2827
|
+
onClick,
|
|
2828
|
+
children
|
|
2829
|
+
});
|
|
2830
|
+
}
|
|
2831
|
+
function RangeList({ title, items, onPick }) {
|
|
2832
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2833
|
+
className: "gap-element flex flex-col",
|
|
2834
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2835
|
+
className: "text-text-primary text-xs font-medium",
|
|
2836
|
+
children: title
|
|
2837
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
2838
|
+
className: "grid grid-cols-2 gap-1",
|
|
2839
|
+
children: items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2840
|
+
type: "button",
|
|
2841
|
+
className: require_pagination.cn("hover:bg-bg-subtle w-full cursor-pointer rounded px-2 py-1 text-left text-xs", require_pagination.OPT_TEXT_SECONDARY),
|
|
2842
|
+
onClick: () => onPick(item.range),
|
|
2843
|
+
children: item.label
|
|
2844
|
+
}) }, `${item.label}-${index}`))
|
|
2845
|
+
})]
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* The windows most analytics tools open with. Exported so a caller gets a
|
|
2850
|
+
* sensible list without inventing date math of their own.
|
|
2851
|
+
*/
|
|
2852
|
+
const DEFAULT_TIME_PRESETS = [
|
|
2853
|
+
{
|
|
2854
|
+
label: "Last 15 minutes",
|
|
2855
|
+
range: {
|
|
2856
|
+
from: "now-15m",
|
|
2857
|
+
to: "now"
|
|
2858
|
+
}
|
|
2859
|
+
},
|
|
2860
|
+
{
|
|
2861
|
+
label: "Last hour",
|
|
2862
|
+
range: {
|
|
2863
|
+
from: "now-1h",
|
|
2864
|
+
to: "now"
|
|
2865
|
+
}
|
|
2866
|
+
},
|
|
2867
|
+
{
|
|
2868
|
+
label: "Last 24 hours",
|
|
2869
|
+
range: {
|
|
2870
|
+
from: "now-24h",
|
|
2871
|
+
to: "now"
|
|
2872
|
+
}
|
|
2873
|
+
},
|
|
2874
|
+
{
|
|
2875
|
+
label: "Last 7 days",
|
|
2876
|
+
range: {
|
|
2877
|
+
from: "now-7d",
|
|
2878
|
+
to: "now"
|
|
2879
|
+
}
|
|
2880
|
+
},
|
|
2881
|
+
{
|
|
2882
|
+
label: "Last 30 days",
|
|
2883
|
+
range: {
|
|
2884
|
+
from: "now-30d",
|
|
2885
|
+
to: "now"
|
|
2886
|
+
}
|
|
2887
|
+
},
|
|
2888
|
+
{
|
|
2889
|
+
label: "Last 90 days",
|
|
2890
|
+
range: {
|
|
2891
|
+
from: "now-90d",
|
|
2892
|
+
to: "now"
|
|
2893
|
+
}
|
|
2894
|
+
},
|
|
2895
|
+
{
|
|
2896
|
+
label: "Today",
|
|
2897
|
+
range: {
|
|
2898
|
+
from: "now/d",
|
|
2899
|
+
to: "now/d"
|
|
2900
|
+
}
|
|
2901
|
+
},
|
|
2902
|
+
{
|
|
2903
|
+
label: "This week",
|
|
2904
|
+
range: {
|
|
2905
|
+
from: "now/w",
|
|
2906
|
+
to: "now/w"
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
];
|
|
2910
|
+
//#endregion
|
|
2911
|
+
//#region src/shells/time-series-panel.tsx
|
|
2912
|
+
const defaultLabels$3 = {
|
|
2913
|
+
back: "Back to previous range",
|
|
2914
|
+
legend: "Series"
|
|
2915
|
+
};
|
|
2916
|
+
/**
|
|
2917
|
+
* TimeSeriesPanel — a chart you can steer.
|
|
2918
|
+
*
|
|
2919
|
+
* The point of putting the control and the chart in one component is the
|
|
2920
|
+
* loop between them: drag across the plot and the window narrows to what you
|
|
2921
|
+
* dragged, which re-queries, which redraws the axis you just picked. That
|
|
2922
|
+
* only works if someone owns the step back out again — so zoom keeps a
|
|
2923
|
+
* history, and the header offers the way back rather than leaving the reader
|
|
2924
|
+
* to retype the range they came from.
|
|
2925
|
+
*
|
|
2926
|
+
* @example
|
|
2927
|
+
* ```tsx
|
|
2928
|
+
* const [range, setRange] = useState<TimeRange>({ from: "now-7d", to: "now" });
|
|
2929
|
+
*
|
|
2930
|
+
* <TimeSeriesPanel
|
|
2931
|
+
* title="Sessions"
|
|
2932
|
+
* data={points}
|
|
2933
|
+
* series={[{ dataKey: "sessions", name: "Sessions" }]}
|
|
2934
|
+
* range={range}
|
|
2935
|
+
* onRangeChange={setRange}
|
|
2936
|
+
* onRefresh={() => refetch()}
|
|
2937
|
+
* toggleableLegend
|
|
2938
|
+
* />
|
|
2939
|
+
* ```
|
|
2940
|
+
*/
|
|
2941
|
+
function TimeSeriesPanel({ title, description, actions, data, series, range, onRangeChange, granularity = "auto", timeZone, locale, showRangeControl = true, presets, recentRanges, refresh, onRefreshChange, onRefresh, toggleableLegend = false, annotations, isLoading = false, height = 280, className, chartProps, labels: customLabels, "aria-label": ariaLabel }) {
|
|
2942
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$3), customLabels);
|
|
2943
|
+
const [history, setHistory] = (0, react.useState)([]);
|
|
2944
|
+
const [hidden, setHidden] = (0, react.useState)([]);
|
|
2945
|
+
const resolved = (0, react.useMemo)(() => (0, _reopt_ai_opt_charts.resolveTimeRange)(range, { timeZone }), [range, timeZone]);
|
|
2946
|
+
const visibleSeries = (0, react.useMemo)(() => series.filter((def) => !hidden.includes(def.dataKey)), [series, hidden]);
|
|
2947
|
+
const handleRangeSelect = (0, react.useCallback)((selection) => {
|
|
2948
|
+
setHistory((current) => [...current, range].slice(-10));
|
|
2949
|
+
onRangeChange((0, _reopt_ai_opt_charts.toAbsoluteRange)(selection));
|
|
2950
|
+
}, [range, onRangeChange]);
|
|
2951
|
+
const goBack = (0, react.useCallback)(() => {
|
|
2952
|
+
setHistory((current) => {
|
|
2953
|
+
const previous = current[current.length - 1];
|
|
2954
|
+
if (previous) onRangeChange(previous);
|
|
2955
|
+
return current.slice(0, -1);
|
|
2956
|
+
});
|
|
2957
|
+
}, [onRangeChange]);
|
|
2958
|
+
const setRange = (0, react.useCallback)((next) => {
|
|
2959
|
+
setHistory([]);
|
|
2960
|
+
onRangeChange(next);
|
|
2961
|
+
}, [onRangeChange]);
|
|
2962
|
+
const toggleSeries = (0, react.useCallback)((dataKey) => {
|
|
2963
|
+
setHidden((current) => current.includes(dataKey) ? current.filter((key) => key !== dataKey) : [...current, dataKey]);
|
|
2964
|
+
}, []);
|
|
2965
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2966
|
+
className: require_pagination.cn(require_pagination.OPT_SURFACE, require_pagination.OPT_BORDER, "flex flex-col", className),
|
|
2967
|
+
"aria-label": ariaLabel,
|
|
2968
|
+
"data-opt-id": "Y0XCY",
|
|
2969
|
+
"data-opt-slug": "time-series-panel.TimeSeriesPanel",
|
|
2970
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
2971
|
+
className: "gap-group flex flex-wrap items-start justify-between px-5 pt-5 pb-3",
|
|
2972
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2973
|
+
className: "gap-element flex min-w-0 flex-col",
|
|
2974
|
+
children: [title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2975
|
+
className: "text-text-primary truncate text-base font-semibold",
|
|
2976
|
+
children: title
|
|
2977
|
+
}), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2978
|
+
className: require_pagination.cn("text-text-secondary", "truncate text-sm"),
|
|
2979
|
+
children: description
|
|
2980
|
+
})]
|
|
2981
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2982
|
+
className: "gap-element flex flex-wrap items-center",
|
|
2983
|
+
children: [
|
|
2984
|
+
history.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Button, {
|
|
2985
|
+
variant: "ghost",
|
|
2986
|
+
size: "sm",
|
|
2987
|
+
onClick: goBack,
|
|
2988
|
+
leftIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Undo2, {
|
|
2989
|
+
className: "h-3.5 w-3.5",
|
|
2990
|
+
"aria-hidden": "true"
|
|
2991
|
+
}),
|
|
2992
|
+
children: labels.back
|
|
2993
|
+
}),
|
|
2994
|
+
actions,
|
|
2995
|
+
showRangeControl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeRangeControl, {
|
|
2996
|
+
value: range,
|
|
2997
|
+
onChange: setRange,
|
|
2998
|
+
presets,
|
|
2999
|
+
recentRanges,
|
|
3000
|
+
timeZone,
|
|
3001
|
+
locale,
|
|
3002
|
+
refresh,
|
|
3003
|
+
onRefreshChange,
|
|
3004
|
+
onRefresh,
|
|
3005
|
+
isLoading,
|
|
3006
|
+
labels: customLabels === null || customLabels === void 0 ? void 0 : customLabels.control
|
|
3007
|
+
})
|
|
3008
|
+
]
|
|
3009
|
+
})]
|
|
3010
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3011
|
+
className: "px-2 pb-4",
|
|
3012
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_reopt_ai_opt_charts.TimeSeriesChart, require_pagination._objectSpread2({
|
|
3013
|
+
data,
|
|
3014
|
+
series: visibleSeries,
|
|
3015
|
+
from: resolved === null || resolved === void 0 ? void 0 : resolved.from,
|
|
3016
|
+
to: resolved === null || resolved === void 0 ? void 0 : resolved.to,
|
|
3017
|
+
granularity,
|
|
3018
|
+
timeZone,
|
|
3019
|
+
locale,
|
|
3020
|
+
height,
|
|
3021
|
+
selectable: true,
|
|
3022
|
+
onRangeSelect: handleRangeSelect,
|
|
3023
|
+
annotations,
|
|
3024
|
+
showLegend: series.length > 1,
|
|
3025
|
+
legendOptions: toggleableLegend ? {
|
|
3026
|
+
onClick: toggleSeries,
|
|
3027
|
+
inactiveKeys: hidden
|
|
3028
|
+
} : void 0,
|
|
3029
|
+
"aria-label": typeof title === "string" ? `${title} time series` : ariaLabel
|
|
3030
|
+
}, chartProps))
|
|
3031
|
+
})]
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
TimeSeriesPanel.displayName = "TimeSeriesPanel";
|
|
3035
|
+
//#endregion
|
|
3036
|
+
//#region src/shells/log-table.tsx
|
|
3037
|
+
const HIDE_BELOW = {
|
|
3038
|
+
always: "",
|
|
3039
|
+
sm: "hidden @[40rem]/log:flex",
|
|
3040
|
+
md: "hidden @[46rem]/log:flex",
|
|
3041
|
+
lg: "hidden @[58rem]/log:flex",
|
|
3042
|
+
xl: "hidden @[70rem]/log:flex"
|
|
3043
|
+
};
|
|
3044
|
+
const ROW_HEIGHT = {
|
|
3045
|
+
compact: 26,
|
|
3046
|
+
normal: 32,
|
|
3047
|
+
comfortable: 40
|
|
3048
|
+
};
|
|
3049
|
+
const DENSITY_TEXT = {
|
|
3050
|
+
compact: "text-[11px]",
|
|
3051
|
+
normal: "text-xs",
|
|
3052
|
+
comfortable: "text-sm"
|
|
3053
|
+
};
|
|
3054
|
+
const defaultLabels$2 = {
|
|
3055
|
+
listLabel: "Log",
|
|
3056
|
+
loadingMore: "Loading earlier entries…",
|
|
3057
|
+
endOfResults: "That is every entry in this range.",
|
|
3058
|
+
filterFor: (value) => `Filter for ${value}`,
|
|
3059
|
+
filterOut: (value) => `Filter out ${value}`,
|
|
3060
|
+
sortAscending: "Sort ascending",
|
|
3061
|
+
sortDescending: "Sort descending"
|
|
3062
|
+
};
|
|
3063
|
+
/** Rows rendered beyond the viewport on each side, so a fast scroll stays filled. */
|
|
3064
|
+
const OVERSCAN = 10;
|
|
3065
|
+
/**
|
|
3066
|
+
* A stable hue per value. Colour does real work in a log — the eye groups by it
|
|
3067
|
+
* long before it reads the text — so the same event name has to land on the
|
|
3068
|
+
* same hue on every render and in every session, which rules out an index into
|
|
3069
|
+
* a palette.
|
|
3070
|
+
*/
|
|
3071
|
+
function hueOf(value) {
|
|
3072
|
+
let hash = 0;
|
|
3073
|
+
for (let index = 0; index < value.length; index += 1) hash = hash * 31 + value.charCodeAt(index) | 0;
|
|
3074
|
+
return Math.abs(hash) % 360;
|
|
3075
|
+
}
|
|
3076
|
+
const EM_DASH = "—";
|
|
3077
|
+
/**
|
|
3078
|
+
* A column with nothing in it is a fact about the entry, not a value. Dimming
|
|
3079
|
+
* every absence the same way lets the eye skip the gaps while scanning, instead
|
|
3080
|
+
* of reading a dash as though it were data.
|
|
3081
|
+
*/
|
|
3082
|
+
const ABSENT = "text-text-tertiary/50";
|
|
3083
|
+
function columnStyle(column) {
|
|
3084
|
+
return column.width ? {
|
|
3085
|
+
width: column.width,
|
|
3086
|
+
flex: "0 0 auto"
|
|
3087
|
+
} : {
|
|
3088
|
+
flex: "1 1 0%",
|
|
3089
|
+
minWidth: 0
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
/**
|
|
3093
|
+
* LogTable — a windowed table for entries that keep arriving.
|
|
3094
|
+
*
|
|
3095
|
+
* Written by hand rather than delegated to a data grid for the two things a log
|
|
3096
|
+
* needs that a grid does not: rows are inserted at the *top* while the reader
|
|
3097
|
+
* is partway down, which needs a scroll correction applied before paint, and
|
|
3098
|
+
* "am I at the newest row?" has to be answerable from the scroll container to
|
|
3099
|
+
* decide whether to follow the tail or hold position.
|
|
3100
|
+
*
|
|
3101
|
+
* What it takes from a data grid is the part readers actually reach for:
|
|
3102
|
+
* columns are data, so a caller can let people add, drop and reorder them; a
|
|
3103
|
+
* cell offers to filter for or against its own value; and density is a setting
|
|
3104
|
+
* rather than a constant.
|
|
3105
|
+
*
|
|
3106
|
+
* @example
|
|
3107
|
+
* ```tsx
|
|
3108
|
+
* <LogTable
|
|
3109
|
+
* rows={rows}
|
|
3110
|
+
* getRowId={(row) => row.id}
|
|
3111
|
+
* columns={[
|
|
3112
|
+
* { id: "time", header: "Time", width: "9rem", mono: true, value: (r) => r.time },
|
|
3113
|
+
* { id: "name", header: "Event", width: "14rem", field: "name", marker: true, value: (r) => r.name },
|
|
3114
|
+
* { id: "path", header: "Path", field: "path", value: (r) => r.path },
|
|
3115
|
+
* ]}
|
|
3116
|
+
* onCellAction={(action) => setQuery(applyCellAction(query, action))}
|
|
3117
|
+
* />
|
|
3118
|
+
* ```
|
|
3119
|
+
*/
|
|
3120
|
+
function LogTableInner({ rows, columns, getRowId, selectedId = null, onSelect, onLoadMore, hasMore = false, loadingMore = false, prepending = false, onFollowChange, isNew, sort = null, onSortChange, onCellAction, density = "normal", showHeader = true, emptyState, labels: customLabels, className }, ref) {
|
|
3121
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$2), customLabels);
|
|
3122
|
+
const rowHeight = ROW_HEIGHT[density];
|
|
3123
|
+
const scrollRef = (0, react.useRef)(null);
|
|
3124
|
+
const [scrollTop, setScrollTop] = (0, react.useState)(0);
|
|
3125
|
+
const [viewportHeight, setViewportHeight] = (0, react.useState)(0);
|
|
3126
|
+
const followingRef = (0, react.useRef)(true);
|
|
3127
|
+
const previousTopIdRef = (0, react.useRef)(null);
|
|
3128
|
+
const previousLengthRef = (0, react.useRef)(0);
|
|
3129
|
+
(0, react.useImperativeHandle)(ref, () => ({
|
|
3130
|
+
scrollToTop: () => {
|
|
3131
|
+
const container = scrollRef.current;
|
|
3132
|
+
if (!container) return;
|
|
3133
|
+
container.scrollTop = 0;
|
|
3134
|
+
setScrollTop(0);
|
|
3135
|
+
if (!followingRef.current) {
|
|
3136
|
+
followingRef.current = true;
|
|
3137
|
+
onFollowChange === null || onFollowChange === void 0 || onFollowChange(true);
|
|
3138
|
+
}
|
|
3139
|
+
},
|
|
3140
|
+
revealRow: (id) => {
|
|
3141
|
+
const index = rows.findIndex((row) => getRowId(row) === id);
|
|
3142
|
+
const container = scrollRef.current;
|
|
3143
|
+
if (index === -1 || !container) return;
|
|
3144
|
+
const top = index * rowHeight;
|
|
3145
|
+
const bottom = top + rowHeight;
|
|
3146
|
+
if (top < container.scrollTop) container.scrollTop = top;
|
|
3147
|
+
else if (bottom > container.scrollTop + container.clientHeight) container.scrollTop = bottom - container.clientHeight;
|
|
3148
|
+
}
|
|
3149
|
+
}), [
|
|
3150
|
+
onFollowChange,
|
|
3151
|
+
rows,
|
|
3152
|
+
getRowId,
|
|
3153
|
+
rowHeight
|
|
3154
|
+
]);
|
|
3155
|
+
(0, react.useEffect)(() => {
|
|
3156
|
+
const container = scrollRef.current;
|
|
3157
|
+
if (!container) return;
|
|
3158
|
+
const observer = new ResizeObserver(() => setViewportHeight(container.clientHeight));
|
|
3159
|
+
observer.observe(container);
|
|
3160
|
+
setViewportHeight(container.clientHeight);
|
|
3161
|
+
return () => observer.disconnect();
|
|
3162
|
+
}, []);
|
|
3163
|
+
/**
|
|
3164
|
+
* Report whether the list is pinned to the newest row.
|
|
3165
|
+
*
|
|
3166
|
+
* Called from more than the scroll handler on purpose: the list can arrive at
|
|
3167
|
+
* the top without a scroll event — a reset, a jump to newest, a shorter
|
|
3168
|
+
* result set — and a stale "not following" would silently divert every live
|
|
3169
|
+
* row into a held-back pile.
|
|
3170
|
+
*/
|
|
3171
|
+
const syncFollowing = (0, react.useCallback)((container) => {
|
|
3172
|
+
const following = container.scrollTop <= rowHeight;
|
|
3173
|
+
if (following === followingRef.current) return;
|
|
3174
|
+
followingRef.current = following;
|
|
3175
|
+
onFollowChange === null || onFollowChange === void 0 || onFollowChange(following);
|
|
3176
|
+
}, [onFollowChange, rowHeight]);
|
|
3177
|
+
/**
|
|
3178
|
+
* Hold the reader's place when rows arrive above them.
|
|
3179
|
+
*
|
|
3180
|
+
* Runs before paint: by the time the browser draws, the scroll offset has
|
|
3181
|
+
* already absorbed the inserted height, so the content under the cursor never
|
|
3182
|
+
* moves. Skipped while following, where the whole point is to stay at the top.
|
|
3183
|
+
*/
|
|
3184
|
+
(0, react.useLayoutEffect)(() => {
|
|
3185
|
+
const container = scrollRef.current;
|
|
3186
|
+
const previousTopId = previousTopIdRef.current;
|
|
3187
|
+
previousTopIdRef.current = rows.length ? getRowId(rows[0]) : null;
|
|
3188
|
+
const previousLength = previousLengthRef.current;
|
|
3189
|
+
previousLengthRef.current = rows.length;
|
|
3190
|
+
if (!container) return;
|
|
3191
|
+
syncFollowing(container);
|
|
3192
|
+
if (!prepending || followingRef.current || !previousTopId) return;
|
|
3193
|
+
if (rows.length <= previousLength) return;
|
|
3194
|
+
const prepended = rows.findIndex((row) => getRowId(row) === previousTopId);
|
|
3195
|
+
if (prepended <= 0) return;
|
|
3196
|
+
container.scrollTop += prepended * rowHeight;
|
|
3197
|
+
setScrollTop(container.scrollTop);
|
|
3198
|
+
}, [
|
|
3199
|
+
rows,
|
|
3200
|
+
getRowId,
|
|
3201
|
+
prepending,
|
|
3202
|
+
rowHeight,
|
|
3203
|
+
syncFollowing
|
|
3204
|
+
]);
|
|
3205
|
+
/**
|
|
3206
|
+
* Page forward when the rows do not reach the bottom of the viewport.
|
|
3207
|
+
*
|
|
3208
|
+
* Without this the list only advances on a scroll event, so a tall screen —
|
|
3209
|
+
* or a filter matching few rows per page — stops at the first page with no
|
|
3210
|
+
* way to ask for more.
|
|
3211
|
+
*/
|
|
3212
|
+
(0, react.useEffect)(() => {
|
|
3213
|
+
const container = scrollRef.current;
|
|
3214
|
+
if (!container || !hasMore || loadingMore || !onLoadMore) return;
|
|
3215
|
+
if (container.scrollHeight <= container.clientHeight) onLoadMore();
|
|
3216
|
+
}, [
|
|
3217
|
+
hasMore,
|
|
3218
|
+
loadingMore,
|
|
3219
|
+
onLoadMore,
|
|
3220
|
+
rows.length,
|
|
3221
|
+
viewportHeight
|
|
3222
|
+
]);
|
|
3223
|
+
const handleScroll = (0, react.useCallback)((event) => {
|
|
3224
|
+
const container = event.currentTarget;
|
|
3225
|
+
setScrollTop(container.scrollTop);
|
|
3226
|
+
syncFollowing(container);
|
|
3227
|
+
const remaining = container.scrollHeight - container.scrollTop - container.clientHeight;
|
|
3228
|
+
if (hasMore && !loadingMore && remaining < container.clientHeight) onLoadMore === null || onLoadMore === void 0 || onLoadMore();
|
|
3229
|
+
}, [
|
|
3230
|
+
hasMore,
|
|
3231
|
+
loadingMore,
|
|
3232
|
+
onLoadMore,
|
|
3233
|
+
syncFollowing
|
|
3234
|
+
]);
|
|
3235
|
+
const totalHeight = rows.length * rowHeight;
|
|
3236
|
+
const firstVisible = Math.max(0, Math.floor(scrollTop / rowHeight) - OVERSCAN);
|
|
3237
|
+
const visibleCount = Math.ceil((viewportHeight || 480) / rowHeight) + 20;
|
|
3238
|
+
const lastVisible = Math.min(rows.length, firstVisible + visibleCount);
|
|
3239
|
+
const visibleRows = (0, react.useMemo)(() => rows.slice(firstVisible, lastVisible), [
|
|
3240
|
+
rows,
|
|
3241
|
+
firstVisible,
|
|
3242
|
+
lastVisible
|
|
3243
|
+
]);
|
|
3244
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3245
|
+
className: require_pagination.cn("@container/log flex min-h-0 flex-col", className),
|
|
3246
|
+
"data-opt-id": "NJ4N9",
|
|
3247
|
+
"data-opt-slug": "log-table.LogTable",
|
|
3248
|
+
children: [showHeader && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LogTableHeader, {
|
|
3249
|
+
columns,
|
|
3250
|
+
sort,
|
|
3251
|
+
onSortChange,
|
|
3252
|
+
density,
|
|
3253
|
+
labels
|
|
3254
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3255
|
+
ref: scrollRef,
|
|
3256
|
+
onScroll: handleScroll,
|
|
3257
|
+
className: "relative min-h-0 flex-1 overflow-x-hidden overflow-y-auto",
|
|
3258
|
+
role: "listbox",
|
|
3259
|
+
"aria-label": labels.listLabel,
|
|
3260
|
+
"aria-activedescendant": selectedId ? `log-row-${selectedId}` : void 0,
|
|
3261
|
+
tabIndex: 0,
|
|
3262
|
+
children: [
|
|
3263
|
+
rows.length === 0 ? emptyState : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3264
|
+
style: { height: totalHeight },
|
|
3265
|
+
className: "relative",
|
|
3266
|
+
children: visibleRows.map((row, index) => {
|
|
3267
|
+
var _isNew;
|
|
3268
|
+
const id = getRowId(row);
|
|
3269
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LogTableRow, {
|
|
3270
|
+
id,
|
|
3271
|
+
row,
|
|
3272
|
+
columns,
|
|
3273
|
+
top: (firstVisible + index) * rowHeight,
|
|
3274
|
+
height: rowHeight,
|
|
3275
|
+
density,
|
|
3276
|
+
selected: id === selectedId,
|
|
3277
|
+
isNew: (_isNew = isNew === null || isNew === void 0 ? void 0 : isNew(row)) !== null && _isNew !== void 0 ? _isNew : false,
|
|
3278
|
+
onSelect,
|
|
3279
|
+
onCellAction,
|
|
3280
|
+
labels
|
|
3281
|
+
}, id);
|
|
3282
|
+
})
|
|
3283
|
+
}),
|
|
3284
|
+
loadingMore && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3285
|
+
className: "gap-element text-text-tertiary flex items-center justify-center py-3 text-xs",
|
|
3286
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Spinner, {
|
|
3287
|
+
size: "sm",
|
|
3288
|
+
"aria-hidden": true
|
|
3289
|
+
}), labels.loadingMore]
|
|
3290
|
+
}),
|
|
3291
|
+
!hasMore && rows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3292
|
+
className: require_pagination.cn("text-text-tertiary", "py-3 text-center text-xs"),
|
|
3293
|
+
children: labels.endOfResults
|
|
3294
|
+
})
|
|
3295
|
+
]
|
|
3296
|
+
})]
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
/**
|
|
3300
|
+
* LogTable — see {@link LogTableInner}.
|
|
3301
|
+
*
|
|
3302
|
+
* The cast preserves the row generic through `forwardRef`, which otherwise
|
|
3303
|
+
* widens it to its constraint and takes the column callbacks' types with it.
|
|
3304
|
+
*/
|
|
3305
|
+
const LogTable = (0, react.forwardRef)(LogTableInner);
|
|
3306
|
+
function LogTableHeader({ columns, sort, onSortChange, density, labels }) {
|
|
3307
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3308
|
+
role: "row",
|
|
3309
|
+
className: require_pagination.cn("border-border-subtle text-text-tertiary flex shrink-0 items-center gap-3 border-b px-3", DENSITY_TEXT[density]),
|
|
3310
|
+
style: { height: ROW_HEIGHT[density] },
|
|
3311
|
+
children: columns.map((column) => {
|
|
3312
|
+
var _column$priority;
|
|
3313
|
+
const active = (sort === null || sort === void 0 ? void 0 : sort.columnId) === column.id;
|
|
3314
|
+
const sortable = column.sortable && onSortChange;
|
|
3315
|
+
const content = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3316
|
+
column.marker && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3317
|
+
"aria-hidden": true,
|
|
3318
|
+
className: "size-1.5 shrink-0"
|
|
3319
|
+
}),
|
|
3320
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3321
|
+
className: "truncate",
|
|
3322
|
+
children: column.header
|
|
3323
|
+
}),
|
|
3324
|
+
active && (sort.direction === "asc" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronUp, {
|
|
3325
|
+
className: "size-3 shrink-0",
|
|
3326
|
+
"aria-hidden": true
|
|
3327
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {
|
|
3328
|
+
className: "size-3 shrink-0",
|
|
3329
|
+
"aria-hidden": true
|
|
3330
|
+
}))
|
|
3331
|
+
] });
|
|
3332
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3333
|
+
role: "columnheader",
|
|
3334
|
+
"aria-sort": active ? sort.direction === "asc" ? "ascending" : "descending" : void 0,
|
|
3335
|
+
className: require_pagination.cn("flex items-center gap-1.5", column.align === "right" && "justify-end", HIDE_BELOW[(_column$priority = column.priority) !== null && _column$priority !== void 0 ? _column$priority : "always"]),
|
|
3336
|
+
style: columnStyle(column),
|
|
3337
|
+
children: sortable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3338
|
+
type: "button",
|
|
3339
|
+
className: "hover:text-text-primary flex cursor-pointer items-center gap-1.5 truncate",
|
|
3340
|
+
title: active && sort.direction === "desc" ? labels.sortAscending : labels.sortDescending,
|
|
3341
|
+
onClick: () => onSortChange({
|
|
3342
|
+
columnId: column.id,
|
|
3343
|
+
direction: active && sort.direction === "desc" ? "asc" : "desc"
|
|
3344
|
+
}),
|
|
3345
|
+
children: content
|
|
3346
|
+
}) : content
|
|
3347
|
+
}, column.id);
|
|
3348
|
+
})
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
function LogTableRowImpl({ id, row, columns, top, height, density, selected, isNew, onSelect, onCellAction, labels }) {
|
|
3352
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3353
|
+
role: "option",
|
|
3354
|
+
"aria-selected": selected,
|
|
3355
|
+
tabIndex: -1,
|
|
3356
|
+
id: `log-row-${id}`,
|
|
3357
|
+
"data-log-row-id": id,
|
|
3358
|
+
onClick: () => onSelect === null || onSelect === void 0 ? void 0 : onSelect(id),
|
|
3359
|
+
onKeyDown: (event) => {
|
|
3360
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
3361
|
+
event.preventDefault();
|
|
3362
|
+
onSelect === null || onSelect === void 0 || onSelect(id);
|
|
3363
|
+
},
|
|
3364
|
+
style: {
|
|
3365
|
+
top,
|
|
3366
|
+
height
|
|
3367
|
+
},
|
|
3368
|
+
className: require_pagination.cn("border-border-subtle/60 absolute inset-x-0 flex items-center gap-3 border-b px-3", DENSITY_TEXT[density], onSelect && "cursor-pointer", "transition-colors duration-100", "hover:bg-surface-hover", selected && "bg-accent-subtle hover:bg-accent-subtle", isNew && !selected && "opt-log-row-enter"),
|
|
3369
|
+
children: columns.map((column) => {
|
|
3370
|
+
var _column$priority2;
|
|
3371
|
+
const raw = column.value(row);
|
|
3372
|
+
const text = raw === null || raw === void 0 || raw === "" ? "" : String(raw);
|
|
3373
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3374
|
+
className: require_pagination.cn("group/cell relative flex min-w-0 items-center gap-1.5", column.align === "right" && "justify-end", HIDE_BELOW[(_column$priority2 = column.priority) !== null && _column$priority2 !== void 0 ? _column$priority2 : "always"]),
|
|
3375
|
+
style: columnStyle(column),
|
|
3376
|
+
children: [
|
|
3377
|
+
column.marker && text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3378
|
+
"aria-hidden": true,
|
|
3379
|
+
className: "size-1.5 shrink-0 rounded-full",
|
|
3380
|
+
style: { backgroundColor: `hsl(${hueOf(text)} 65% 55%)` }
|
|
3381
|
+
}),
|
|
3382
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3383
|
+
className: require_pagination.cn("truncate", column.mono && "font-mono", text ? "text-text-secondary" : ABSENT),
|
|
3384
|
+
title: text || void 0,
|
|
3385
|
+
children: column.render ? column.render(row) : text || EM_DASH
|
|
3386
|
+
}),
|
|
3387
|
+
onCellAction && column.field && text && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CellActions, {
|
|
3388
|
+
field: column.field,
|
|
3389
|
+
value: text,
|
|
3390
|
+
onAction: onCellAction,
|
|
3391
|
+
labels
|
|
3392
|
+
})
|
|
3393
|
+
]
|
|
3394
|
+
}, column.id);
|
|
3395
|
+
})
|
|
3396
|
+
});
|
|
3397
|
+
}
|
|
3398
|
+
/**
|
|
3399
|
+
* Memoized because a live log re-renders several times a second while almost
|
|
3400
|
+
* every visible row is unchanged.
|
|
3401
|
+
*/
|
|
3402
|
+
const LogTableRow = (0, react.memo)(LogTableRowImpl);
|
|
3403
|
+
/**
|
|
3404
|
+
* The two questions a reader has about a value they just spotted: show me only
|
|
3405
|
+
* these, and get these out of my way.
|
|
3406
|
+
*
|
|
3407
|
+
* Revealed on hover rather than always drawn — a log is scanned, and a pair of
|
|
3408
|
+
* buttons on every cell of every row is a wall of chrome. They stay in the tab
|
|
3409
|
+
* order regardless, since hover is not an interaction everyone has.
|
|
3410
|
+
*/
|
|
3411
|
+
function CellActions({ field, value, onAction, labels }) {
|
|
3412
|
+
const button = "cursor-pointer rounded-sm p-0.5 text-text-tertiary hover:bg-bg-muted hover:text-text-primary";
|
|
3413
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3414
|
+
className: require_pagination.cn("bg-surface/90 ml-auto hidden shrink-0 items-center gap-0.5 rounded pl-1", "group-hover/cell:flex focus-within:flex"),
|
|
3415
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3416
|
+
type: "button",
|
|
3417
|
+
className: button,
|
|
3418
|
+
title: labels.filterFor(value),
|
|
3419
|
+
"aria-label": labels.filterFor(value),
|
|
3420
|
+
onClick: (event) => {
|
|
3421
|
+
event.stopPropagation();
|
|
3422
|
+
onAction({
|
|
3423
|
+
field,
|
|
3424
|
+
value,
|
|
3425
|
+
intent: "include"
|
|
3426
|
+
});
|
|
3427
|
+
},
|
|
3428
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, {
|
|
3429
|
+
className: "size-3",
|
|
3430
|
+
"aria-hidden": true
|
|
3431
|
+
})
|
|
3432
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3433
|
+
type: "button",
|
|
3434
|
+
className: button,
|
|
3435
|
+
title: labels.filterOut(value),
|
|
3436
|
+
"aria-label": labels.filterOut(value),
|
|
3437
|
+
onClick: (event) => {
|
|
3438
|
+
event.stopPropagation();
|
|
3439
|
+
onAction({
|
|
3440
|
+
field,
|
|
3441
|
+
value,
|
|
3442
|
+
intent: "exclude"
|
|
3443
|
+
});
|
|
3444
|
+
},
|
|
3445
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Minus, {
|
|
3446
|
+
className: "size-3",
|
|
3447
|
+
"aria-hidden": true
|
|
3448
|
+
})
|
|
3449
|
+
})]
|
|
3450
|
+
});
|
|
3451
|
+
}
|
|
3452
|
+
//#endregion
|
|
3453
|
+
//#region src/lib/query/query-ast.ts
|
|
3454
|
+
const EMPTY_QUERY = { clauses: [] };
|
|
3455
|
+
/** Whether a query constrains anything at all. */
|
|
3456
|
+
function isEmptyQuery(query) {
|
|
3457
|
+
return !query || query.clauses.length === 0;
|
|
3458
|
+
}
|
|
3459
|
+
function sameClause(a, b) {
|
|
3460
|
+
if (a.type !== b.type) return false;
|
|
3461
|
+
if (a.type === "term" && b.type === "term") return a.value === b.value && a.negated === b.negated;
|
|
3462
|
+
const fieldA = a;
|
|
3463
|
+
const fieldB = b;
|
|
3464
|
+
return fieldA.field === fieldB.field && fieldA.operator === fieldB.operator && fieldA.negated === fieldB.negated && fieldA.values.length === fieldB.values.length && fieldA.values.every((value, index) => value === fieldB.values[index]);
|
|
3465
|
+
}
|
|
3466
|
+
/** Add a clause unless an identical one is already there. */
|
|
3467
|
+
function addClause(query, clause) {
|
|
3468
|
+
if (query.clauses.some((existing) => sameClause(existing, clause))) return query;
|
|
3469
|
+
return { clauses: [...query.clauses, clause] };
|
|
3470
|
+
}
|
|
3471
|
+
/**
|
|
3472
|
+
* Constrain a field to a value — what a cell's "filter for this" action does.
|
|
3473
|
+
*
|
|
3474
|
+
* Applying the opposite of a constraint already present replaces it rather than
|
|
3475
|
+
* adding it: `status:200` then "filter out 200" should read as one changed
|
|
3476
|
+
* mind, not as a query that can never match.
|
|
3477
|
+
*/
|
|
3478
|
+
function withFieldValue(query, field, value, options = {}) {
|
|
3479
|
+
var _options$negated, _options$operator;
|
|
3480
|
+
const negated = (_options$negated = options.negated) !== null && _options$negated !== void 0 ? _options$negated : false;
|
|
3481
|
+
const operator = (_options$operator = options.operator) !== null && _options$operator !== void 0 ? _options$operator : "eq";
|
|
3482
|
+
return { clauses: [...query.clauses.filter((clause) => !(clause.type === "field" && clause.field === field && clause.operator === operator && clause.values.length === 1 && clause.values[0] === value)), {
|
|
3483
|
+
type: "field",
|
|
3484
|
+
field,
|
|
3485
|
+
operator,
|
|
3486
|
+
values: [value],
|
|
3487
|
+
negated
|
|
3488
|
+
}] };
|
|
3489
|
+
}
|
|
3490
|
+
/** Drop every clause naming `field`. */
|
|
3491
|
+
function withoutField(query, field) {
|
|
3492
|
+
return { clauses: query.clauses.filter((clause) => clause.type !== "field" || clause.field !== field) };
|
|
3493
|
+
}
|
|
3494
|
+
/** Drop the clause at `index`, for a removable chip. */
|
|
3495
|
+
function removeClause(query, index) {
|
|
3496
|
+
return { clauses: query.clauses.filter((_, at) => at !== index) };
|
|
3497
|
+
}
|
|
3498
|
+
/** Every field the query names, in the order they first appear. */
|
|
3499
|
+
function queryFields(query) {
|
|
3500
|
+
const seen = [];
|
|
3501
|
+
for (const clause of query.clauses) if (clause.type === "field" && !seen.includes(clause.field)) seen.push(clause.field);
|
|
3502
|
+
return seen;
|
|
3503
|
+
}
|
|
3504
|
+
/** The values a field is constrained to, if any — for showing a filter as active. */
|
|
3505
|
+
function fieldValues(query, field) {
|
|
3506
|
+
return query.clauses.flatMap((clause) => clause.type === "field" && clause.field === field && !clause.negated ? clause.values : []);
|
|
3507
|
+
}
|
|
3508
|
+
/**
|
|
3509
|
+
* Fold a cell action into the query.
|
|
3510
|
+
*
|
|
3511
|
+
* The pair of buttons on a log cell is the fastest path from "I noticed this
|
|
3512
|
+
* value" to a filtered view, and the reason it works is that the result is a
|
|
3513
|
+
* query the reader can then see, edit and share — not a hidden filter stack
|
|
3514
|
+
* with its own rules.
|
|
3515
|
+
*/
|
|
3516
|
+
function applyCellAction(query, action) {
|
|
3517
|
+
return withFieldValue(query, action.field, action.value, { negated: action.intent === "exclude" });
|
|
3518
|
+
}
|
|
3519
|
+
//#endregion
|
|
3520
|
+
//#region src/lib/query/query-parse.ts
|
|
3521
|
+
const FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
|
|
3522
|
+
/**
|
|
3523
|
+
* Split on whitespace, but keep quoted runs and parenthesised groups whole.
|
|
3524
|
+
*
|
|
3525
|
+
* An unterminated quote or group swallows the rest of the line rather than
|
|
3526
|
+
* erroring: the reader is mid-word, and the alternative is the query flickering
|
|
3527
|
+
* between two different meanings as they type the closing character.
|
|
3528
|
+
*/
|
|
3529
|
+
function tokenize(input) {
|
|
3530
|
+
const tokens = [];
|
|
3531
|
+
let index = 0;
|
|
3532
|
+
while (index < input.length) {
|
|
3533
|
+
while (index < input.length && /\s/.test(input[index])) index += 1;
|
|
3534
|
+
if (index >= input.length) break;
|
|
3535
|
+
const start = index;
|
|
3536
|
+
let text = "";
|
|
3537
|
+
let quoted = false;
|
|
3538
|
+
let atTokenStart = true;
|
|
3539
|
+
let depth = 0;
|
|
3540
|
+
while (index < input.length) {
|
|
3541
|
+
const char = input[index];
|
|
3542
|
+
if (char === "\"") {
|
|
3543
|
+
if (atTokenStart) quoted = true;
|
|
3544
|
+
atTokenStart = false;
|
|
3545
|
+
index += 1;
|
|
3546
|
+
while (index < input.length && input[index] !== "\"") {
|
|
3547
|
+
text += input[index];
|
|
3548
|
+
index += 1;
|
|
3549
|
+
}
|
|
3550
|
+
index += 1;
|
|
3551
|
+
continue;
|
|
3552
|
+
}
|
|
3553
|
+
if (char === "(") depth += 1;
|
|
3554
|
+
if (char === ")") depth = Math.max(0, depth - 1);
|
|
3555
|
+
if (/\s/.test(char) && depth === 0) break;
|
|
3556
|
+
atTokenStart = false;
|
|
3557
|
+
text += char;
|
|
3558
|
+
index += 1;
|
|
3559
|
+
}
|
|
3560
|
+
if (text.length > 0 || quoted) tokens.push({
|
|
3561
|
+
text,
|
|
3562
|
+
at: start,
|
|
3563
|
+
quoted
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
return tokens;
|
|
3567
|
+
}
|
|
3568
|
+
/** `(a or b)` → `["a", "b"]`; anything else is a single value. */
|
|
3569
|
+
function readValues(raw) {
|
|
3570
|
+
const trimmed = raw.trim();
|
|
3571
|
+
if (!trimmed.startsWith("(")) return [stripQuotes(trimmed)];
|
|
3572
|
+
return trimmed.replace(/^\(/, "").replace(/\)$/, "").split(/\s+or\s+/i).map((part) => stripQuotes(part.trim())).filter((part) => part.length > 0);
|
|
3573
|
+
}
|
|
3574
|
+
function stripQuotes(value) {
|
|
3575
|
+
if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1);
|
|
3576
|
+
return value.replace(/^"/, "");
|
|
3577
|
+
}
|
|
3578
|
+
const OPERATORS = [
|
|
3579
|
+
{
|
|
3580
|
+
symbol: ">=",
|
|
3581
|
+
operator: "gte"
|
|
3582
|
+
},
|
|
3583
|
+
{
|
|
3584
|
+
symbol: "<=",
|
|
3585
|
+
operator: "lte"
|
|
3586
|
+
},
|
|
3587
|
+
{
|
|
3588
|
+
symbol: ":",
|
|
3589
|
+
operator: "eq"
|
|
3590
|
+
},
|
|
3591
|
+
{
|
|
3592
|
+
symbol: ">",
|
|
3593
|
+
operator: "gt"
|
|
3594
|
+
},
|
|
3595
|
+
{
|
|
3596
|
+
symbol: "<",
|
|
3597
|
+
operator: "lt"
|
|
3598
|
+
}
|
|
3599
|
+
];
|
|
3600
|
+
/** Where a field operator starts in the token, or `-1`. */
|
|
3601
|
+
function findOperator(text) {
|
|
3602
|
+
let best = null;
|
|
3603
|
+
for (const { symbol, operator } of OPERATORS) {
|
|
3604
|
+
const index = text.indexOf(symbol);
|
|
3605
|
+
if (index <= 0) continue;
|
|
3606
|
+
if (!best || index < best.index || index === best.index && symbol.length > best.symbol.length) best = {
|
|
3607
|
+
index,
|
|
3608
|
+
symbol,
|
|
3609
|
+
operator
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
return best;
|
|
3613
|
+
}
|
|
3614
|
+
/** Parse, keeping whatever could not be understood as a plain term. */
|
|
3615
|
+
function parseQueryWithDiagnostics(input) {
|
|
3616
|
+
const diagnostics = [];
|
|
3617
|
+
const clauses = [];
|
|
3618
|
+
let trailing = null;
|
|
3619
|
+
for (const token of tokenize(input)) {
|
|
3620
|
+
let text = token.text;
|
|
3621
|
+
let negated = false;
|
|
3622
|
+
if (!token.quoted && text.startsWith("-") && text.length > 1) {
|
|
3623
|
+
negated = true;
|
|
3624
|
+
text = text.slice(1);
|
|
3625
|
+
}
|
|
3626
|
+
const operator = token.quoted ? null : findOperator(text);
|
|
3627
|
+
if (!operator) {
|
|
3628
|
+
if (text.length > 0) clauses.push({
|
|
3629
|
+
type: "term",
|
|
3630
|
+
value: text,
|
|
3631
|
+
negated
|
|
3632
|
+
});
|
|
3633
|
+
continue;
|
|
3634
|
+
}
|
|
3635
|
+
const field = text.slice(0, operator.index);
|
|
3636
|
+
const rawValue = text.slice(operator.index + operator.symbol.length);
|
|
3637
|
+
if (!FIELD_NAME.test(field)) {
|
|
3638
|
+
clauses.push({
|
|
3639
|
+
type: "term",
|
|
3640
|
+
value: text,
|
|
3641
|
+
negated
|
|
3642
|
+
});
|
|
3643
|
+
continue;
|
|
3644
|
+
}
|
|
3645
|
+
if (rawValue === "") {
|
|
3646
|
+
trailing = {
|
|
3647
|
+
field,
|
|
3648
|
+
operator: operator.operator,
|
|
3649
|
+
value: ""
|
|
3650
|
+
};
|
|
3651
|
+
continue;
|
|
3652
|
+
}
|
|
3653
|
+
if (rawValue === "*" && operator.operator === "eq") {
|
|
3654
|
+
clauses.push({
|
|
3655
|
+
type: "field",
|
|
3656
|
+
field,
|
|
3657
|
+
operator: "exists",
|
|
3658
|
+
values: [],
|
|
3659
|
+
negated
|
|
3660
|
+
});
|
|
3661
|
+
continue;
|
|
3662
|
+
}
|
|
3663
|
+
if (operator.operator === "eq" && rawValue.length > 2 && rawValue.startsWith("*") && rawValue.endsWith("*")) {
|
|
3664
|
+
clauses.push({
|
|
3665
|
+
type: "field",
|
|
3666
|
+
field,
|
|
3667
|
+
operator: "contains",
|
|
3668
|
+
values: [stripQuotes(rawValue.slice(1, -1))],
|
|
3669
|
+
negated
|
|
3670
|
+
});
|
|
3671
|
+
continue;
|
|
3672
|
+
}
|
|
3673
|
+
const values = readValues(rawValue);
|
|
3674
|
+
if (values.length === 0) {
|
|
3675
|
+
diagnostics.push({
|
|
3676
|
+
at: token.at,
|
|
3677
|
+
text: token.text,
|
|
3678
|
+
message: "Empty value group"
|
|
3679
|
+
});
|
|
3680
|
+
continue;
|
|
3681
|
+
}
|
|
3682
|
+
if (operator.operator !== "eq" && values.length > 1) diagnostics.push({
|
|
3683
|
+
at: token.at,
|
|
3684
|
+
text: token.text,
|
|
3685
|
+
message: "A comparison takes a single value"
|
|
3686
|
+
});
|
|
3687
|
+
const clause = {
|
|
3688
|
+
type: "field",
|
|
3689
|
+
field,
|
|
3690
|
+
operator: operator.operator,
|
|
3691
|
+
values: operator.operator === "eq" ? values : values.slice(0, 1),
|
|
3692
|
+
negated
|
|
3693
|
+
};
|
|
3694
|
+
clauses.push(clause);
|
|
3695
|
+
if (!rawValue.startsWith("(")) {
|
|
3696
|
+
var _values$;
|
|
3697
|
+
trailing = {
|
|
3698
|
+
field,
|
|
3699
|
+
operator: operator.operator,
|
|
3700
|
+
value: (_values$ = values[0]) !== null && _values$ !== void 0 ? _values$ : ""
|
|
3701
|
+
};
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
return {
|
|
3705
|
+
query: { clauses },
|
|
3706
|
+
diagnostics,
|
|
3707
|
+
trailing
|
|
3708
|
+
};
|
|
3709
|
+
}
|
|
3710
|
+
/** Parse, discarding diagnostics. */
|
|
3711
|
+
function parseQuery(input) {
|
|
3712
|
+
return parseQueryWithDiagnostics(input).query;
|
|
3713
|
+
}
|
|
3714
|
+
//#endregion
|
|
3715
|
+
//#region src/lib/query/query-format.ts
|
|
3716
|
+
const OPERATOR_SYMBOL = {
|
|
3717
|
+
eq: ":",
|
|
3718
|
+
contains: ":",
|
|
3719
|
+
gt: ">",
|
|
3720
|
+
gte: ">=",
|
|
3721
|
+
lt: "<",
|
|
3722
|
+
lte: "<=",
|
|
3723
|
+
exists: ":"
|
|
3724
|
+
};
|
|
3725
|
+
/** Quote a value that would otherwise re-parse as something else. */
|
|
3726
|
+
function quoteValue(value) {
|
|
3727
|
+
return /[\s"()]/.test(value) || value === "" ? `"${value.replace(/"/g, "")}"` : value;
|
|
3728
|
+
}
|
|
3729
|
+
/** One clause as text. */
|
|
3730
|
+
function formatClause(clause) {
|
|
3731
|
+
var _clause$values$2;
|
|
3732
|
+
const prefix = clause.negated ? "-" : "";
|
|
3733
|
+
if (clause.type === "term") return `${prefix}${quoteValue(clause.value)}`;
|
|
3734
|
+
if (clause.operator === "exists") return `${prefix}${clause.field}:*`;
|
|
3735
|
+
if (clause.operator === "contains") {
|
|
3736
|
+
var _clause$values$;
|
|
3737
|
+
return `${prefix}${clause.field}:*${(_clause$values$ = clause.values[0]) !== null && _clause$values$ !== void 0 ? _clause$values$ : ""}*`;
|
|
3738
|
+
}
|
|
3739
|
+
const symbol = OPERATOR_SYMBOL[clause.operator];
|
|
3740
|
+
if (clause.values.length > 1) return `${prefix}${clause.field}${symbol}(${clause.values.map(quoteValue).join(" or ")})`;
|
|
3741
|
+
return `${prefix}${clause.field}${symbol}${quoteValue((_clause$values$2 = clause.values[0]) !== null && _clause$values$2 !== void 0 ? _clause$values$2 : "")}`;
|
|
3742
|
+
}
|
|
3743
|
+
/** The whole query as text. */
|
|
3744
|
+
function formatQuery(query) {
|
|
3745
|
+
return query.clauses.map(formatClause).join(" ");
|
|
3746
|
+
}
|
|
3747
|
+
/** A short human label for a clause chip. */
|
|
3748
|
+
function describeClause(clause) {
|
|
3749
|
+
if (clause.type === "term") return clause.negated ? `not "${clause.value}"` : `"${clause.value}"`;
|
|
3750
|
+
if (clause.operator === "exists") return clause.negated ? `${clause.field} missing` : `${clause.field} present`;
|
|
3751
|
+
if (clause.operator === "contains") {
|
|
3752
|
+
var _clause$values$3;
|
|
3753
|
+
return `${clause.negated ? "not " : ""}${clause.field} ⊃ ${(_clause$values$3 = clause.values[0]) !== null && _clause$values$3 !== void 0 ? _clause$values$3 : ""}`;
|
|
3754
|
+
}
|
|
3755
|
+
const values = clause.values.join(", ");
|
|
3756
|
+
const symbol = OPERATOR_SYMBOL[clause.operator];
|
|
3757
|
+
return `${clause.negated ? "not " : ""}${clause.field} ${symbol === ":" ? "=" : symbol} ${values}`;
|
|
3758
|
+
}
|
|
3759
|
+
//#endregion
|
|
3760
|
+
//#region src/shells/query-bar.tsx
|
|
3761
|
+
const defaultLabels$1 = {
|
|
3762
|
+
placeholder: "Search, or filter with field:value",
|
|
3763
|
+
ariaLabel: "Search query",
|
|
3764
|
+
clear: "Clear query",
|
|
3765
|
+
removeClause: (clause) => `Remove ${clause}`,
|
|
3766
|
+
fieldsHeading: "Fields",
|
|
3767
|
+
valuesHeading: "Values",
|
|
3768
|
+
syntaxHint: null
|
|
3769
|
+
};
|
|
3770
|
+
const MAX_SUGGESTIONS = 8;
|
|
3771
|
+
/**
|
|
3772
|
+
* QueryBar — one control for "find this" and "only these".
|
|
3773
|
+
*
|
|
3774
|
+
* A search box and a row of filter pickers answer halves of the same question
|
|
3775
|
+
* and neither can express the other, so people end up using both and holding
|
|
3776
|
+
* the combination in their head. Typing the filter is what closes that: the
|
|
3777
|
+
* whole question is one string, which means it can be read back, corrected in
|
|
3778
|
+
* place, pasted to a colleague, and stored in a URL.
|
|
3779
|
+
*
|
|
3780
|
+
* The text is the source of truth while the reader is typing; the parsed query
|
|
3781
|
+
* is committed on Enter or blur. Anything half-written parses to a plain term
|
|
3782
|
+
* rather than an error, so the bar never argues with someone mid-word.
|
|
3783
|
+
*
|
|
3784
|
+
* @example
|
|
3785
|
+
* ```tsx
|
|
3786
|
+
* <QueryBar
|
|
3787
|
+
* value={query}
|
|
3788
|
+
* onChange={setQuery}
|
|
3789
|
+
* fields={[{ name: "path", values: ["/checkout", "/cart"] }]}
|
|
3790
|
+
* showClauses
|
|
3791
|
+
* />
|
|
3792
|
+
* ```
|
|
3793
|
+
*/
|
|
3794
|
+
function QueryBar({ value, onChange, fields = [], showClauses = false, live = false, disabled = false, className, labels: customLabels }) {
|
|
3795
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels$1), customLabels);
|
|
3796
|
+
const inputRef = (0, react.useRef)(null);
|
|
3797
|
+
const [text, setText] = (0, react.useState)(() => formatQuery(value));
|
|
3798
|
+
const [focused, setFocused] = (0, react.useState)(false);
|
|
3799
|
+
const [highlighted, setHighlighted] = (0, react.useState)(0);
|
|
3800
|
+
const committedRef = (0, react.useRef)(formatQuery(value));
|
|
3801
|
+
(0, react.useEffect)(() => {
|
|
3802
|
+
const next = formatQuery(value);
|
|
3803
|
+
if (next === committedRef.current) return;
|
|
3804
|
+
committedRef.current = next;
|
|
3805
|
+
if (!focused) setText(next);
|
|
3806
|
+
}, [value, focused]);
|
|
3807
|
+
const parsed = (0, react.useMemo)(() => parseQueryWithDiagnostics(text), [text]);
|
|
3808
|
+
const commit = (0, react.useCallback)((nextText) => {
|
|
3809
|
+
const nextQuery = parseQueryWithDiagnostics(nextText).query;
|
|
3810
|
+
committedRef.current = formatQuery(nextQuery);
|
|
3811
|
+
onChange(nextQuery);
|
|
3812
|
+
}, [onChange]);
|
|
3813
|
+
const suggestions = useSuggestions(text, parsed.trailing, fields);
|
|
3814
|
+
const applySuggestion = (0, react.useCallback)((suggestion) => {
|
|
3815
|
+
var _inputRef$current;
|
|
3816
|
+
const nextText = suggestion.replace(text);
|
|
3817
|
+
setText(nextText);
|
|
3818
|
+
if (suggestion.kind === "value") commit(nextText);
|
|
3819
|
+
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
|
|
3820
|
+
setHighlighted(0);
|
|
3821
|
+
}, [text, commit]);
|
|
3822
|
+
const handleKeyDown = (event) => {
|
|
3823
|
+
if (suggestions.length > 0 && focused) {
|
|
3824
|
+
if (event.key === "ArrowDown") {
|
|
3825
|
+
event.preventDefault();
|
|
3826
|
+
setHighlighted((current) => (current + 1) % suggestions.length);
|
|
3827
|
+
return;
|
|
3828
|
+
}
|
|
3829
|
+
if (event.key === "ArrowUp") {
|
|
3830
|
+
event.preventDefault();
|
|
3831
|
+
setHighlighted((current) => (current - 1 + suggestions.length) % suggestions.length);
|
|
3832
|
+
return;
|
|
3833
|
+
}
|
|
3834
|
+
if (event.key === "Tab" || event.key === "Enter" && highlighted > 0) {
|
|
3835
|
+
event.preventDefault();
|
|
3836
|
+
applySuggestion(suggestions[highlighted]);
|
|
3837
|
+
return;
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
if (event.key === "Enter") {
|
|
3841
|
+
event.preventDefault();
|
|
3842
|
+
commit(text);
|
|
3843
|
+
return;
|
|
3844
|
+
}
|
|
3845
|
+
if (event.key === "Escape") {
|
|
3846
|
+
var _inputRef$current2;
|
|
3847
|
+
setText(committedRef.current);
|
|
3848
|
+
(_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 || _inputRef$current2.blur();
|
|
3849
|
+
}
|
|
3850
|
+
};
|
|
3851
|
+
const showSuggestions = focused && suggestions.length > 0;
|
|
3852
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3853
|
+
className: require_pagination.cn("relative flex min-w-0 flex-col gap-1.5", className),
|
|
3854
|
+
"data-opt-id": "JSVZ1",
|
|
3855
|
+
"data-opt-slug": "query-bar.QueryBar",
|
|
3856
|
+
children: [
|
|
3857
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3858
|
+
className: require_pagination.cn("bg-surface flex h-9 items-center gap-2 rounded-md border px-2.5", require_pagination.OPT_BORDER, "focus-within:border-accent transition-colors", disabled && "opacity-60"),
|
|
3859
|
+
children: [
|
|
3860
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Search, {
|
|
3861
|
+
className: "text-text-tertiary size-4 shrink-0",
|
|
3862
|
+
"aria-hidden": true
|
|
3863
|
+
}),
|
|
3864
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
3865
|
+
ref: inputRef,
|
|
3866
|
+
type: "text",
|
|
3867
|
+
role: "combobox",
|
|
3868
|
+
"aria-expanded": showSuggestions,
|
|
3869
|
+
"aria-controls": "query-bar-suggestions",
|
|
3870
|
+
"aria-autocomplete": "list",
|
|
3871
|
+
"aria-label": labels.ariaLabel,
|
|
3872
|
+
placeholder: labels.placeholder,
|
|
3873
|
+
disabled,
|
|
3874
|
+
className: require_pagination.cn("text-text-primary placeholder:text-text-tertiary min-w-0 flex-1", "bg-transparent font-mono text-sm outline-none focus-visible:outline-none"),
|
|
3875
|
+
value: text,
|
|
3876
|
+
onChange: (event) => {
|
|
3877
|
+
setText(event.target.value);
|
|
3878
|
+
setHighlighted(0);
|
|
3879
|
+
if (live) commit(event.target.value);
|
|
3880
|
+
},
|
|
3881
|
+
onFocus: () => setFocused(true),
|
|
3882
|
+
onBlur: () => {
|
|
3883
|
+
window.setTimeout(() => setFocused(false), 120);
|
|
3884
|
+
if (!live) commit(text);
|
|
3885
|
+
},
|
|
3886
|
+
onKeyDown: handleKeyDown
|
|
3887
|
+
}),
|
|
3888
|
+
text.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3889
|
+
type: "button",
|
|
3890
|
+
"aria-label": labels.clear,
|
|
3891
|
+
title: labels.clear,
|
|
3892
|
+
className: require_pagination.cn("text-text-tertiary hover:text-text-primary cursor-pointer rounded-sm p-0.5", "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset"),
|
|
3893
|
+
onClick: () => {
|
|
3894
|
+
setText("");
|
|
3895
|
+
commit("");
|
|
3896
|
+
},
|
|
3897
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {
|
|
3898
|
+
className: "size-3.5",
|
|
3899
|
+
"aria-hidden": true
|
|
3900
|
+
})
|
|
3901
|
+
})
|
|
3902
|
+
]
|
|
3903
|
+
}),
|
|
3904
|
+
showSuggestions && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
3905
|
+
id: "query-bar-suggestions",
|
|
3906
|
+
role: "listbox",
|
|
3907
|
+
className: require_pagination.cn("bg-surface-overlay absolute top-10 z-40 max-h-64 w-full overflow-y-auto rounded-md border p-1 shadow-lg", "border border-border"),
|
|
3908
|
+
children: suggestions.map((suggestion, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3909
|
+
type: "button",
|
|
3910
|
+
role: "option",
|
|
3911
|
+
"aria-selected": index === highlighted,
|
|
3912
|
+
className: require_pagination.cn("flex w-full cursor-pointer items-center justify-between gap-3 rounded px-2 py-1 text-left text-sm", index === highlighted ? "bg-bg-subtle" : "hover:bg-bg-subtle"),
|
|
3913
|
+
onMouseEnter: () => setHighlighted(index),
|
|
3914
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
3915
|
+
onClick: () => applySuggestion(suggestion),
|
|
3916
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3917
|
+
className: "text-text-primary truncate font-mono",
|
|
3918
|
+
children: suggestion.label
|
|
3919
|
+
}), suggestion.hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3920
|
+
className: "text-text-tertiary shrink-0 text-xs",
|
|
3921
|
+
children: suggestion.hint
|
|
3922
|
+
})]
|
|
3923
|
+
}) }, `${suggestion.kind}-${suggestion.label}`))
|
|
3924
|
+
}),
|
|
3925
|
+
showClauses && value.clauses.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
3926
|
+
className: "flex flex-wrap items-center gap-1.5",
|
|
3927
|
+
children: value.clauses.map((clause, index) => {
|
|
3928
|
+
const label = describeClause(clause);
|
|
3929
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
3930
|
+
className: require_pagination.cn("bg-bg-subtle text-text-secondary flex items-center gap-1 rounded px-1.5 py-0.5 text-xs", clause.negated && "text-danger-fg"),
|
|
3931
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3932
|
+
className: "truncate",
|
|
3933
|
+
children: label
|
|
3934
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3935
|
+
type: "button",
|
|
3936
|
+
"aria-label": labels.removeClause(label),
|
|
3937
|
+
className: "hover:text-text-primary cursor-pointer",
|
|
3938
|
+
onClick: () => {
|
|
3939
|
+
const next = removeClause(value, index);
|
|
3940
|
+
setText(formatQuery(next));
|
|
3941
|
+
committedRef.current = formatQuery(next);
|
|
3942
|
+
onChange(next);
|
|
3943
|
+
},
|
|
3944
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {
|
|
3945
|
+
className: "size-3",
|
|
3946
|
+
"aria-hidden": true
|
|
3947
|
+
})
|
|
3948
|
+
})]
|
|
3949
|
+
}, `${label}-${index}`);
|
|
3950
|
+
})
|
|
3951
|
+
}),
|
|
3952
|
+
parsed.diagnostics.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QueryDiagnostics, { diagnostics: parsed.diagnostics }),
|
|
3953
|
+
labels.syntaxHint && !showSuggestions && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
3954
|
+
className: "text-text-tertiary text-xs",
|
|
3955
|
+
children: labels.syntaxHint
|
|
3956
|
+
})
|
|
3957
|
+
]
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
QueryBar.displayName = "QueryBar";
|
|
3961
|
+
function QueryDiagnostics({ diagnostics }) {
|
|
3962
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
3963
|
+
className: "text-warning-fg flex flex-col gap-0.5 text-xs",
|
|
3964
|
+
children: diagnostics.map((diagnostic, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
3965
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
3966
|
+
className: "font-mono",
|
|
3967
|
+
children: diagnostic.text
|
|
3968
|
+
}),
|
|
3969
|
+
" —",
|
|
3970
|
+
" ",
|
|
3971
|
+
diagnostic.message
|
|
3972
|
+
] }, `${diagnostic.at}-${index}`))
|
|
3973
|
+
});
|
|
3974
|
+
}
|
|
3975
|
+
/**
|
|
3976
|
+
* What to offer, based on the token under the cursor.
|
|
3977
|
+
*
|
|
3978
|
+
* Completing fields is the part that makes the syntax discoverable at all —
|
|
3979
|
+
* nobody guesses that `utm_source` is a field they can filter on. Once the
|
|
3980
|
+
* field is chosen, its known values are the second half of the same guess.
|
|
3981
|
+
*/
|
|
3982
|
+
function useSuggestions(text, trailing, fields) {
|
|
3983
|
+
return (0, react.useMemo)(() => {
|
|
3984
|
+
const lastSpace = text.lastIndexOf(" ");
|
|
3985
|
+
const head = lastSpace === -1 ? "" : text.slice(0, lastSpace + 1);
|
|
3986
|
+
const token = text.slice(lastSpace + 1);
|
|
3987
|
+
if (trailing) {
|
|
3988
|
+
var _field$values;
|
|
3989
|
+
const field = fields.find((candidate) => candidate.name === trailing.field);
|
|
3990
|
+
const values = (_field$values = field === null || field === void 0 ? void 0 : field.values) !== null && _field$values !== void 0 ? _field$values : [];
|
|
3991
|
+
const typed = trailing.value.toLowerCase();
|
|
3992
|
+
return values.filter((value) => value.toLowerCase().includes(typed)).slice(0, MAX_SUGGESTIONS).map((value) => ({
|
|
3993
|
+
kind: "value",
|
|
3994
|
+
label: `${trailing.field}:${value}`,
|
|
3995
|
+
replace: () => `${head}${trailing.field}:${/[\s"()]/.test(value) ? `"${value}"` : value} `
|
|
3996
|
+
}));
|
|
3997
|
+
}
|
|
3998
|
+
const bare = token.replace(/^-/, "").toLowerCase();
|
|
3999
|
+
if (bare.length === 0) return [];
|
|
4000
|
+
return fields.filter((field) => field.name.toLowerCase().includes(bare)).slice(0, MAX_SUGGESTIONS).map((field) => ({
|
|
4001
|
+
kind: "field",
|
|
4002
|
+
label: `${field.name}:`,
|
|
4003
|
+
hint: field.hint,
|
|
4004
|
+
replace: () => `${head}${token.startsWith("-") ? "-" : ""}${field.name}:`
|
|
4005
|
+
}));
|
|
4006
|
+
}, [
|
|
4007
|
+
text,
|
|
4008
|
+
trailing,
|
|
4009
|
+
fields
|
|
4010
|
+
]);
|
|
4011
|
+
}
|
|
4012
|
+
//#endregion
|
|
4013
|
+
//#region src/shells/field-sidebar.tsx
|
|
4014
|
+
const defaultLabels = {
|
|
4015
|
+
heading: "Fields",
|
|
4016
|
+
searchPlaceholder: "Find a field",
|
|
4017
|
+
searchLabel: "Find a field",
|
|
4018
|
+
empty: "No fields match.",
|
|
4019
|
+
addColumn: (field) => `Show ${field} as a column`,
|
|
4020
|
+
removeColumn: (field) => `Remove the ${field} column`,
|
|
4021
|
+
filterFor: (value) => `Filter for ${value}`,
|
|
4022
|
+
filterOut: (value) => `Filter out ${value}`,
|
|
4023
|
+
topValues: (count) => `Top ${count} values`,
|
|
4024
|
+
noValues: "No values in this range.",
|
|
4025
|
+
loadingValues: "Counting…",
|
|
4026
|
+
otherGroup: "Other"
|
|
4027
|
+
};
|
|
4028
|
+
/**
|
|
4029
|
+
* FieldSidebar — what is in this data, and what does it contain.
|
|
4030
|
+
*
|
|
4031
|
+
* Without it, a log answers only the questions you already knew to ask: you can
|
|
4032
|
+
* filter on `utm_source` if you remember it exists, and you can read a value if
|
|
4033
|
+
* you open a row and scroll to it. Listing the fields turns that around — the
|
|
4034
|
+
* data tells the reader what is available — and the value counts answer the
|
|
4035
|
+
* question that actually follows ("mostly which?") before they have to run a
|
|
4036
|
+
* query to find out.
|
|
4037
|
+
*
|
|
4038
|
+
* Three actions live here because they are the three things people do next:
|
|
4039
|
+
* put the field in the table, keep only rows with a value, drop rows with it.
|
|
4040
|
+
*
|
|
4041
|
+
* @example
|
|
4042
|
+
* ```tsx
|
|
4043
|
+
* <FieldSidebar
|
|
4044
|
+
* fields={fields}
|
|
4045
|
+
* expandedField={openField}
|
|
4046
|
+
* onExpandedFieldChange={setOpenField}
|
|
4047
|
+
* values={valuesQuery.data}
|
|
4048
|
+
* valuesLoading={valuesQuery.isLoading}
|
|
4049
|
+
* onToggleColumn={(field, next) => setColumns(...)}
|
|
4050
|
+
* onFilter={(field, value, intent) => setQuery(applyCellAction(query, { field, value, intent }))}
|
|
4051
|
+
* />
|
|
4052
|
+
* ```
|
|
4053
|
+
*/
|
|
4054
|
+
function FieldSidebar({ fields, expandedField = null, onExpandedFieldChange, values, valuesLoading = false, sampledRows, onToggleColumn, onFilter, header, className, labels: customLabels }) {
|
|
4055
|
+
const labels = require_pagination._objectSpread2(require_pagination._objectSpread2({}, defaultLabels), customLabels);
|
|
4056
|
+
const [search, setSearch] = (0, react.useState)("");
|
|
4057
|
+
const groups = (0, react.useMemo)(() => {
|
|
4058
|
+
const needle = search.trim().toLowerCase();
|
|
4059
|
+
const matching = needle ? fields.filter((field) => field.name.toLowerCase().includes(needle)) : fields;
|
|
4060
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
4061
|
+
for (const field of matching) {
|
|
4062
|
+
var _field$group;
|
|
4063
|
+
const key = (_field$group = field.group) !== null && _field$group !== void 0 ? _field$group : "";
|
|
4064
|
+
const bucket = byGroup.get(key);
|
|
4065
|
+
if (bucket) bucket.push(field);
|
|
4066
|
+
else byGroup.set(key, [field]);
|
|
4067
|
+
}
|
|
4068
|
+
return [...byGroup.entries()];
|
|
4069
|
+
}, [fields, search]);
|
|
4070
|
+
const total = (0, react.useMemo)(() => (values !== null && values !== void 0 ? values : []).reduce((sum, entry) => sum + entry.count, 0), [values]);
|
|
4071
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
4072
|
+
className: require_pagination.cn("flex min-h-0 flex-col", className),
|
|
4073
|
+
"aria-label": labels.heading,
|
|
4074
|
+
"data-opt-id": "17D4Y",
|
|
4075
|
+
"data-opt-slug": "field-sidebar.FieldSidebar",
|
|
4076
|
+
children: [
|
|
4077
|
+
header,
|
|
4078
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4079
|
+
className: require_pagination.cn("flex items-center gap-2 border-b px-3 py-2", "focus-within:border-accent transition-colors", require_pagination.OPT_BORDER),
|
|
4080
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Search, {
|
|
4081
|
+
className: "text-text-tertiary size-3.5 shrink-0",
|
|
4082
|
+
"aria-hidden": true
|
|
4083
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
4084
|
+
type: "search",
|
|
4085
|
+
"aria-label": labels.searchLabel,
|
|
4086
|
+
placeholder: labels.searchPlaceholder,
|
|
4087
|
+
className: require_pagination.cn("text-text-primary placeholder:text-text-tertiary min-w-0 flex-1", "bg-transparent text-sm outline-none focus-visible:outline-none"),
|
|
4088
|
+
value: search,
|
|
4089
|
+
onChange: (event) => setSearch(event.target.value)
|
|
4090
|
+
})]
|
|
4091
|
+
}),
|
|
4092
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4093
|
+
className: "min-h-0 flex-1 overflow-y-auto",
|
|
4094
|
+
children: groups.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4095
|
+
className: require_pagination.cn(require_pagination.OPT_TEXT_TERTIARY, "px-3 py-4 text-xs"),
|
|
4096
|
+
children: labels.empty
|
|
4097
|
+
}) : groups.map(([group, groupFields]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { children: [group && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
4098
|
+
className: require_pagination.cn("text-text-tertiary", "px-3 pt-3 pb-1 text-[11px] font-medium tracking-wide uppercase"),
|
|
4099
|
+
children: group
|
|
4100
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: groupFields.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldRow, {
|
|
4101
|
+
field,
|
|
4102
|
+
expanded: expandedField === field.name,
|
|
4103
|
+
onToggleExpanded: () => onExpandedFieldChange === null || onExpandedFieldChange === void 0 ? void 0 : onExpandedFieldChange(expandedField === field.name ? null : field.name),
|
|
4104
|
+
onToggleColumn,
|
|
4105
|
+
labels,
|
|
4106
|
+
children: expandedField === field.name && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldValues, {
|
|
4107
|
+
values,
|
|
4108
|
+
loading: valuesLoading,
|
|
4109
|
+
total,
|
|
4110
|
+
sampledRows,
|
|
4111
|
+
onFilter: onFilter ? (value, intent) => onFilter(field.name, value, intent) : void 0,
|
|
4112
|
+
labels
|
|
4113
|
+
})
|
|
4114
|
+
}, field.name)) })] }, group || "__ungrouped__"))
|
|
4115
|
+
})
|
|
4116
|
+
]
|
|
4117
|
+
});
|
|
4118
|
+
}
|
|
4119
|
+
FieldSidebar.displayName = "FieldSidebar";
|
|
4120
|
+
function FieldRow({ field, expanded, onToggleExpanded, onToggleColumn, labels, children }) {
|
|
4121
|
+
var _field$columnable, _field$inTable;
|
|
4122
|
+
const columnable = (_field$columnable = field.columnable) !== null && _field$columnable !== void 0 ? _field$columnable : true;
|
|
4123
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4124
|
+
className: "hover:bg-bg-subtle group/field flex items-center gap-1.5 px-3 py-1",
|
|
4125
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4126
|
+
type: "button",
|
|
4127
|
+
className: "flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-left",
|
|
4128
|
+
"aria-expanded": expanded,
|
|
4129
|
+
onClick: onToggleExpanded,
|
|
4130
|
+
children: [field.type && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4131
|
+
className: require_pagination.cn("text-text-tertiary", "w-8 shrink-0 truncate font-mono text-[10px] uppercase"),
|
|
4132
|
+
children: field.type
|
|
4133
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4134
|
+
className: "text-text-primary truncate font-mono text-xs",
|
|
4135
|
+
children: field.name
|
|
4136
|
+
})]
|
|
4137
|
+
}), onToggleColumn && columnable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4138
|
+
type: "button",
|
|
4139
|
+
className: require_pagination.cn("cursor-pointer rounded-sm p-0.5", field.inTable ? "text-accent" : "text-text-tertiary hover:text-text-primary opacity-0 group-hover/field:opacity-100 focus-visible:opacity-100"),
|
|
4140
|
+
"aria-label": field.inTable ? labels.removeColumn(field.name) : labels.addColumn(field.name),
|
|
4141
|
+
title: field.inTable ? labels.removeColumn(field.name) : labels.addColumn(field.name),
|
|
4142
|
+
"aria-pressed": (_field$inTable = field.inTable) !== null && _field$inTable !== void 0 ? _field$inTable : false,
|
|
4143
|
+
onClick: () => onToggleColumn(field.name, !field.inTable),
|
|
4144
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Columns3, {
|
|
4145
|
+
className: "size-3.5",
|
|
4146
|
+
"aria-hidden": true
|
|
4147
|
+
})
|
|
4148
|
+
})]
|
|
4149
|
+
}), children] });
|
|
4150
|
+
}
|
|
4151
|
+
/**
|
|
4152
|
+
* The distribution, as bars.
|
|
4153
|
+
*
|
|
4154
|
+
* A count alone ("342") means nothing without the denominator, and a reader
|
|
4155
|
+
* scanning for "what is this field mostly" reads the bar before the number.
|
|
4156
|
+
* The sample size is stated rather than implied — these are the top values in
|
|
4157
|
+
* the rows that were counted, not in all of history, and a chart that hides
|
|
4158
|
+
* that invites the wrong conclusion.
|
|
4159
|
+
*/
|
|
4160
|
+
function FieldValues({ values, loading, total, sampledRows, onFilter, labels }) {
|
|
4161
|
+
if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
4162
|
+
className: require_pagination.cn(require_pagination.OPT_TEXT_TERTIARY, "flex items-center gap-2 px-3 pb-2 text-xs"),
|
|
4163
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_pagination.Spinner, {
|
|
4164
|
+
size: "sm",
|
|
4165
|
+
"aria-hidden": true
|
|
4166
|
+
}), labels.loadingValues]
|
|
4167
|
+
});
|
|
4168
|
+
if (!values || values.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4169
|
+
className: require_pagination.cn(require_pagination.OPT_TEXT_TERTIARY, "px-3 pb-2 text-xs"),
|
|
4170
|
+
children: labels.noValues
|
|
4171
|
+
});
|
|
4172
|
+
const denominator = sampledRows !== null && sampledRows !== void 0 ? sampledRows : total;
|
|
4173
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4174
|
+
className: "flex flex-col gap-1 px-3 pt-1 pb-2",
|
|
4175
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
4176
|
+
className: require_pagination.cn(require_pagination.OPT_TEXT_TERTIARY, "text-[11px]"),
|
|
4177
|
+
children: [labels.topValues(values.length), sampledRows ? ` · ${sampledRows.toLocaleString()}` : ""]
|
|
4178
|
+
}), values.map((entry) => {
|
|
4179
|
+
var _entry$share;
|
|
4180
|
+
const share = (_entry$share = entry.share) !== null && _entry$share !== void 0 ? _entry$share : denominator > 0 ? entry.count / denominator : 0;
|
|
4181
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4182
|
+
className: "group/value flex flex-col gap-0.5",
|
|
4183
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4184
|
+
className: "flex items-center justify-between gap-2",
|
|
4185
|
+
children: [
|
|
4186
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4187
|
+
className: "text-text-secondary min-w-0 flex-1 truncate font-mono text-[11px]",
|
|
4188
|
+
title: entry.value,
|
|
4189
|
+
children: entry.value || "—"
|
|
4190
|
+
}),
|
|
4191
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4192
|
+
className: require_pagination.cn(require_pagination.OPT_TEXT_TERTIARY, "shrink-0 text-[11px] tabular-nums"),
|
|
4193
|
+
children: [Math.round(share * 100), "%"]
|
|
4194
|
+
}),
|
|
4195
|
+
onFilter && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4196
|
+
className: "hidden shrink-0 items-center gap-0.5 group-hover/value:flex focus-within:flex",
|
|
4197
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4198
|
+
type: "button",
|
|
4199
|
+
className: "text-text-tertiary hover:text-text-primary cursor-pointer",
|
|
4200
|
+
"aria-label": labels.filterFor(entry.value),
|
|
4201
|
+
title: labels.filterFor(entry.value),
|
|
4202
|
+
onClick: () => onFilter(entry.value, "include"),
|
|
4203
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, {
|
|
4204
|
+
className: "size-3",
|
|
4205
|
+
"aria-hidden": true
|
|
4206
|
+
})
|
|
4207
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4208
|
+
type: "button",
|
|
4209
|
+
className: "text-text-tertiary hover:text-text-primary cursor-pointer",
|
|
4210
|
+
"aria-label": labels.filterOut(entry.value),
|
|
4211
|
+
title: labels.filterOut(entry.value),
|
|
4212
|
+
onClick: () => onFilter(entry.value, "exclude"),
|
|
4213
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Minus, {
|
|
4214
|
+
className: "size-3",
|
|
4215
|
+
"aria-hidden": true
|
|
4216
|
+
})
|
|
4217
|
+
})]
|
|
4218
|
+
})
|
|
4219
|
+
]
|
|
4220
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4221
|
+
className: "bg-bg-muted h-1 w-full overflow-hidden rounded-full",
|
|
4222
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4223
|
+
className: "bg-accent h-full rounded-full",
|
|
4224
|
+
style: { width: `${Math.max(2, Math.round(share * 100))}%` }
|
|
4225
|
+
})
|
|
4226
|
+
})]
|
|
4227
|
+
}, entry.value);
|
|
4228
|
+
})]
|
|
4229
|
+
});
|
|
4230
|
+
}
|
|
4231
|
+
//#endregion
|
|
2402
4232
|
Object.defineProperty(exports, "AppMenubar", {
|
|
2403
4233
|
enumerable: true,
|
|
2404
4234
|
get: function() {
|
|
@@ -2483,6 +4313,12 @@ Object.defineProperty(exports, "ContentTabs", {
|
|
|
2483
4313
|
return ContentTabs;
|
|
2484
4314
|
}
|
|
2485
4315
|
});
|
|
4316
|
+
Object.defineProperty(exports, "DEFAULT_TIME_PRESETS", {
|
|
4317
|
+
enumerable: true,
|
|
4318
|
+
get: function() {
|
|
4319
|
+
return DEFAULT_TIME_PRESETS;
|
|
4320
|
+
}
|
|
4321
|
+
});
|
|
2486
4322
|
Object.defineProperty(exports, "DashboardGrid", {
|
|
2487
4323
|
enumerable: true,
|
|
2488
4324
|
get: function() {
|
|
@@ -2507,6 +4343,12 @@ Object.defineProperty(exports, "DomainTable", {
|
|
|
2507
4343
|
return DomainTable;
|
|
2508
4344
|
}
|
|
2509
4345
|
});
|
|
4346
|
+
Object.defineProperty(exports, "EMPTY_QUERY", {
|
|
4347
|
+
enumerable: true,
|
|
4348
|
+
get: function() {
|
|
4349
|
+
return EMPTY_QUERY;
|
|
4350
|
+
}
|
|
4351
|
+
});
|
|
2510
4352
|
Object.defineProperty(exports, "EditorToolbar", {
|
|
2511
4353
|
enumerable: true,
|
|
2512
4354
|
get: function() {
|
|
@@ -2531,12 +4373,24 @@ Object.defineProperty(exports, "FaqAccordion", {
|
|
|
2531
4373
|
return FaqAccordion;
|
|
2532
4374
|
}
|
|
2533
4375
|
});
|
|
4376
|
+
Object.defineProperty(exports, "FieldSidebar", {
|
|
4377
|
+
enumerable: true,
|
|
4378
|
+
get: function() {
|
|
4379
|
+
return FieldSidebar;
|
|
4380
|
+
}
|
|
4381
|
+
});
|
|
2534
4382
|
Object.defineProperty(exports, "KeyValueEditor", {
|
|
2535
4383
|
enumerable: true,
|
|
2536
4384
|
get: function() {
|
|
2537
4385
|
return KeyValueEditor;
|
|
2538
4386
|
}
|
|
2539
4387
|
});
|
|
4388
|
+
Object.defineProperty(exports, "LogTable", {
|
|
4389
|
+
enumerable: true,
|
|
4390
|
+
get: function() {
|
|
4391
|
+
return LogTable;
|
|
4392
|
+
}
|
|
4393
|
+
});
|
|
2540
4394
|
Object.defineProperty(exports, "NotificationToast", {
|
|
2541
4395
|
enumerable: true,
|
|
2542
4396
|
get: function() {
|
|
@@ -2549,6 +4403,12 @@ Object.defineProperty(exports, "ProjectSwitcher", {
|
|
|
2549
4403
|
return ProjectSwitcher;
|
|
2550
4404
|
}
|
|
2551
4405
|
});
|
|
4406
|
+
Object.defineProperty(exports, "QueryBar", {
|
|
4407
|
+
enumerable: true,
|
|
4408
|
+
get: function() {
|
|
4409
|
+
return QueryBar;
|
|
4410
|
+
}
|
|
4411
|
+
});
|
|
2552
4412
|
Object.defineProperty(exports, "SearchCombobox", {
|
|
2553
4413
|
enumerable: true,
|
|
2554
4414
|
get: function() {
|
|
@@ -2573,6 +4433,18 @@ Object.defineProperty(exports, "StatusSelect", {
|
|
|
2573
4433
|
return StatusSelect;
|
|
2574
4434
|
}
|
|
2575
4435
|
});
|
|
4436
|
+
Object.defineProperty(exports, "TimeRangeControl", {
|
|
4437
|
+
enumerable: true,
|
|
4438
|
+
get: function() {
|
|
4439
|
+
return TimeRangeControl;
|
|
4440
|
+
}
|
|
4441
|
+
});
|
|
4442
|
+
Object.defineProperty(exports, "TimeSeriesPanel", {
|
|
4443
|
+
enumerable: true,
|
|
4444
|
+
get: function() {
|
|
4445
|
+
return TimeSeriesPanel;
|
|
4446
|
+
}
|
|
4447
|
+
});
|
|
2576
4448
|
Object.defineProperty(exports, "ToastProvider", {
|
|
2577
4449
|
enumerable: true,
|
|
2578
4450
|
get: function() {
|
|
@@ -2585,15 +4457,105 @@ Object.defineProperty(exports, "WorkflowCanvas", {
|
|
|
2585
4457
|
return WorkflowCanvas;
|
|
2586
4458
|
}
|
|
2587
4459
|
});
|
|
4460
|
+
Object.defineProperty(exports, "addClause", {
|
|
4461
|
+
enumerable: true,
|
|
4462
|
+
get: function() {
|
|
4463
|
+
return addClause;
|
|
4464
|
+
}
|
|
4465
|
+
});
|
|
4466
|
+
Object.defineProperty(exports, "applyCellAction", {
|
|
4467
|
+
enumerable: true,
|
|
4468
|
+
get: function() {
|
|
4469
|
+
return applyCellAction;
|
|
4470
|
+
}
|
|
4471
|
+
});
|
|
4472
|
+
Object.defineProperty(exports, "describeClause", {
|
|
4473
|
+
enumerable: true,
|
|
4474
|
+
get: function() {
|
|
4475
|
+
return describeClause;
|
|
4476
|
+
}
|
|
4477
|
+
});
|
|
4478
|
+
Object.defineProperty(exports, "fieldValues", {
|
|
4479
|
+
enumerable: true,
|
|
4480
|
+
get: function() {
|
|
4481
|
+
return fieldValues;
|
|
4482
|
+
}
|
|
4483
|
+
});
|
|
4484
|
+
Object.defineProperty(exports, "formatClause", {
|
|
4485
|
+
enumerable: true,
|
|
4486
|
+
get: function() {
|
|
4487
|
+
return formatClause;
|
|
4488
|
+
}
|
|
4489
|
+
});
|
|
4490
|
+
Object.defineProperty(exports, "formatQuery", {
|
|
4491
|
+
enumerable: true,
|
|
4492
|
+
get: function() {
|
|
4493
|
+
return formatQuery;
|
|
4494
|
+
}
|
|
4495
|
+
});
|
|
4496
|
+
Object.defineProperty(exports, "isEmptyQuery", {
|
|
4497
|
+
enumerable: true,
|
|
4498
|
+
get: function() {
|
|
4499
|
+
return isEmptyQuery;
|
|
4500
|
+
}
|
|
4501
|
+
});
|
|
4502
|
+
Object.defineProperty(exports, "parseQuery", {
|
|
4503
|
+
enumerable: true,
|
|
4504
|
+
get: function() {
|
|
4505
|
+
return parseQuery;
|
|
4506
|
+
}
|
|
4507
|
+
});
|
|
4508
|
+
Object.defineProperty(exports, "parseQueryWithDiagnostics", {
|
|
4509
|
+
enumerable: true,
|
|
4510
|
+
get: function() {
|
|
4511
|
+
return parseQueryWithDiagnostics;
|
|
4512
|
+
}
|
|
4513
|
+
});
|
|
4514
|
+
Object.defineProperty(exports, "queryFields", {
|
|
4515
|
+
enumerable: true,
|
|
4516
|
+
get: function() {
|
|
4517
|
+
return queryFields;
|
|
4518
|
+
}
|
|
4519
|
+
});
|
|
4520
|
+
Object.defineProperty(exports, "quoteValue", {
|
|
4521
|
+
enumerable: true,
|
|
4522
|
+
get: function() {
|
|
4523
|
+
return quoteValue;
|
|
4524
|
+
}
|
|
4525
|
+
});
|
|
4526
|
+
Object.defineProperty(exports, "removeClause", {
|
|
4527
|
+
enumerable: true,
|
|
4528
|
+
get: function() {
|
|
4529
|
+
return removeClause;
|
|
4530
|
+
}
|
|
4531
|
+
});
|
|
2588
4532
|
Object.defineProperty(exports, "toast", {
|
|
2589
4533
|
enumerable: true,
|
|
2590
4534
|
get: function() {
|
|
2591
4535
|
return toast;
|
|
2592
4536
|
}
|
|
2593
4537
|
});
|
|
4538
|
+
Object.defineProperty(exports, "useAutoRefresh", {
|
|
4539
|
+
enumerable: true,
|
|
4540
|
+
get: function() {
|
|
4541
|
+
return useAutoRefresh;
|
|
4542
|
+
}
|
|
4543
|
+
});
|
|
2594
4544
|
Object.defineProperty(exports, "useToast", {
|
|
2595
4545
|
enumerable: true,
|
|
2596
4546
|
get: function() {
|
|
2597
4547
|
return useToast;
|
|
2598
4548
|
}
|
|
2599
4549
|
});
|
|
4550
|
+
Object.defineProperty(exports, "withFieldValue", {
|
|
4551
|
+
enumerable: true,
|
|
4552
|
+
get: function() {
|
|
4553
|
+
return withFieldValue;
|
|
4554
|
+
}
|
|
4555
|
+
});
|
|
4556
|
+
Object.defineProperty(exports, "withoutField", {
|
|
4557
|
+
enumerable: true,
|
|
4558
|
+
get: function() {
|
|
4559
|
+
return withoutField;
|
|
4560
|
+
}
|
|
4561
|
+
});
|