datool 0.0.4 → 0.0.5
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/client-dist/assets/index-DUkIilaZ.css +1 -0
- package/client-dist/assets/index-OdNyDkx7.js +164 -0
- package/client-dist/index.html +2 -2
- package/package.json +1 -1
- package/src/client/App.tsx +146 -6
- package/src/client/components/data-table.tsx +3 -0
- package/src/client/components/viewer-settings.tsx +67 -0
- package/src/client/index.css +3 -0
- package/src/client/lib/datool-url-state.ts +29 -2
- package/client-dist/assets/index-B5MN-j1l.js +0 -164
- package/client-dist/assets/index-BkIiz0aS.css +0 -1
package/client-dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>datool</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-OdNyDkx7.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DUkIilaZ.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/client/App.tsx
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
} from "@/lib/data-table-search"
|
|
42
42
|
import {
|
|
43
43
|
readDatoolColumnVisibility,
|
|
44
|
+
readDatoolGrouping,
|
|
44
45
|
readDatoolSearch,
|
|
45
46
|
readSelectedStreamId,
|
|
46
47
|
writeDatoolUrlState,
|
|
@@ -59,10 +60,89 @@ type ViewerExportColumn = {
|
|
|
59
60
|
label: string
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
const GROUPED_ROW_GAP = 32
|
|
64
|
+
|
|
62
65
|
function toActionRows(rows: ViewerRow[]): Record<string, unknown>[] {
|
|
63
66
|
return rows.map(({ __datoolRowId: _datoolRowId, ...row }) => row)
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
function stringifyGroupingValue(value: unknown) {
|
|
70
|
+
if (value === undefined) {
|
|
71
|
+
return "undefined:"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (value === null) {
|
|
75
|
+
return "null:"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (value instanceof Date) {
|
|
79
|
+
return `date:${value.toISOString()}`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (typeof value === "object") {
|
|
83
|
+
try {
|
|
84
|
+
return `object:${JSON.stringify(value)}`
|
|
85
|
+
} catch {
|
|
86
|
+
return `object:${String(value)}`
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return `${typeof value}:${String(value)}`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function groupViewerRows(
|
|
94
|
+
rows: ViewerRow[],
|
|
95
|
+
columns: ViewerExportColumn[]
|
|
96
|
+
): {
|
|
97
|
+
groupStartRowIds: Set<string>
|
|
98
|
+
rows: ViewerRow[]
|
|
99
|
+
} {
|
|
100
|
+
if (columns.length === 0 || rows.length === 0) {
|
|
101
|
+
return {
|
|
102
|
+
groupStartRowIds: new Set<string>(),
|
|
103
|
+
rows,
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const groupOrder: string[] = []
|
|
108
|
+
const rowsByGroup = new Map<string, ViewerRow[]>()
|
|
109
|
+
|
|
110
|
+
for (const row of rows) {
|
|
111
|
+
const groupKey = columns
|
|
112
|
+
.map((column) =>
|
|
113
|
+
stringifyGroupingValue(getValueAtPath(row, column.accessorKey))
|
|
114
|
+
)
|
|
115
|
+
.join("\u001f")
|
|
116
|
+
const existingRows = rowsByGroup.get(groupKey)
|
|
117
|
+
|
|
118
|
+
if (existingRows) {
|
|
119
|
+
existingRows.push(row)
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
groupOrder.push(groupKey)
|
|
124
|
+
rowsByGroup.set(groupKey, [row])
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const groupedRows: ViewerRow[] = []
|
|
128
|
+
const groupStartRowIds = new Set<string>()
|
|
129
|
+
|
|
130
|
+
groupOrder.forEach((groupKey, index) => {
|
|
131
|
+
const groupRows = rowsByGroup.get(groupKey) ?? []
|
|
132
|
+
|
|
133
|
+
if (index > 0 && groupRows[0]) {
|
|
134
|
+
groupStartRowIds.add(groupRows[0].__datoolRowId)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
groupedRows.push(...groupRows)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
groupStartRowIds,
|
|
142
|
+
rows: groupedRows,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
66
146
|
function applyActionRowChanges(
|
|
67
147
|
currentRows: ViewerRow[],
|
|
68
148
|
targetRowIds: string[],
|
|
@@ -347,7 +427,10 @@ function DatoolTable({
|
|
|
347
427
|
isLoadingConfig,
|
|
348
428
|
rows,
|
|
349
429
|
settingsColumns,
|
|
430
|
+
groupedColumnIds,
|
|
431
|
+
groupedRowStartIds,
|
|
350
432
|
selectedStreamId,
|
|
433
|
+
setGroupedColumnIds,
|
|
351
434
|
setColumnVisibility,
|
|
352
435
|
searchInputRef,
|
|
353
436
|
handleExport,
|
|
@@ -369,7 +452,10 @@ function DatoolTable({
|
|
|
369
452
|
label: string
|
|
370
453
|
visible: boolean
|
|
371
454
|
}>
|
|
455
|
+
groupedColumnIds: string[]
|
|
456
|
+
groupedRowStartIds: Set<string>
|
|
372
457
|
selectedStreamId: string | null
|
|
458
|
+
setGroupedColumnIds: React.Dispatch<React.SetStateAction<string[]>>
|
|
373
459
|
setColumnVisibility: React.Dispatch<React.SetStateAction<VisibilityState>>
|
|
374
460
|
searchInputRef: React.RefObject<DataTableSearchInputHandle | null>
|
|
375
461
|
handleExport: (format: "csv" | "md") => void
|
|
@@ -378,6 +464,19 @@ function DatoolTable({
|
|
|
378
464
|
setShouldConnect: React.Dispatch<React.SetStateAction<boolean>>
|
|
379
465
|
}) {
|
|
380
466
|
const { search, setSearch } = useDataTableContext<ViewerRow>()
|
|
467
|
+
const resolveRowStyle = React.useCallback(
|
|
468
|
+
(row: ViewerRow) => {
|
|
469
|
+
if (!groupedRowStartIds.has(row.__datoolRowId)) {
|
|
470
|
+
return undefined
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
borderTop: `${GROUPED_ROW_GAP}px solid var(--color-table-gap)`,
|
|
475
|
+
boxShadow: `inset 0 1px 0 0 var(--color-border)`,
|
|
476
|
+
} satisfies React.CSSProperties
|
|
477
|
+
},
|
|
478
|
+
[groupedRowStartIds]
|
|
479
|
+
)
|
|
381
480
|
const rowActions = React.useMemo<DataTableRowAction<ViewerRow>[]>(
|
|
382
481
|
() => {
|
|
383
482
|
const configActions =
|
|
@@ -526,9 +625,24 @@ function DatoolTable({
|
|
|
526
625
|
/>
|
|
527
626
|
<ViewerSettings
|
|
528
627
|
columns={settingsColumns}
|
|
628
|
+
groupedColumnIds={groupedColumnIds}
|
|
529
629
|
isDisabled={isLoadingConfig || !activeStream}
|
|
530
630
|
onExportCsv={() => handleExport("csv")}
|
|
531
631
|
onExportMarkdown={() => handleExport("md")}
|
|
632
|
+
onClearGrouping={() => setGroupedColumnIds([])}
|
|
633
|
+
onToggleGrouping={(columnId, grouped) =>
|
|
634
|
+
setGroupedColumnIds((current) => {
|
|
635
|
+
if (grouped) {
|
|
636
|
+
return current.includes(columnId)
|
|
637
|
+
? current
|
|
638
|
+
: [...current, columnId]
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return current.filter(
|
|
642
|
+
(currentColumnId) => currentColumnId !== columnId
|
|
643
|
+
)
|
|
644
|
+
})
|
|
645
|
+
}
|
|
532
646
|
onToggleColumn={(columnId, visible) =>
|
|
533
647
|
setColumnVisibility((current) => ({
|
|
534
648
|
...current,
|
|
@@ -545,7 +659,7 @@ function DatoolTable({
|
|
|
545
659
|
|
|
546
660
|
<div className="min-h-0 flex-1">
|
|
547
661
|
{activeStream ? (
|
|
548
|
-
<DataTable rowActions={rowActions} />
|
|
662
|
+
<DataTable rowActions={rowActions} rowStyle={resolveRowStyle} />
|
|
549
663
|
) : (
|
|
550
664
|
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
|
|
551
665
|
No stream selected.
|
|
@@ -582,6 +696,7 @@ export default function App() {
|
|
|
582
696
|
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(
|
|
583
697
|
{}
|
|
584
698
|
)
|
|
699
|
+
const [groupedColumnIds, setGroupedColumnIds] = React.useState<string[]>([])
|
|
585
700
|
const eventSourceRef = React.useRef<EventSource | null>(null)
|
|
586
701
|
const hasInitializedStreamRef = React.useRef(false)
|
|
587
702
|
const [hydratedTableId, setHydratedTableId] = React.useState<string | null>(
|
|
@@ -677,6 +792,23 @@ export default function App() {
|
|
|
677
792
|
exportColumns.filter((column) => columnVisibility[column.id] !== false),
|
|
678
793
|
[columnVisibility, exportColumns]
|
|
679
794
|
)
|
|
795
|
+
const exportColumnsById = React.useMemo(
|
|
796
|
+
() => new Map(exportColumns.map((column) => [column.id, column])),
|
|
797
|
+
[exportColumns]
|
|
798
|
+
)
|
|
799
|
+
const groupedExportColumns = React.useMemo(
|
|
800
|
+
() =>
|
|
801
|
+
groupedColumnIds.flatMap((columnId) => {
|
|
802
|
+
const column = exportColumnsById.get(columnId)
|
|
803
|
+
|
|
804
|
+
return column ? [column] : []
|
|
805
|
+
}),
|
|
806
|
+
[exportColumnsById, groupedColumnIds]
|
|
807
|
+
)
|
|
808
|
+
const groupedRowsState = React.useMemo(
|
|
809
|
+
() => groupViewerRows(rows, groupedExportColumns),
|
|
810
|
+
[groupedExportColumns, rows]
|
|
811
|
+
)
|
|
680
812
|
const isConnecting = Boolean(selectedStreamId) && shouldConnect && !isConnected
|
|
681
813
|
const columnIds = React.useMemo(
|
|
682
814
|
() => exportColumns.map((column) => column.id),
|
|
@@ -694,12 +826,14 @@ export default function App() {
|
|
|
694
826
|
|
|
695
827
|
React.useEffect(() => {
|
|
696
828
|
if (!activeStream) {
|
|
829
|
+
setGroupedColumnIds([])
|
|
697
830
|
setHydratedTableId(null)
|
|
698
831
|
return
|
|
699
832
|
}
|
|
700
833
|
|
|
701
834
|
setSearch(readDatoolSearch(tableId))
|
|
702
835
|
setColumnVisibility(readDatoolColumnVisibility(tableId, columnIds))
|
|
836
|
+
setGroupedColumnIds(readDatoolGrouping(tableId, columnIds))
|
|
703
837
|
setHydratedTableId(tableId)
|
|
704
838
|
}, [activeStream, columnIds, tableId])
|
|
705
839
|
|
|
@@ -712,6 +846,7 @@ export default function App() {
|
|
|
712
846
|
writeDatoolUrlState({
|
|
713
847
|
columnIds,
|
|
714
848
|
columnVisibility,
|
|
849
|
+
groupBy: groupedColumnIds,
|
|
715
850
|
search,
|
|
716
851
|
selectedStreamId,
|
|
717
852
|
tableId,
|
|
@@ -723,6 +858,7 @@ export default function App() {
|
|
|
723
858
|
activeStream,
|
|
724
859
|
columnIds,
|
|
725
860
|
columnVisibility,
|
|
861
|
+
groupedColumnIds,
|
|
726
862
|
hydratedTableId,
|
|
727
863
|
search,
|
|
728
864
|
selectedStreamId,
|
|
@@ -740,6 +876,7 @@ export default function App() {
|
|
|
740
876
|
setColumnVisibility(
|
|
741
877
|
readDatoolColumnVisibility(nextTableId, columnIds)
|
|
742
878
|
)
|
|
879
|
+
setGroupedColumnIds(readDatoolGrouping(nextTableId, columnIds))
|
|
743
880
|
setHydratedTableId(nextTableId)
|
|
744
881
|
}
|
|
745
882
|
|
|
@@ -853,8 +990,8 @@ export default function App() {
|
|
|
853
990
|
const fileBaseName = `${sanitizeFilePart(activeStream.label)}-${timeStamp}`
|
|
854
991
|
const content =
|
|
855
992
|
format === "csv"
|
|
856
|
-
? buildCsvContent(rows, visibleExportColumns)
|
|
857
|
-
: buildMarkdownContent(rows, visibleExportColumns)
|
|
993
|
+
? buildCsvContent(groupedRowsState.rows, visibleExportColumns)
|
|
994
|
+
: buildMarkdownContent(groupedRowsState.rows, visibleExportColumns)
|
|
858
995
|
|
|
859
996
|
downloadTextFile(
|
|
860
997
|
content,
|
|
@@ -862,15 +999,15 @@ export default function App() {
|
|
|
862
999
|
format === "csv" ? "text/csv" : "text/markdown"
|
|
863
1000
|
)
|
|
864
1001
|
},
|
|
865
|
-
[activeStream, rows, visibleExportColumns]
|
|
1002
|
+
[activeStream, groupedRowsState.rows, visibleExportColumns]
|
|
866
1003
|
)
|
|
867
1004
|
|
|
868
1005
|
return (
|
|
869
1006
|
<DataTableProvider
|
|
870
|
-
autoScrollToBottom
|
|
1007
|
+
autoScrollToBottom={groupedColumnIds.length === 0}
|
|
871
1008
|
columnVisibility={columnVisibility}
|
|
872
1009
|
columns={columns}
|
|
873
|
-
data={rows}
|
|
1010
|
+
data={groupedRowsState.rows}
|
|
874
1011
|
getRowId={(row) => row.__datoolRowId}
|
|
875
1012
|
height="100%"
|
|
876
1013
|
id={tableId}
|
|
@@ -889,8 +1026,11 @@ export default function App() {
|
|
|
889
1026
|
isConnected={isConnected}
|
|
890
1027
|
isConnecting={isConnecting}
|
|
891
1028
|
isLoadingConfig={isLoadingConfig}
|
|
1029
|
+
groupedColumnIds={groupedColumnIds}
|
|
1030
|
+
groupedRowStartIds={groupedRowsState.groupStartRowIds}
|
|
892
1031
|
rows={rows}
|
|
893
1032
|
settingsColumns={settingsColumns}
|
|
1033
|
+
setGroupedColumnIds={setGroupedColumnIds}
|
|
894
1034
|
setColumnVisibility={setColumnVisibility}
|
|
895
1035
|
searchInputRef={searchInputRef}
|
|
896
1036
|
selectedStreamId={selectedStreamId}
|
|
@@ -184,6 +184,7 @@ export type DataTableProps<TData extends DataTableRow> = {
|
|
|
184
184
|
resolveColumnHighlightTerms?: (columnId: string, query: string) => string[]
|
|
185
185
|
rowActions?: DataTableRowAction<TData>[]
|
|
186
186
|
rowClassName?: (row: TData) => string | undefined
|
|
187
|
+
rowStyle?: (row: TData) => React.CSSProperties | undefined
|
|
187
188
|
rowHeight?: number
|
|
188
189
|
statePersistence?: "localStorage" | "none" | "url"
|
|
189
190
|
}
|
|
@@ -1139,6 +1140,7 @@ function DataTableView<TData extends DataTableRow>({
|
|
|
1139
1140
|
resolveColumnHighlightTerms,
|
|
1140
1141
|
rowActions,
|
|
1141
1142
|
rowClassName,
|
|
1143
|
+
rowStyle,
|
|
1142
1144
|
rowHeight = 48,
|
|
1143
1145
|
statePersistence = "localStorage",
|
|
1144
1146
|
}: DataTableProps<TData>) {
|
|
@@ -1923,6 +1925,7 @@ function DataTableView<TData extends DataTableRow>({
|
|
|
1923
1925
|
}
|
|
1924
1926
|
}}
|
|
1925
1927
|
style={{
|
|
1928
|
+
...rowStyle?.(row.original),
|
|
1926
1929
|
minHeight: rowHeight,
|
|
1927
1930
|
transform: `translateY(${virtualRow.start}px)`,
|
|
1928
1931
|
width: table.getTotalSize(),
|
|
@@ -2,6 +2,7 @@ import * as React from "react"
|
|
|
2
2
|
import {
|
|
3
3
|
DownloadIcon,
|
|
4
4
|
EllipsisIcon,
|
|
5
|
+
LayoutGridIcon,
|
|
5
6
|
MoonIcon,
|
|
6
7
|
Settings2Icon,
|
|
7
8
|
SunIcon,
|
|
@@ -38,9 +39,12 @@ type ViewerSettingsColumn = {
|
|
|
38
39
|
|
|
39
40
|
type ViewerSettingsProps = {
|
|
40
41
|
columns: ViewerSettingsColumn[]
|
|
42
|
+
groupedColumnIds: string[]
|
|
41
43
|
isDisabled?: boolean
|
|
42
44
|
onExportCsv: () => void
|
|
43
45
|
onExportMarkdown: () => void
|
|
46
|
+
onClearGrouping: () => void
|
|
47
|
+
onToggleGrouping: (columnId: string, grouped: boolean) => void
|
|
44
48
|
onToggleColumn: (columnId: string, visible: boolean) => void
|
|
45
49
|
className?: string
|
|
46
50
|
}
|
|
@@ -69,14 +73,26 @@ const THEME_OPTIONS: Array<{
|
|
|
69
73
|
|
|
70
74
|
export function ViewerSettings({
|
|
71
75
|
columns,
|
|
76
|
+
groupedColumnIds,
|
|
72
77
|
isDisabled = false,
|
|
73
78
|
onExportCsv,
|
|
74
79
|
onExportMarkdown,
|
|
80
|
+
onClearGrouping,
|
|
81
|
+
onToggleGrouping,
|
|
75
82
|
onToggleColumn,
|
|
76
83
|
className,
|
|
77
84
|
}: ViewerSettingsProps) {
|
|
78
85
|
const { theme, setTheme } = useTheme()
|
|
79
86
|
const canExport = !isDisabled && columns.length > 0
|
|
87
|
+
const groupedLabels = React.useMemo(
|
|
88
|
+
() =>
|
|
89
|
+
groupedColumnIds.flatMap((columnId) => {
|
|
90
|
+
const column = columns.find((candidate) => candidate.id === columnId)
|
|
91
|
+
|
|
92
|
+
return column ? [column.label] : []
|
|
93
|
+
}),
|
|
94
|
+
[columns, groupedColumnIds]
|
|
95
|
+
)
|
|
80
96
|
|
|
81
97
|
return (
|
|
82
98
|
<DropdownMenu>
|
|
@@ -131,6 +147,57 @@ export function ViewerSettings({
|
|
|
131
147
|
))}
|
|
132
148
|
</DropdownMenuSubContent>
|
|
133
149
|
</DropdownMenuSub>
|
|
150
|
+
<DropdownMenuSub>
|
|
151
|
+
<DropdownMenuSubTrigger
|
|
152
|
+
className="min-h-9 text-sm"
|
|
153
|
+
disabled={isDisabled || columns.length === 0}
|
|
154
|
+
>
|
|
155
|
+
<LayoutGridIcon className="size-4 text-muted-foreground" />
|
|
156
|
+
Group rows
|
|
157
|
+
</DropdownMenuSubTrigger>
|
|
158
|
+
<DropdownMenuSubContent className="max-h-80 w-64 overflow-y-auto">
|
|
159
|
+
<DropdownMenuLabel>
|
|
160
|
+
{groupedLabels.length > 0
|
|
161
|
+
? `Grouped by ${groupedLabels.join(", ")}`
|
|
162
|
+
: "Group rows by field"}
|
|
163
|
+
</DropdownMenuLabel>
|
|
164
|
+
<DropdownMenuSeparator />
|
|
165
|
+
<DropdownMenuItem
|
|
166
|
+
className="min-h-9 text-sm"
|
|
167
|
+
disabled={groupedColumnIds.length === 0}
|
|
168
|
+
onSelect={(event) => {
|
|
169
|
+
event.preventDefault()
|
|
170
|
+
onClearGrouping()
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
Clear grouping
|
|
174
|
+
</DropdownMenuItem>
|
|
175
|
+
<DropdownMenuSeparator />
|
|
176
|
+
{columns.map((column) => (
|
|
177
|
+
<DropdownMenuCheckboxItem
|
|
178
|
+
key={column.id}
|
|
179
|
+
checked={groupedColumnIds.includes(column.id)}
|
|
180
|
+
className="min-h-9 text-sm"
|
|
181
|
+
onSelect={(event) => {
|
|
182
|
+
event.preventDefault()
|
|
183
|
+
}}
|
|
184
|
+
onCheckedChange={(checked) =>
|
|
185
|
+
onToggleGrouping(column.id, checked === true)
|
|
186
|
+
}
|
|
187
|
+
>
|
|
188
|
+
<span className="flex min-w-0 items-center gap-2 pr-4">
|
|
189
|
+
{column.kind ? (
|
|
190
|
+
<DataTableColIcon
|
|
191
|
+
kind={column.kind}
|
|
192
|
+
className="size-4 shrink-0 text-muted-foreground"
|
|
193
|
+
/>
|
|
194
|
+
) : null}
|
|
195
|
+
<span className="truncate">{column.label}</span>
|
|
196
|
+
</span>
|
|
197
|
+
</DropdownMenuCheckboxItem>
|
|
198
|
+
))}
|
|
199
|
+
</DropdownMenuSubContent>
|
|
200
|
+
</DropdownMenuSub>
|
|
134
201
|
<DropdownMenuSub>
|
|
135
202
|
<DropdownMenuSubTrigger className="min-h-9 text-sm">
|
|
136
203
|
<SunIcon className="size-4 text-muted-foreground dark:hidden" />
|
package/src/client/index.css
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
--secondary: oklch(0.967 0.001 286.375);
|
|
18
18
|
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
19
19
|
--muted: oklch(0.967 0.001 286.375);
|
|
20
|
+
--table-gap: oklch(0.967 0.001 286.375);
|
|
20
21
|
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
21
22
|
--accent: oklch(0.967 0.001 286.375);
|
|
22
23
|
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
--secondary: oklch(0.274 0.006 286.033);
|
|
53
54
|
--secondary-foreground: oklch(0.985 0 0);
|
|
54
55
|
--muted: oklch(0.274 0.006 286.033);
|
|
56
|
+
--table-gap: oklch(0.180 0.005 285.823);
|
|
55
57
|
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
56
58
|
--accent: oklch(0.274 0.006 286.033);
|
|
57
59
|
--accent-foreground: oklch(0.985 0 0);
|
|
@@ -97,6 +99,7 @@
|
|
|
97
99
|
--color-accent: var(--accent);
|
|
98
100
|
--color-muted-foreground: var(--muted-foreground);
|
|
99
101
|
--color-muted: var(--muted);
|
|
102
|
+
--color-table-gap: var(--table-gap);
|
|
100
103
|
--color-secondary-foreground: var(--secondary-foreground);
|
|
101
104
|
--color-secondary: var(--secondary);
|
|
102
105
|
--color-primary-foreground: var(--primary-foreground);
|
|
@@ -5,6 +5,7 @@ type PersistedTableState = {
|
|
|
5
5
|
columnSizing?: Record<string, number>
|
|
6
6
|
columnVisibility?: VisibilityState
|
|
7
7
|
globalFilter?: string
|
|
8
|
+
groupBy?: string[]
|
|
8
9
|
highlightedColumns?: Record<string, boolean>
|
|
9
10
|
sorting?: unknown[]
|
|
10
11
|
}
|
|
@@ -23,7 +24,9 @@ function getTableUrlParam(tableId: string) {
|
|
|
23
24
|
|
|
24
25
|
function isDatoolUrlParam(key: string) {
|
|
25
26
|
return (
|
|
26
|
-
key.startsWith(
|
|
27
|
+
key.startsWith(
|
|
28
|
+
`${DATA_TABLE_URL_PARAM_PREFIX}${LOG_VIEWER_TABLE_ID_PREFIX}`
|
|
29
|
+
) ||
|
|
27
30
|
(key.startsWith(`${LOG_VIEWER_TABLE_ID_PREFIX}-`) && key.endsWith("-search"))
|
|
28
31
|
)
|
|
29
32
|
}
|
|
@@ -57,6 +60,14 @@ function sanitizeColumnVisibility(
|
|
|
57
60
|
)
|
|
58
61
|
}
|
|
59
62
|
|
|
63
|
+
function sanitizeGroupBy(groupBy: string[] | undefined, columnIds: string[]) {
|
|
64
|
+
const validIds = new Set(columnIds)
|
|
65
|
+
|
|
66
|
+
return (groupBy ?? []).filter((columnId, index, values) => {
|
|
67
|
+
return validIds.has(columnId) && values.indexOf(columnId) === index
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
function cleanUpDatoolParams(url: URL, tableId: string) {
|
|
61
72
|
const activeSearchParam = getSearchUrlParam(tableId)
|
|
62
73
|
const activeTableParam = getTableUrlParam(tableId)
|
|
@@ -89,7 +100,10 @@ export function readDatoolSearch(tableId: string) {
|
|
|
89
100
|
return ""
|
|
90
101
|
}
|
|
91
102
|
|
|
92
|
-
return
|
|
103
|
+
return (
|
|
104
|
+
new URL(window.location.href).searchParams.get(getSearchUrlParam(tableId)) ??
|
|
105
|
+
""
|
|
106
|
+
)
|
|
93
107
|
}
|
|
94
108
|
|
|
95
109
|
export function readDatoolColumnVisibility(
|
|
@@ -102,15 +116,21 @@ export function readDatoolColumnVisibility(
|
|
|
102
116
|
)
|
|
103
117
|
}
|
|
104
118
|
|
|
119
|
+
export function readDatoolGrouping(tableId: string, columnIds: string[]) {
|
|
120
|
+
return sanitizeGroupBy(readPersistedTableState(tableId)?.groupBy, columnIds)
|
|
121
|
+
}
|
|
122
|
+
|
|
105
123
|
export function writeDatoolUrlState({
|
|
106
124
|
columnIds,
|
|
107
125
|
columnVisibility,
|
|
126
|
+
groupBy,
|
|
108
127
|
search,
|
|
109
128
|
selectedStreamId,
|
|
110
129
|
tableId,
|
|
111
130
|
}: {
|
|
112
131
|
columnIds: string[]
|
|
113
132
|
columnVisibility: VisibilityState
|
|
133
|
+
groupBy: string[]
|
|
114
134
|
search: string
|
|
115
135
|
selectedStreamId: string | null
|
|
116
136
|
tableId: string
|
|
@@ -125,6 +145,7 @@ export function writeDatoolUrlState({
|
|
|
125
145
|
columnVisibility,
|
|
126
146
|
columnIds
|
|
127
147
|
)
|
|
148
|
+
const nextGroupBy = sanitizeGroupBy(groupBy, columnIds)
|
|
128
149
|
const nextTableState = {
|
|
129
150
|
...readPersistedTableState(tableId),
|
|
130
151
|
} satisfies PersistedTableState
|
|
@@ -149,6 +170,12 @@ export function writeDatoolUrlState({
|
|
|
149
170
|
delete nextTableState.columnVisibility
|
|
150
171
|
}
|
|
151
172
|
|
|
173
|
+
if (nextGroupBy.length > 0) {
|
|
174
|
+
nextTableState.groupBy = nextGroupBy
|
|
175
|
+
} else {
|
|
176
|
+
delete nextTableState.groupBy
|
|
177
|
+
}
|
|
178
|
+
|
|
152
179
|
if (Object.keys(nextTableState).length > 0) {
|
|
153
180
|
url.searchParams.set(getTableUrlParam(tableId), JSON.stringify(nextTableState))
|
|
154
181
|
} else {
|