diff --git a/src/App.js b/src/App.js index 1f3e394..3f17495 100644 --- a/src/App.js +++ b/src/App.js @@ -1130,6 +1130,7 @@ class App extends React.Component {
< and > to quickly navigate selected events
+
Click row to view full log entry and long click to also center map
diff --git a/src/LogTable.js b/src/LogTable.js index 45a7499..ae90a5d 100644 --- a/src/LogTable.js +++ b/src/LogTable.js @@ -1,18 +1,53 @@ // src/LogTable.js import React, { useState } from "react"; -import { useSortBy, useTable } from "react-table"; +import { useSortBy, useTable, useBlockLayout, useResizeColumns } from "react-table"; import { FixedSizeList as List } from "react-window"; import AutoSizer from "react-virtualized-auto-sizer"; import _ from "lodash"; +const CELL_PADDING = 24; + +function getDisplayText(col, entry) { + const raw = typeof col.accessor === "function" ? col.accessor(entry) : _.get(entry, col.accessor); + if (raw === undefined || raw === null) return ""; + const str = typeof raw === "object" ? JSON.stringify(raw) : String(raw); + if (col.trim) return str.replace(col.trim, ""); + return str; +} + +function computeColumnWidths(columns, data) { + const sampleSize = Math.min(data.length, 200); + + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + context.font = "16px Times-Roman"; + + columns.forEach((col) => { + const headerStr = col.Header || ""; + let maxWidthPx = context.measureText(headerStr).width; + + for (let i = 0; i < sampleSize; i++) { + const text = getDisplayText(col, data[i]); + if (text) { + const textWidth = context.measureText(text).width; + if (textWidth > maxWidthPx) maxWidthPx = textWidth; + } + } + const fitted = Math.ceil(maxWidthPx + CELL_PADDING); + col.width = Math.min(fitted, col.width); + }); +} + function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerOnLocation }) { - const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable( + const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, totalColumnsWidth } = useTable( { columns, data, autoResetSortBy: false, }, - useSortBy + useBlockLayout, + useSortBy, + useResizeColumns ); const handleRowSelection = React.useCallback( @@ -94,7 +129,7 @@ function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerO key={key} {...restCellProps} className={`logtable-cell ${cell.column.className || ""}`} - style={{ width: cell.column.width }} + style={restCellProps.style} > {cell.render("Cell")} @@ -103,7 +138,7 @@ function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerO ); }, - [prepareRow, rows, selectedRow, handleRowSelection, centerOnLocation] + [prepareRow, rows, selectedRow, handleRowSelection, centerOnLocation, totalColumnsWidth] ); return ( @@ -116,8 +151,8 @@ function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerO > {({ height, width }) => ( -
-
+
+
{headerGroups.map((headerGroup) => { const { key, ...restHeaderGroupProps } = headerGroup.getHeaderGroupProps(); @@ -130,9 +165,14 @@ function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerO key={key} {...restColumnProps} className={`logtable-header-cell ${column.className || ""}`} - style={{ width: column.width }} + style={{ ...restColumnProps.style, position: "relative" }} > {column.render("Header")} +
e.stopPropagation()} + />
); })} @@ -143,11 +183,13 @@ function Table({ columns, data, onSelectionChange, listRef, selectedRow, centerO
width ? totalColumnsWidth : width} overscanCount={10} + itemData={totalColumnsWidth} + style={{ overflowX: "hidden" }} > {Row} @@ -168,9 +210,9 @@ function LogTable(props) { const data = React.useMemo(() => { return props.logData.tripLogs.getLogs_(new Date(minTime), new Date(maxTime), props.filters).value(); }, [props.logData.tripLogs, minTime, maxTime, props.filters]); - const columnShortWidth = 50; - const columnRegularWidth = 120; - const columnLargeWidth = 152; + const columnShortWidth = 100; + const columnRegularWidth = 130; + const columnLargeWidth = 190; const columns = React.useMemo(() => { const stdColumns = _.filter( [ @@ -185,7 +227,8 @@ function LogTable(props) { { Header: "Method", accessor: "@type", - Cell: ({ cell: { value } }) => , + Cell: TrimCellRenderer, + trim: "type.googleapis.com/maps.fleetengine.", width: columnRegularWidth, className: "logtable-cell", solutionTypes: ["ODRD", "LMFS"], @@ -199,7 +242,6 @@ function LogTable(props) { } }, width: columnShortWidth, - maxWidth: columnShortWidth, className: "logtable-cell short-column", solutionTypes: ["ODRD", "LMFS"], }, @@ -207,9 +249,9 @@ function LogTable(props) { Header: "Sensor", accessor: "lastlocation.rawlocationsensor", id: "lastlocation_rawlocationsensor", - Cell: ({ cell: { value } }) => , + Cell: TrimCellRenderer, + trim: "LOCATION_SENSOR_", width: columnShortWidth, - maxWidth: columnShortWidth, className: "logtable-cell", solutionTypes: ["ODRD", "LMFS"], }, @@ -217,7 +259,8 @@ function LogTable(props) { Header: "Location", accessor: "lastlocation.locationsensor", id: "lastlocation_locationsensor", - Cell: ({ cell: { value } }) => , + Cell: TrimCellRenderer, + trim: "_LOCATION_PROVIDER", width: columnRegularWidth, className: "logtable-cell", solutionTypes: ["ODRD", "LMFS"], @@ -232,8 +275,7 @@ function LogTable(props) { } return null; }, - width: 90, - maxWidth: 90, + width: columnRegularWidth, className: "logtable-cell", solutionTypes: ["ODRD"], }, @@ -241,7 +283,8 @@ function LogTable(props) { Header: "Vehicle State", accessor: "response.vehiclestate", id: "response_vehiclestate", - Cell: ({ cell: { value } }) => , + Cell: TrimCellRenderer, + trim: "VEHICLE_STATE_", width: columnRegularWidth, className: "logtable-cell", solutionTypes: ["ODRD"], @@ -249,7 +292,8 @@ function LogTable(props) { { Header: "Task State", accessor: "response.state", - Cell: ({ cell: { value } }) => , + Cell: TrimCellRenderer, + trim: "TASK_STATE_", width: columnRegularWidth, className: "logtable-cell", solutionTypes: ["LMFS"], @@ -258,7 +302,8 @@ function LogTable(props) { Header: "Trip Status", accessor: "response.tripstatus", id: "response_tripstatus", - Cell: ({ cell: { value } }) => , + Cell: TrimCellRenderer, + trim: "TRIP_STATUS_", width: columnLargeWidth, className: "logtable-cell", solutionTypes: ["ODRD"], @@ -290,7 +335,8 @@ function LogTable(props) { { Header: "Nav Status", accessor: "navStatus", - Cell: ({ cell: { value } }) => , + Cell: TrimCellRenderer, + trim: "NAVIGATION_STATUS_", width: columnLargeWidth, className: "logtable-cell", solutionTypes: ["ODRD", "LMFS"], @@ -315,14 +361,9 @@ function LogTable(props) { }, }); }); - const headers = [ - { - Header: "Event Logs Table (click row to view full log entry and long click to also center map)", - columns: stdColumns, - }, - ]; - return headers; - }, [props.extraColumns, props.logData.solutionType]); + computeColumnWidths(stdColumns, data); + return stdColumns; + }, [props.extraColumns, props.logData.solutionType, data]); const handleRowSelection = React.useCallback( (rowIndex, rowData) => { @@ -365,8 +406,9 @@ function LogTable(props) { ); } -// Helper method for removing common substrings in cells -const TrimCell = ({ value, trim }) => { +const TrimCellRenderer = ({ cell }) => { + const { value } = cell; + const trim = cell.column.trim; return <>{value && value.replace(trim, "")}; }; diff --git a/src/global.css b/src/global.css index fe6e34d..1822461 100644 --- a/src/global.css +++ b/src/global.css @@ -37,21 +37,31 @@ .logtable-header-cell, .logtable-cell { flex: 0 0 auto; - padding: 8px; + padding: 8px 8px 4px 4px; overflow: hidden; - text-overflow: ellipsis; white-space: nowrap; display: flex; align-items: center; justify-content: flex-start; + box-sizing: border-box; +} + +.logtable-header-cell, +.logtable-header-cell.logtable-cell { + border-right: 1px solid rgba(0, 0, 0, 0.3); + text-overflow: clip; +} + +.logtable-cell { + text-overflow: clip; } .logtable-header-row { - border-bottom: 1px solid black; + border-bottom: none; } .logtable-row { - border-bottom: 1px solid #eee; + border: none; } .logtable-row.selected { @@ -66,7 +76,15 @@ .logtable-header-cell>*, .logtable-cell>* { overflow: hidden; - text-overflow: ellipsis; +} + +.logtable-header-cell>*, +.logtable-header-cell.logtable-cell>* { + text-overflow: clip; +} + +.logtable-cell>* { + text-overflow: clip; } /* Event indicator styles */ @@ -584,4 +602,22 @@ width: 1px; height: 100%; background-color: rgba(0, 0, 0, 0.15); -} \ No newline at end of file +} +/* Resizer styles for react-table */ +.resizer { + display: inline-block; + background: transparent; + width: 5px; + height: 100%; + position: absolute; + right: 0; + top: 0; + transform: translateX(50%); + z-index: 1; + touch-action: none; + cursor: col-resize; +} + +.resizer:hover, .resizer.isResizing { + background: rgba(0, 0, 0, 0.5); +}