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