@@ -9,6 +9,7 @@ import { type dh } from '@deephaven/jsapi-types';
99import { Button } from '@deephaven/components' ;
1010import { vsHistory , vsTrash } from '@deephaven/icons' ;
1111import { usePersistentState } from '@deephaven/dashboard' ;
12+ import { type MoveOperation } from '@deephaven/grid' ;
1213import {
1314 type TableOption ,
1415 type TableOptionPanelProps ,
@@ -49,6 +50,8 @@ interface DehydratedStateSnapshot {
4950 selectDistinctColumns : readonly ColumnName [ ] ;
5051 /** Custom columns */
5152 customColumns : readonly ColumnName [ ] ;
53+ /** Re-arranged columns (move operations) */
54+ movedColumns : readonly MoveOperation [ ] ;
5255}
5356
5457/**
@@ -84,6 +87,7 @@ function dehydrateSnapshot(
8487 invertSearchColumns : gridState . invertSearchColumns ,
8588 selectDistinctColumns : [ ...gridState . selectDistinctColumns ] ,
8689 customColumns : [ ...gridState . customColumns ] ,
90+ movedColumns : [ ...gridState . movedColumns ] ,
8791 } ;
8892}
8993
@@ -198,6 +202,12 @@ function TableHistoryPanel(_props: TableOptionPanelProps): JSX.Element {
198202 dispatch ( { type : 'SET_SORTS' , sorts : hydratedSorts } ) ;
199203 dispatch ( { type : 'SET_REVERSE' , reverse : snapshot . reverse } ) ;
200204
205+ // Restore moved columns (re-arranged column order)
206+ dispatch ( {
207+ type : 'SET_MOVED_COLUMNS' ,
208+ columns : [ ...snapshot . movedColumns ] ,
209+ } ) ;
210+
201211 // Stay on the Table History screen after restoring
202212 } ,
203213 [ dispatch , model , irisGridUtils , gridState . selectDistinctColumns ]
@@ -216,8 +226,104 @@ function TableHistoryPanel(_props: TableOptionPanelProps): JSX.Element {
216226 setState ( { snapshots : [ ] } ) ;
217227 } , [ setState ] ) ;
218228
229+ // Compute what has changed since the last saved snapshot
230+ const changedProperties = useMemo ( ( ) => {
231+ const lastSnapshot = snapshots [ snapshots . length - 1 ] ;
232+ if ( lastSnapshot == null ) {
233+ return null ; // No previous snapshot to compare against
234+ }
235+
236+ const currentDehydrated = dehydrateSnapshot ( gridState , irisGridUtils ) ;
237+ const changes : string [ ] = [ ] ;
238+
239+ // Compare sorts
240+ const sortsChanged =
241+ JSON . stringify ( currentDehydrated . sorts ) !==
242+ JSON . stringify ( lastSnapshot . sorts ) ;
243+ if ( sortsChanged ) {
244+ changes . push ( `Sorts: ${ currentDehydrated . sorts . length } column(s)` ) ;
245+ }
246+
247+ // Compare quick filters
248+ const quickFiltersChanged =
249+ JSON . stringify ( currentDehydrated . quickFilters ) !==
250+ JSON . stringify ( lastSnapshot . quickFilters ) ;
251+ if ( quickFiltersChanged ) {
252+ changes . push (
253+ `Quick Filters: ${ currentDehydrated . quickFilters . length } filter(s)`
254+ ) ;
255+ }
256+
257+ // Compare advanced filters
258+ const advancedFiltersChanged =
259+ JSON . stringify ( currentDehydrated . advancedFilters ) !==
260+ JSON . stringify ( lastSnapshot . advancedFilters ) ;
261+ if ( advancedFiltersChanged ) {
262+ changes . push (
263+ `Advanced Filters: ${ currentDehydrated . advancedFilters . length } filter(s)`
264+ ) ;
265+ }
266+
267+ // Compare search
268+ if ( currentDehydrated . searchValue !== lastSnapshot . searchValue ) {
269+ changes . push ( `Search: "${ currentDehydrated . searchValue || '(empty)' } "` ) ;
270+ }
271+
272+ // Compare reverse
273+ if ( currentDehydrated . reverse !== lastSnapshot . reverse ) {
274+ changes . push ( `Reverse: ${ currentDehydrated . reverse } ` ) ;
275+ }
276+
277+ // Compare select distinct
278+ const selectDistinctChanged =
279+ JSON . stringify ( currentDehydrated . selectDistinctColumns ) !==
280+ JSON . stringify ( lastSnapshot . selectDistinctColumns ) ;
281+ if ( selectDistinctChanged ) {
282+ changes . push (
283+ `Select Distinct: ${ currentDehydrated . selectDistinctColumns . length } column(s)`
284+ ) ;
285+ }
286+
287+ // Compare custom columns
288+ const customColumnsChanged =
289+ JSON . stringify ( currentDehydrated . customColumns ) !==
290+ JSON . stringify ( lastSnapshot . customColumns ) ;
291+ if ( customColumnsChanged ) {
292+ changes . push (
293+ `Custom Columns: ${ currentDehydrated . customColumns . length } column(s)`
294+ ) ;
295+ }
296+
297+ // Compare moved columns
298+ const movedColumnsChanged =
299+ JSON . stringify ( currentDehydrated . movedColumns ) !==
300+ JSON . stringify ( lastSnapshot . movedColumns ) ;
301+ if ( movedColumnsChanged ) {
302+ changes . push (
303+ `Column Order: ${ currentDehydrated . movedColumns . length } move(s)`
304+ ) ;
305+ }
306+
307+ return changes ;
308+ } , [ snapshots , gridState , irisGridUtils ] ) ;
309+
219310 return (
220311 < div className = "container mt-3" >
312+ { /* Show changed properties since last snapshot */ }
313+ { changedProperties != null && changedProperties . length > 0 && (
314+ < div className = "mb-3" >
315+ < h6 className = "text-muted mb-1" > Changed since last snapshot:</ h6 >
316+ < ul className = "list-unstyled text-muted small mb-0" >
317+ { changedProperties . map ( change => (
318+ < li key = { change } > • { change } </ li >
319+ ) ) }
320+ </ ul >
321+ </ div >
322+ ) }
323+ { changedProperties != null && changedProperties . length === 0 && (
324+ < p className = "text-muted small mb-3" > No changes since last snapshot.</ p >
325+ ) }
326+
221327 < div className = "d-flex flex-column gap-2" >
222328 < Button kind = "primary" onClick = { handleSaveSnapshot } >
223329 Save Snapshot
@@ -280,7 +386,7 @@ const TableHistoryOption: TableOption = {
280386
281387 menuItem : {
282388 title : 'Table History' ,
283- subtitle : 'Save and restore table state' ,
389+ // subtitle: 'Save and restore table state',
284390 icon : vsHistory ,
285391 order : - 50 ,
286392 isAvailable : ( ) => true ,
0 commit comments