11import * as React from "react" ;
22import {
3- ResultTableProps ,
43 className ,
54 emptyQueryResultsMessage ,
65 jumpToLocation ,
@@ -19,158 +18,157 @@ import { onNavigation } from "./results";
1918import { tryGetResolvableLocation } from "../../common/bqrs-utils" ;
2019import { ScrollIntoViewHelper } from "./scroll-into-view-helper" ;
2120import { sendTelemetry } from "../common/telemetry" ;
21+ import { assertNever } from "../../common/helpers-pure" ;
2222
23- export type RawTableProps = ResultTableProps & {
23+ export type RawTableProps = {
24+ databaseUri : string ;
2425 resultSet : RawTableResultSet ;
2526 sortState ?: RawResultsSortState ;
2627 offset : number ;
2728} ;
2829
29- interface RawTableState {
30- selectedItem ?: { row : number ; column : number } ;
30+ interface TableItem {
31+ readonly row : number ;
32+ readonly column : number ;
3133}
3234
33- export class RawTable extends React . Component < RawTableProps , RawTableState > {
34- private scroller = new ScrollIntoViewHelper ( ) ;
35-
36- constructor ( props : RawTableProps ) {
37- super ( props ) ;
38- this . setSelection = this . setSelection . bind ( this ) ;
39- this . handleNavigationEvent = this . handleNavigationEvent . bind ( this ) ;
40- this . state = { } ;
41- }
42-
43- private setSelection ( row : number , column : number ) {
44- this . setState ( ( prev ) => ( {
45- ...prev ,
46- selectedItem : { row, column } ,
47- } ) ) ;
48- sendTelemetry ( "local-results-raw-results-table-selected" ) ;
49- }
50-
51- render ( ) : React . ReactNode {
52- const { resultSet, databaseUri } = this . props ;
53-
54- let dataRows = resultSet . rows ;
55- if ( dataRows . length === 0 ) {
56- return emptyQueryResultsMessage ( ) ;
57- }
58-
59- let numTruncatedResults = 0 ;
60- if ( dataRows . length > RAW_RESULTS_LIMIT ) {
61- numTruncatedResults = dataRows . length - RAW_RESULTS_LIMIT ;
62- dataRows = dataRows . slice ( 0 , RAW_RESULTS_LIMIT ) ;
63- }
64-
65- const tableRows = dataRows . map ( ( row : ResultRow , rowIndex : number ) => (
66- < RawTableRow
67- key = { rowIndex }
68- rowIndex = { rowIndex + this . props . offset }
69- row = { row }
70- databaseUri = { databaseUri }
71- selectedColumn = {
72- this . state . selectedItem ?. row === rowIndex
73- ? this . state . selectedItem ?. column
74- : undefined
35+ export function RawTable ( {
36+ databaseUri,
37+ resultSet,
38+ sortState,
39+ offset,
40+ } : RawTableProps ) {
41+ const [ selectedItem , setSelectedItem ] = React . useState <
42+ TableItem | undefined
43+ > ( ) ;
44+
45+ const scroller = React . useMemo ( ( ) => new ScrollIntoViewHelper ( ) , [ ] ) ;
46+ scroller . update ( ) ;
47+
48+ const setSelection = React . useCallback (
49+ ( row : number , column : number ) : void => {
50+ setSelectedItem ( { row, column } ) ;
51+ sendTelemetry ( "local-results-raw-results-table-selected" ) ;
52+ } ,
53+ [ ] ,
54+ ) ;
55+
56+ const navigateWithDelta = React . useCallback (
57+ ( rowDelta : number , columnDelta : number ) : void => {
58+ setSelectedItem ( ( prevSelectedItem ) => {
59+ const numberOfAlerts = resultSet . rows . length ;
60+ if ( numberOfAlerts === 0 ) {
61+ return prevSelectedItem ;
7562 }
76- onSelected = { this . setSelection }
77- scroller = { this . scroller }
78- />
79- ) ) ;
80-
81- if ( numTruncatedResults > 0 ) {
82- const colSpan = dataRows [ 0 ] . length + 1 ; // one row for each data column, plus index column
83- tableRows . push (
84- < tr >
85- < td
86- key = { "message" }
87- colSpan = { colSpan }
88- style = { { textAlign : "center" , fontStyle : "italic" } }
89- >
90- Too many results to show at once. { numTruncatedResults } result(s)
91- omitted.
92- </ td >
93- </ tr > ,
94- ) ;
95- }
96-
97- return (
98- < table className = { className } >
99- < RawTableHeader
100- columns = { resultSet . schema . columns }
101- schemaName = { resultSet . schema . name }
102- sortState = { this . props . sortState }
103- />
104- < tbody > { tableRows } </ tbody >
105- </ table >
106- ) ;
107- }
108-
109- private handleNavigationEvent ( event : NavigateMsg ) {
110- switch ( event . direction ) {
111- case NavigationDirection . up : {
112- this . navigateWithDelta ( - 1 , 0 ) ;
113- break ;
114- }
115- case NavigationDirection . down : {
116- this . navigateWithDelta ( 1 , 0 ) ;
117- break ;
118- }
119- case NavigationDirection . left : {
120- this . navigateWithDelta ( 0 , - 1 ) ;
121- break ;
122- }
123- case NavigationDirection . right : {
124- this . navigateWithDelta ( 0 , 1 ) ;
125- break ;
126- }
127- }
128- }
129-
130- private navigateWithDelta ( rowDelta : number , columnDelta : number ) {
131- this . setState ( ( prevState ) => {
132- const numberOfAlerts = this . props . resultSet . rows . length ;
133- if ( numberOfAlerts === 0 ) {
134- return prevState ;
135- }
136- const currentRow = prevState . selectedItem ?. row ;
137- const nextRow = currentRow === undefined ? 0 : currentRow + rowDelta ;
138- if ( nextRow < 0 || nextRow >= numberOfAlerts ) {
139- return prevState ;
140- }
141- const currentColumn = prevState . selectedItem ?. column ;
142- const nextColumn =
143- currentColumn === undefined ? 0 : currentColumn + columnDelta ;
144- // Jump to the location of the new cell
145- const rowData = this . props . resultSet . rows [ nextRow ] ;
146- if ( nextColumn < 0 || nextColumn >= rowData . length ) {
147- return prevState ;
148- }
149- const cellData = rowData [ nextColumn ] ;
150- if ( cellData != null && typeof cellData === "object" ) {
151- const location = tryGetResolvableLocation ( cellData . url ) ;
152- if ( location !== undefined ) {
153- jumpToLocation ( location , this . props . databaseUri ) ;
63+ const currentRow = prevSelectedItem ?. row ;
64+ const nextRow = currentRow === undefined ? 0 : currentRow + rowDelta ;
65+ if ( nextRow < 0 || nextRow >= numberOfAlerts ) {
66+ return prevSelectedItem ;
67+ }
68+ const currentColumn = prevSelectedItem ?. column ;
69+ const nextColumn =
70+ currentColumn === undefined ? 0 : currentColumn + columnDelta ;
71+ // Jump to the location of the new cell
72+ const rowData = resultSet . rows [ nextRow ] ;
73+ if ( nextColumn < 0 || nextColumn >= rowData . length ) {
74+ return prevSelectedItem ;
75+ }
76+ const cellData = rowData [ nextColumn ] ;
77+ if ( cellData != null && typeof cellData === "object" ) {
78+ const location = tryGetResolvableLocation ( cellData . url ) ;
79+ if ( location !== undefined ) {
80+ jumpToLocation ( location , databaseUri ) ;
81+ }
15482 }
83+ scroller . scrollIntoViewOnNextUpdate ( ) ;
84+ return { row : nextRow , column : nextColumn } ;
85+ } ) ;
86+ } ,
87+ [ databaseUri , resultSet , scroller ] ,
88+ ) ;
89+
90+ const handleNavigationEvent = React . useCallback (
91+ ( event : NavigateMsg ) => {
92+ switch ( event . direction ) {
93+ case NavigationDirection . up : {
94+ navigateWithDelta ( - 1 , 0 ) ;
95+ break ;
96+ }
97+ case NavigationDirection . down : {
98+ navigateWithDelta ( 1 , 0 ) ;
99+ break ;
100+ }
101+ case NavigationDirection . left : {
102+ navigateWithDelta ( 0 , - 1 ) ;
103+ break ;
104+ }
105+ case NavigationDirection . right : {
106+ navigateWithDelta ( 0 , 1 ) ;
107+ break ;
108+ }
109+ default :
110+ assertNever ( event . direction ) ;
155111 }
156- this . scroller . scrollIntoViewOnNextUpdate ( ) ;
157- return {
158- ...prevState ,
159- selectedItem : { row : nextRow , column : nextColumn } ,
160- } ;
161- } ) ;
112+ } ,
113+ [ navigateWithDelta ] ,
114+ ) ;
115+
116+ React . useEffect ( ( ) => {
117+ onNavigation . addListener ( handleNavigationEvent ) ;
118+ return ( ) => {
119+ onNavigation . removeListener ( handleNavigationEvent ) ;
120+ } ;
121+ } , [ handleNavigationEvent ] ) ;
122+
123+ let dataRows = resultSet . rows ;
124+ if ( dataRows . length === 0 ) {
125+ return emptyQueryResultsMessage ( ) ;
162126 }
163127
164- componentDidUpdate ( ) {
165- this . scroller . update ( ) ;
128+ let numTruncatedResults = 0 ;
129+ if ( dataRows . length > RAW_RESULTS_LIMIT ) {
130+ numTruncatedResults = dataRows . length - RAW_RESULTS_LIMIT ;
131+ dataRows = dataRows . slice ( 0 , RAW_RESULTS_LIMIT ) ;
166132 }
167133
168- componentDidMount ( ) {
169- this . scroller . update ( ) ;
170- onNavigation . addListener ( this . handleNavigationEvent ) ;
134+ const tableRows = dataRows . map ( ( row : ResultRow , rowIndex : number ) => (
135+ < RawTableRow
136+ key = { rowIndex }
137+ rowIndex = { rowIndex + offset }
138+ row = { row }
139+ databaseUri = { databaseUri }
140+ selectedColumn = {
141+ selectedItem ?. row === rowIndex ? selectedItem ?. column : undefined
142+ }
143+ onSelected = { setSelection }
144+ scroller = { scroller }
145+ />
146+ ) ) ;
147+
148+ if ( numTruncatedResults > 0 ) {
149+ const colSpan = dataRows [ 0 ] . length + 1 ; // one row for each data column, plus index column
150+ tableRows . push (
151+ < tr >
152+ < td
153+ key = { "message" }
154+ colSpan = { colSpan }
155+ style = { { textAlign : "center" , fontStyle : "italic" } }
156+ >
157+ Too many results to show at once. { numTruncatedResults } result(s)
158+ omitted.
159+ </ td >
160+ </ tr > ,
161+ ) ;
171162 }
172163
173- componentWillUnmount ( ) {
174- onNavigation . removeListener ( this . handleNavigationEvent ) ;
175- }
164+ return (
165+ < table className = { className } >
166+ < RawTableHeader
167+ columns = { resultSet . schema . columns }
168+ schemaName = { resultSet . schema . name }
169+ sortState = { sortState }
170+ />
171+ < tbody > { tableRows } </ tbody >
172+ </ table >
173+ ) ;
176174}
0 commit comments