@@ -20,37 +20,36 @@ import { AlertTableHeader } from "./AlertTableHeader";
2020import { AlertTableNoResults } from "./AlertTableNoResults" ;
2121import { AlertTableTruncatedMessage } from "./AlertTableTruncatedMessage" ;
2222import { AlertTableResultRow } from "./AlertTableResultRow" ;
23+ import { useCallback , useEffect , useRef , useState } from "react" ;
2324
2425type AlertTableProps = ResultTableProps & {
2526 resultSet : InterpretedResultSet < SarifInterpretationData > ;
2627} ;
27- interface AlertTableState {
28- expanded : Set < string > ;
29- selectedItem : undefined | Keys . ResultKey ;
30- }
3128
32- export class AlertTable extends React . Component <
33- AlertTableProps ,
34- AlertTableState
35- > {
36- private scroller = new ScrollIntoViewHelper ( ) ;
29+ export function AlertTable ( props : AlertTableProps ) {
30+ const { databaseUri, resultSet } = props ;
3731
38- constructor ( props : AlertTableProps ) {
39- super ( props ) ;
40- this . state = { expanded : new Set < string > ( ) , selectedItem : undefined } ;
41- this . handleNavigationEvent = this . handleNavigationEvent . bind ( this ) ;
32+ const scroller = useRef < ScrollIntoViewHelper | undefined > ( undefined ) ;
33+ if ( scroller . current === undefined ) {
34+ scroller . current = new ScrollIntoViewHelper ( ) ;
4235 }
36+ useEffect ( ( ) => scroller . current ?. update ( ) ) ;
37+
38+ const [ expanded , setExpanded ] = useState < Set < string > > ( new Set < string > ( ) ) ;
39+ const [ selectedItem , setSelectedItem ] = useState < Keys . ResultKey | undefined > (
40+ undefined ,
41+ ) ;
4342
4443 /**
4544 * Given a list of `keys`, toggle the first, and if we 'open' the
4645 * first item, open all the rest as well. This mimics vscode's file
4746 * explorer tree view behavior.
4847 */
49- toggle ( e : React . MouseEvent , keys : Keys . ResultKey [ ] ) {
48+ const toggle = useCallback ( ( e : React . MouseEvent , keys : Keys . ResultKey [ ] ) => {
5049 const keyStrings = keys . map ( Keys . keyToString ) ;
51- this . setState ( ( previousState ) => {
52- const expanded = new Set ( previousState . expanded ) ;
53- if ( previousState . expanded . has ( keyStrings [ 0 ] ) ) {
50+ setExpanded ( ( previousExpanded ) => {
51+ const expanded = new Set ( previousExpanded ) ;
52+ if ( previousExpanded . has ( keyStrings [ 0 ] ) ) {
5453 expanded . delete ( keyStrings [ 0 ] ) ;
5554 } else {
5655 for ( const str of keyStrings ) {
@@ -60,99 +59,94 @@ export class AlertTable extends React.Component<
6059 if ( expanded ) {
6160 sendTelemetry ( "local-results-alert-table-path-expanded" ) ;
6261 }
63- return { expanded } ;
62+ return expanded ;
6463 } ) ;
6564 e . stopPropagation ( ) ;
6665 e . preventDefault ( ) ;
67- }
68-
69- render ( ) : JSX . Element {
70- const { databaseUri, resultSet } = this . props ;
71-
72- const { numTruncatedResults, sourceLocationPrefix } =
73- resultSet . interpretation ;
74-
75- const updateSelectionCallback = (
76- resultKey : Keys . PathNode | Keys . Result | undefined ,
77- ) => {
78- this . setState ( ( previousState ) => ( {
79- ...previousState ,
80- selectedItem : resultKey ,
81- } ) ) ;
82- sendTelemetry ( "local-results-alert-table-path-selected" ) ;
83- } ;
66+ } , [ ] ) ;
8467
85- if ( ! resultSet . interpretation . data . runs ?. [ 0 ] ?. results ?. length ) {
86- return < AlertTableNoResults { ...this . props } /> ;
68+ const getNewSelection = (
69+ key : Keys . ResultKey | undefined ,
70+ direction : NavigationDirection ,
71+ ) : Keys . ResultKey => {
72+ if ( key === undefined ) {
73+ return { resultIndex : 0 } ;
8774 }
75+ const { resultIndex, pathIndex, pathNodeIndex } = key ;
76+ switch ( direction ) {
77+ case NavigationDirection . up :
78+ case NavigationDirection . down : {
79+ const delta = direction === NavigationDirection . up ? - 1 : 1 ;
80+ if ( key . pathNodeIndex !== undefined ) {
81+ return {
82+ resultIndex,
83+ pathIndex : key . pathIndex ,
84+ pathNodeIndex : key . pathNodeIndex + delta ,
85+ } ;
86+ } else if ( pathIndex !== undefined ) {
87+ return { resultIndex, pathIndex : pathIndex + delta } ;
88+ } else {
89+ return { resultIndex : resultIndex + delta } ;
90+ }
91+ }
92+ case NavigationDirection . left :
93+ if ( key . pathNodeIndex !== undefined ) {
94+ return { resultIndex, pathIndex : key . pathIndex } ;
95+ } else if ( pathIndex !== undefined ) {
96+ return { resultIndex } ;
97+ } else {
98+ return key ;
99+ }
100+ case NavigationDirection . right :
101+ if ( pathIndex === undefined ) {
102+ return { resultIndex, pathIndex : 0 } ;
103+ } else if ( pathNodeIndex === undefined ) {
104+ return { resultIndex, pathIndex, pathNodeIndex : 0 } ;
105+ } else {
106+ return key ;
107+ }
108+ }
109+ } ;
88110
89- return (
90- < table className = { className } >
91- < AlertTableHeader sortState = { resultSet . interpretation . data . sortState } />
92- < tbody >
93- { resultSet . interpretation . data . runs [ 0 ] . results . map (
94- ( result , resultIndex ) => (
95- < AlertTableResultRow
96- key = { resultIndex }
97- result = { result }
98- resultIndex = { resultIndex }
99- expanded = { this . state . expanded }
100- selectedItem = { this . state . selectedItem }
101- databaseUri = { databaseUri }
102- sourceLocationPrefix = { sourceLocationPrefix }
103- updateSelectionCallback = { updateSelectionCallback }
104- toggleExpanded = { this . toggle . bind ( this ) }
105- scroller = { this . scroller }
106- />
107- ) ,
108- ) }
109- < AlertTableTruncatedMessage
110- numTruncatedResults = { numTruncatedResults }
111- />
112- </ tbody >
113- </ table >
114- ) ;
115- }
116-
117- private handleNavigationEvent ( event : NavigateMsg ) {
118- this . setState ( ( prevState ) => {
119- const key = this . getNewSelection ( prevState . selectedItem , event . direction ) ;
120- const data = this . props . resultSet . interpretation . data ;
111+ const handleNavigationEvent = useCallback (
112+ ( event : NavigateMsg ) => {
113+ const key = getNewSelection ( selectedItem , event . direction ) ;
114+ const data = resultSet . interpretation . data ;
121115
122116 // Check if the selected node actually exists (bounds check) and get its location if relevant
123117 let jumpLocation : Sarif . Location | undefined ;
124118 if ( key . pathNodeIndex !== undefined ) {
125119 jumpLocation = Keys . getPathNode ( data , key ) ;
126120 if ( jumpLocation === undefined ) {
127- return prevState ; // Result does not exist
121+ return ; // Result does not exist
128122 }
129123 } else if ( key . pathIndex !== undefined ) {
130124 if ( Keys . getPath ( data , key ) === undefined ) {
131- return prevState ; // Path does not exist
125+ return ; // Path does not exist
132126 }
133127 jumpLocation = undefined ; // When selecting a 'path', don't jump anywhere.
134128 } else {
135129 jumpLocation = Keys . getResult ( data , key ) ?. locations ?. [ 0 ] ;
136130 if ( jumpLocation === undefined ) {
137- return prevState ; // Path step does not exist.
131+ return ; // Path step does not exist.
138132 }
139133 }
140134 if ( jumpLocation !== undefined ) {
141135 const parsedLocation = parseSarifLocation (
142136 jumpLocation ,
143- this . props . resultSet . interpretation . sourceLocationPrefix ,
137+ resultSet . interpretation . sourceLocationPrefix ,
144138 ) ;
145139 if ( ! isNoLocation ( parsedLocation ) ) {
146- jumpToLocation ( parsedLocation , this . props . databaseUri ) ;
140+ jumpToLocation ( parsedLocation , databaseUri ) ;
147141 }
148142 }
149143
150- const expanded = new Set ( prevState . expanded ) ;
144+ const newExpanded = new Set ( expanded ) ;
151145 if ( event . direction === NavigationDirection . right ) {
152146 // When stepping right, expand to ensure the selected node is visible
153- expanded . add ( Keys . keyToString ( { resultIndex : key . resultIndex } ) ) ;
147+ newExpanded . add ( Keys . keyToString ( { resultIndex : key . resultIndex } ) ) ;
154148 if ( key . pathIndex !== undefined ) {
155- expanded . add (
149+ newExpanded . add (
156150 Keys . keyToString ( {
157151 resultIndex : key . resultIndex ,
158152 pathIndex : key . pathIndex ,
@@ -161,75 +155,64 @@ export class AlertTable extends React.Component<
161155 }
162156 } else if ( event . direction === NavigationDirection . left ) {
163157 // When stepping left, collapse immediately
164- expanded . delete ( Keys . keyToString ( key ) ) ;
158+ newExpanded . delete ( Keys . keyToString ( key ) ) ;
165159 } else {
166160 // When stepping up or down, collapse the previous node
167- if ( prevState . selectedItem !== undefined ) {
168- expanded . delete ( Keys . keyToString ( prevState . selectedItem ) ) ;
161+ if ( selectedItem !== undefined ) {
162+ newExpanded . delete ( Keys . keyToString ( selectedItem ) ) ;
169163 }
170164 }
171- this . scroller . scrollIntoViewOnNextUpdate ( ) ;
172- return {
173- ...prevState ,
174- expanded,
175- selectedItem : key ,
176- } ;
177- } ) ;
178- }
165+ scroller . current ?. scrollIntoViewOnNextUpdate ( ) ;
166+ setExpanded ( newExpanded ) ;
167+ setSelectedItem ( key ) ;
168+ } ,
169+ [ databaseUri , expanded , resultSet , selectedItem ] ,
170+ ) ;
171+
172+ useEffect ( ( ) => {
173+ onNavigation . addListener ( handleNavigationEvent ) ;
174+ return ( ) => {
175+ onNavigation . removeListener ( handleNavigationEvent ) ;
176+ } ;
177+ } , [ handleNavigationEvent ] ) ;
179178
180- private getNewSelection (
181- key : Keys . ResultKey | undefined ,
182- direction : NavigationDirection ,
183- ) : Keys . ResultKey {
184- if ( key === undefined ) {
185- return { resultIndex : 0 } ;
186- }
187- const { resultIndex, pathIndex, pathNodeIndex } = key ;
188- switch ( direction ) {
189- case NavigationDirection . up :
190- case NavigationDirection . down : {
191- const delta = direction === NavigationDirection . up ? - 1 : 1 ;
192- if ( key . pathNodeIndex !== undefined ) {
193- return {
194- resultIndex,
195- pathIndex : key . pathIndex ,
196- pathNodeIndex : key . pathNodeIndex + delta ,
197- } ;
198- } else if ( pathIndex !== undefined ) {
199- return { resultIndex, pathIndex : pathIndex + delta } ;
200- } else {
201- return { resultIndex : resultIndex + delta } ;
202- }
203- }
204- case NavigationDirection . left :
205- if ( key . pathNodeIndex !== undefined ) {
206- return { resultIndex, pathIndex : key . pathIndex } ;
207- } else if ( pathIndex !== undefined ) {
208- return { resultIndex } ;
209- } else {
210- return key ;
211- }
212- case NavigationDirection . right :
213- if ( pathIndex === undefined ) {
214- return { resultIndex, pathIndex : 0 } ;
215- } else if ( pathNodeIndex === undefined ) {
216- return { resultIndex, pathIndex, pathNodeIndex : 0 } ;
217- } else {
218- return key ;
219- }
220- }
221- }
179+ const { numTruncatedResults, sourceLocationPrefix } =
180+ resultSet . interpretation ;
222181
223- componentDidUpdate ( ) {
224- this . scroller . update ( ) ;
225- }
182+ const updateSelectionCallback = useCallback (
183+ ( resultKey : Keys . PathNode | Keys . Result | undefined ) => {
184+ setSelectedItem ( resultKey ) ;
185+ sendTelemetry ( "local-results-alert-table-path-selected" ) ;
186+ } ,
187+ [ ] ,
188+ ) ;
226189
227- componentDidMount ( ) {
228- this . scroller . update ( ) ;
229- onNavigation . addListener ( this . handleNavigationEvent ) ;
190+ if ( ! resultSet . interpretation . data . runs ?. [ 0 ] ?. results ?. length ) {
191+ return < AlertTableNoResults { ...props } /> ;
230192 }
231193
232- componentWillUnmount ( ) {
233- onNavigation . removeListener ( this . handleNavigationEvent ) ;
234- }
194+ return (
195+ < table className = { className } >
196+ < AlertTableHeader sortState = { resultSet . interpretation . data . sortState } />
197+ < tbody >
198+ { resultSet . interpretation . data . runs [ 0 ] . results . map (
199+ ( result , resultIndex ) => (
200+ < AlertTableResultRow
201+ key = { resultIndex }
202+ result = { result }
203+ resultIndex = { resultIndex }
204+ expanded = { expanded }
205+ selectedItem = { selectedItem }
206+ databaseUri = { databaseUri }
207+ sourceLocationPrefix = { sourceLocationPrefix }
208+ updateSelectionCallback = { updateSelectionCallback }
209+ toggleExpanded = { toggle }
210+ scroller = { scroller . current }
211+ />
212+ ) ,
213+ ) }
214+ < AlertTableTruncatedMessage numTruncatedResults = { numTruncatedResults } />
215+ </ tbody >
216+ </ table >
217+ ) ;
235218}
0 commit comments