Skip to content

Commit a9f0e03

Browse files
feat: resizeable table columns (#291) 4080543
1 parent 3f8f9c2 commit a9f0e03

File tree

3 files changed

+120
-41
lines changed

3 files changed

+120
-41
lines changed

src/App.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,7 @@ class App extends React.Component {
11301130
<div>
11311131
<strong>&lt;</strong> and <strong>&gt;</strong> to quickly navigate selected events
11321132
</div>
1133+
<div>Click row to view full log entry and long click to also center map</div>
11331134
</div>
11341135
</div>
11351136
</div>

src/LogTable.js

Lines changed: 77 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,53 @@
11
// src/LogTable.js
22
import React, { useState } from "react";
3-
import { useSortBy, useTable } from "react-table";
3+
import { useSortBy, useTable, useBlockLayout, useResizeColumns } from "react-table";
44
import { FixedSizeList as List } from "react-window";
55
import AutoSizer from "react-virtualized-auto-sizer";
66
import _ from "lodash";
77

8+
const CELL_PADDING = 24;
9+
10+
function getDisplayText(col, entry) {
11+
const raw = typeof col.accessor === "function" ? col.accessor(entry) : _.get(entry, col.accessor);
12+
if (raw === undefined || raw === null) return "";
13+
const str = typeof raw === "object" ? JSON.stringify(raw) : String(raw);
14+
if (col.trim) return str.replace(col.trim, "");
15+
return str;
16+
}
17+
18+
function computeColumnWidths(columns, data) {
19+
const sampleSize = Math.min(data.length, 200);
20+
21+
const canvas = document.createElement("canvas");
22+
const context = canvas.getContext("2d");
23+
context.font = "16px Times-Roman";
24+
25+
columns.forEach((col) => {
26+
const headerStr = col.Header || "";
27+
let maxWidthPx = context.measureText(headerStr).width;
28+
29+
for (let i = 0; i < sampleSize; i++) {
30+
const text = getDisplayText(col, data[i]);
31+
if (text) {
32+
const textWidth = context.measureText(text).width;
33+
if (textWidth > maxWidthPx) maxWidthPx = textWidth;
34+
}
35+
}
36+
const fitted = Math.ceil(maxWidthPx + CELL_PADDING);
37+
col.width = Math.min(fitted, col.width);
38+
});
39+
}
40+
841
function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerOnLocation }) {
9-
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable(
42+
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, totalColumnsWidth } = useTable(
1043
{
1144
columns,
1245
data,
1346
autoResetSortBy: false,
1447
},
15-
useSortBy
48+
useBlockLayout,
49+
useSortBy,
50+
useResizeColumns
1651
);
1752

1853
const handleRowSelection = React.useCallback(
@@ -94,7 +129,7 @@ function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerO
94129
key={key}
95130
{...restCellProps}
96131
className={`logtable-cell ${cell.column.className || ""}`}
97-
style={{ width: cell.column.width }}
132+
style={restCellProps.style}
98133
>
99134
{cell.render("Cell")}
100135
</div>
@@ -103,7 +138,7 @@ function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerO
103138
</div>
104139
);
105140
},
106-
[prepareRow, rows, selectedRow, handleRowSelection, centerOnLocation]
141+
[prepareRow, rows, selectedRow, handleRowSelection, centerOnLocation, totalColumnsWidth]
107142
);
108143

