diff --git a/extensions/ql-vscode/src/view/results/raw-results-table.tsx b/extensions/ql-vscode/src/view/results/raw-results-table.tsx index f9ac5949a..923d1a0ab 100644 --- a/extensions/ql-vscode/src/view/results/raw-results-table.tsx +++ b/extensions/ql-vscode/src/view/results/raw-results-table.tsx @@ -1,6 +1,5 @@ import * as React from "react"; import { - ResultTableProps, className, emptyQueryResultsMessage, jumpToLocation, @@ -19,158 +18,157 @@ import { onNavigation } from "./results"; import { tryGetResolvableLocation } from "../../common/bqrs-utils"; import { ScrollIntoViewHelper } from "./scroll-into-view-helper"; import { sendTelemetry } from "../common/telemetry"; +import { assertNever } from "../../common/helpers-pure"; -export type RawTableProps = ResultTableProps & { +export type RawTableProps = { + databaseUri: string; resultSet: RawTableResultSet; sortState?: RawResultsSortState; offset: number; }; -interface RawTableState { - selectedItem?: { row: number; column: number }; +interface TableItem { + readonly row: number; + readonly column: number; } -export class RawTable extends React.Component { - private scroller = new ScrollIntoViewHelper(); +export function RawTable({ + databaseUri, + resultSet, + sortState, + offset, +}: RawTableProps) { + const [selectedItem, setSelectedItem] = React.useState< + TableItem | undefined + >(); - constructor(props: RawTableProps) { - super(props); - this.setSelection = this.setSelection.bind(this); - this.handleNavigationEvent = this.handleNavigationEvent.bind(this); - this.state = {}; - } + const scroller = React.useMemo(() => new ScrollIntoViewHelper(), []); + scroller.update(); - private setSelection(row: number, column: number) { - this.setState((prev) => ({ - ...prev, - selectedItem: { row, column }, - })); - sendTelemetry("local-results-raw-results-table-selected"); - } + const setSelection = React.useCallback( + (row: number, column: number): void => { + setSelectedItem({ row, column }); + sendTelemetry("local-results-raw-results-table-selected"); + }, + [], + ); - render(): React.ReactNode { - const { resultSet, databaseUri } = this.props; - - let dataRows = resultSet.rows; - if (dataRows.length === 0) { - return emptyQueryResultsMessage(); - } - - let numTruncatedResults = 0; - if (dataRows.length > RAW_RESULTS_LIMIT) { - numTruncatedResults = dataRows.length - RAW_RESULTS_LIMIT; - dataRows = dataRows.slice(0, RAW_RESULTS_LIMIT); - } - - const tableRows = dataRows.map((row: ResultRow, rowIndex: number) => ( - { + setSelectedItem((prevSelectedItem) => { + const numberOfAlerts = resultSet.rows.length; + if (numberOfAlerts === 0) { + return prevSelectedItem; } - onSelected={this.setSelection} - scroller={this.scroller} - /> - )); + const currentRow = prevSelectedItem?.row; + const nextRow = currentRow === undefined ? 0 : currentRow + rowDelta; + if (nextRow < 0 || nextRow >= numberOfAlerts) { + return prevSelectedItem; + } + const currentColumn = prevSelectedItem?.column; + const nextColumn = + currentColumn === undefined ? 0 : currentColumn + columnDelta; + // Jump to the location of the new cell + const rowData = resultSet.rows[nextRow]; + if (nextColumn < 0 || nextColumn >= rowData.length) { + return prevSelectedItem; + } + const cellData = rowData[nextColumn]; + if (cellData != null && typeof cellData === "object") { + const location = tryGetResolvableLocation(cellData.url); + if (location !== undefined) { + jumpToLocation(location, databaseUri); + } + } + scroller.scrollIntoViewOnNextUpdate(); + return { row: nextRow, column: nextColumn }; + }); + }, + [databaseUri, resultSet, scroller], + ); - if (numTruncatedResults > 0) { - const colSpan = dataRows[0].length + 1; // one row for each data column, plus index column - tableRows.push( - - - Too many results to show at once. {numTruncatedResults} result(s) - omitted. - - , - ); - } + const handleNavigationEvent = React.useCallback( + (event: NavigateMsg) => { + switch (event.direction) { + case NavigationDirection.up: { + navigateWithDelta(-1, 0); + break; + } + case NavigationDirection.down: { + navigateWithDelta(1, 0); + break; + } + case NavigationDirection.left: { + navigateWithDelta(0, -1); + break; + } + case NavigationDirection.right: { + navigateWithDelta(0, 1); + break; + } + default: + assertNever(event.direction); + } + }, + [navigateWithDelta], + ); - return ( - - - {tableRows} -
+ React.useEffect(() => { + onNavigation.addListener(handleNavigationEvent); + return () => { + onNavigation.removeListener(handleNavigationEvent); + }; + }, [handleNavigationEvent]); + + let dataRows = resultSet.rows; + if (dataRows.length === 0) { + return emptyQueryResultsMessage(); + } + + let numTruncatedResults = 0; + if (dataRows.length > RAW_RESULTS_LIMIT) { + numTruncatedResults = dataRows.length - RAW_RESULTS_LIMIT; + dataRows = dataRows.slice(0, RAW_RESULTS_LIMIT); + } + + const tableRows = dataRows.map((row: ResultRow, rowIndex: number) => ( + + )); + + if (numTruncatedResults > 0) { + const colSpan = dataRows[0].length + 1; // one row for each data column, plus index column + tableRows.push( + + + Too many results to show at once. {numTruncatedResults} result(s) + omitted. + + , ); } - private handleNavigationEvent(event: NavigateMsg) { - switch (event.direction) { - case NavigationDirection.up: { - this.navigateWithDelta(-1, 0); - break; - } - case NavigationDirection.down: { - this.navigateWithDelta(1, 0); - break; - } - case NavigationDirection.left: { - this.navigateWithDelta(0, -1); - break; - } - case NavigationDirection.right: { - this.navigateWithDelta(0, 1); - break; - } - } - } - - private navigateWithDelta(rowDelta: number, columnDelta: number) { - this.setState((prevState) => { - const numberOfAlerts = this.props.resultSet.rows.length; - if (numberOfAlerts === 0) { - return prevState; - } - const currentRow = prevState.selectedItem?.row; - const nextRow = currentRow === undefined ? 0 : currentRow + rowDelta; - if (nextRow < 0 || nextRow >= numberOfAlerts) { - return prevState; - } - const currentColumn = prevState.selectedItem?.column; - const nextColumn = - currentColumn === undefined ? 0 : currentColumn + columnDelta; - // Jump to the location of the new cell - const rowData = this.props.resultSet.rows[nextRow]; - if (nextColumn < 0 || nextColumn >= rowData.length) { - return prevState; - } - const cellData = rowData[nextColumn]; - if (cellData != null && typeof cellData === "object") { - const location = tryGetResolvableLocation(cellData.url); - if (location !== undefined) { - jumpToLocation(location, this.props.databaseUri); - } - } - this.scroller.scrollIntoViewOnNextUpdate(); - return { - ...prevState, - selectedItem: { row: nextRow, column: nextColumn }, - }; - }); - } - - componentDidUpdate() { - this.scroller.update(); - } - - componentDidMount() { - this.scroller.update(); - onNavigation.addListener(this.handleNavigationEvent); - } - - componentWillUnmount() { - onNavigation.removeListener(this.handleNavigationEvent); - } + return ( + + + {tableRows} +
+ ); }