109144
return (
@@ -116,8 +151,8 @@ function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerO
116151
>
117152
<AutoSizer>
118153
{({ height, width }) => (
119-
<div>
120-
<div {...getTableProps()}>
154+
<div style={{ width, overflowX: "auto", overflowY: "hidden" }}>
155+
<div {...getTableProps()} style={{ minWidth: "100%", width: totalColumnsWidth }}>
121156
<div>
122157
{headerGroups.map((headerGroup) => {
123158
const { key, ...restHeaderGroupProps } = headerGroup.getHeaderGroupProps();
@@ -130,9 +165,14 @@ function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerO
130165
key={key}
131166
{...restColumnProps}
132167
className={`logtable-header-cell ${column.className || ""}`}
133-
style={{ width: column.width }}
168+
style={{ ...restColumnProps.style, position: "relative" }}
134169
>
135170
{column.render("Header")}
171+
<div
172+
{...column.getResizerProps()}
173+
className={`resizer ${column.isResizing ? "isResizing" : ""}`}
174+
onClick={(e) => e.stopPropagation()}
175+
/>
136176
</div>
137177
);
138178
})}
@@ -143,11 +183,13 @@ function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerO
143183
<div {...getTableBodyProps()}>
144184
<List
145185
ref={listRef}
146-
height={height - 100}
186+
height={height - 54}
147187
itemCount={rows.length}
148-
itemSize={35}
149-
width={width}
188+
itemSize={32}
189+
width={totalColumnsWidth > width ? totalColumnsWidth : width}
150190
overscanCount={10}
191+
itemData={totalColumnsWidth}
192+
style={{ overflowX: "hidden" }}
151193
>
152194
{Row}
153195
</List>
@@ -168,9 +210,9 @@ function LogTable(props) {
168210
const data = React.useMemo(() => {
169211
return props.logData.tripLogs.getLogs_(new Date(minTime), new Date(maxTime), props.filters).value();
170212
}, [props.logData.tripLogs, minTime, maxTime, props.filters]);
171-
const columnShortWidth = 50;
172-
const columnRegularWidth = 120;
173-
const columnLargeWidth = 152;
213+
const columnShortWidth = 100;
214+
const columnRegularWidth = 130;
215+
const columnLargeWidth = 190;
174216
const columns = React.useMemo(() => {
175217
const stdColumns = _.filter(
176218
[
@@ -185,7 +227,8 @@ function LogTable(props) {
185227
{
186228
Header: "Method",
187229
accessor: "@type",
188-
Cell: ({ cell: { value } }) => <TrimCell value={value} trim="type.googleapis.com/maps.fleetengine." />,
230+
Cell: TrimCellRenderer,
231+
trim: "type.googleapis.com/maps.fleetengine.",
189232
width: columnRegularWidth,
190233
className: "logtable-cell",
191234
solutionTypes: ["ODRD", "LMFS"],
@@ -199,25 +242,25 @@ function LogTable(props) {
199242
}
200243
},
201244
width: columnShortWidth,
202-
maxWidth: columnShortWidth,
203245
className: "logtable-cell short-column",
204246
solutionTypes: ["ODRD", "LMFS"],
205247
},
206248
{
207249
Header: "Sensor",
208250
accessor: "lastlocation.rawlocationsensor",
209251
id: "lastlocation_rawlocationsensor",
210-
Cell: ({ cell: { value } }) => <TrimCell value={value} trim="LOCATION_SENSOR_" />,
252+
Cell: TrimCellRenderer,
253+
trim: "LOCATION_SENSOR_",
211254
width: columnShortWidth,
212-
maxWidth: columnShortWidth,
213255
className: "logtable-cell",
214256
solutionTypes: ["ODRD", "LMFS"],
215257
},
216258
{
217259
Header: "Location",
218260
accessor: "lastlocation.locationsensor",
219261
id: "lastlocation_locationsensor",
220-
Cell: ({ cell: { value } }) => <TrimCell value={value} trim="_LOCATION_PROVIDER" />,
262+
Cell: TrimCellRenderer,
263+
trim: "_LOCATION_PROVIDER",
221264
width: columnRegularWidth,
222265
className: "logtable-cell",
223266
solutionTypes: ["ODRD", "LMFS"],
@@ -232,24 +275,25 @@ function LogTable(props) {
232275
}
233276
return null;
234277
},
235-
width: 90,
236-
maxWidth: 90,
278+
width: columnRegularWidth,
237279
className: "logtable-cell",
238280
solutionTypes: ["ODRD"],
239281
},
240282
{
241283
Header: "Vehicle State",
242284
accessor: "response.vehiclestate",
243285
id: "response_vehiclestate",
244-
Cell: ({ cell: { value } }) => <TrimCell value={value} trim="VEHICLE_STATE_" />,
286+
Cell: TrimCellRenderer,
287+
trim: "VEHICLE_STATE_",
245288
width: columnRegularWidth,
246289
className: "logtable-cell",
247290
solutionTypes: ["ODRD"],
248291
},
249292
{
250293
Header: "Task State",
251294
accessor: "response.state",
252-
Cell: ({ cell: { value } }) => <TrimCell value={value} trim="TASK_STATE_" />,
295+
Cell: TrimCellRenderer,
296+
trim: "TASK_STATE_",
253297
width: columnRegularWidth,
254298
className: "logtable-cell",
255299
solutionTypes: ["LMFS"],
@@ -258,7 +302,8 @@ function LogTable(props) {
258302
Header: "Trip Status",
259303
accessor: "response.tripstatus",
260304
id: "response_tripstatus",
261-
Cell: ({ cell: { value } }) => <TrimCell value={value} trim="TRIP_STATUS_" />,
305+
Cell: TrimCellRenderer,
306+
trim: "TRIP_STATUS_",
262307
width: columnLargeWidth,
263308
className: "logtable-cell",
264309
solutionTypes: ["ODRD"],
@@ -290,7 +335,8 @@ function LogTable(props) {
290335
{
291336
Header: "Nav Status",
292337
accessor: "navStatus",
293-
Cell: ({ cell: { value } }) => <TrimCell value={value} trim="NAVIGATION_STATUS_" />,
338+
Cell: TrimCellRenderer,
339+
trim: "NAVIGATION_STATUS_",
294340
width: columnLargeWidth,
295341
className: "logtable-cell",
296342
solutionTypes: ["ODRD", "LMFS"],
@@ -315,14 +361,9 @@ function LogTable(props) {
315361
},
316362
});
317363
});
318-
const headers = [
319-
{
320-
Header: "Event Logs Table (click row to view full log entry and long click to also center map)",
321-
columns: stdColumns,
322-
},
323-
];
324-
return headers;
325-
}, [props.extraColumns, props.logData.solutionType]);
364+
computeColumnWidths(stdColumns, data);
365+
return stdColumns;
366+
}, [props.extraColumns, props.logData.solutionType, data]);
326367

327368
const handleRowSelection = React.useCallback(
328369
(rowIndex, rowData) => {
@@ -365,8 +406,9 @@ function LogTable(props) {
365406
);
366407
}
367408

368-
// Helper method for removing common substrings in cells
369-
const TrimCell = ({ value, trim }) => {
409+
const TrimCellRenderer = ({ cell }) => {
410+
const { value } = cell;
411+
const trim = cell.column.trim;
370412
return <>{value && value.replace(trim, "")}</>;
371413
};
372414

src/global.css

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,31 @@
3737
.logtable-header-cell,
3838
.logtable-cell {
3939
flex: 0 0 auto;
40-
padding: 8px;
40+
padding: 8px 8px 4px 4px;
4141
overflow: hidden;
42-
text-overflow: ellipsis;
4342
white-space: nowrap;
4443
display: flex;
4544
align-items: center;
4645
justify-content: flex-start;
46+
box-sizing: border-box;
47+
}
48+
49+
.logtable-header-cell,
50+
.logtable-header-cell.logtable-cell {
51+
border-right: 1px solid rgba(0, 0, 0, 0.3);
52+
text-overflow: clip;
53+
}
54+
55+
.logtable-cell {
56+
text-overflow: clip;
4757
}
4858

4959
.logtable-header-row {
50-
border-bottom: 1px solid black;
60+
border-bottom: none;
5161
}
5262

5363
.logtable-row {
54-
border-bottom: 1px solid #eee;
64+
border: none;
5565
}
5666

5767
.logtable-row.selected {
@@ -66,7 +76,15 @@
6676
.logtable-header-cell>*,
6777
.logtable-cell>* {
6878
overflow: hidden;
69-
text-overflow: ellipsis;
79+
}
80+
81+
.logtable-header-cell>*,
82+
.logtable-header-cell.logtable-cell>* {
83+
text-overflow: clip;
84+
}
85+
86+
.logtable-cell>* {
87+
text-overflow: clip;
7088
}
7189

7290
/* Event indicator styles */
@@ -584,4 +602,22 @@
584602
width: 1px;
585603
height: 100%;
586604
background-color: rgba(0, 0, 0, 0.15);
587-
}
605+
}
606+
/* Resizer styles for react-table */
607+
.resizer {
608+
display: inline-block;
609+
background: transparent;
610+
width: 5px;
611+
height: 100%;
612+
position: absolute;
613+
right: 0;
614+
top: 0;
615+
transform: translateX(50%);
616+
z-index: 1;
617+
touch-action: none;
618+
cursor: col-resize;
619+
}
620+
621+
.resizer:hover, .resizer.isResizing {
622+
background: rgba(0, 0, 0, 0.5);
623+
}

0 commit comments

Comments
 (0)