From e77bb30d486676f123abbe9523416038abb2d1c0 Mon Sep 17 00:00:00 2001 From: Christopher Suh Date: Mon, 10 Mar 2025 12:09:07 -0700 Subject: [PATCH] Add a sort button to column header (#18561) * add new sort button * remove extraneous call * wip * store prev sort button * remove reset to unfiltered state after sorting a column * Revert "remove reset to unfiltered state after sorting a column" This reverts commit 1d3b70da7c97fd2d6423542ea4fd0abfa96da750. * add third state for sort button, resetting data to original order * fix sort/filter scenarios * fix build * pr comments & cleanup * cleanup * update comment --- src/reactviews/media/sort-asc.gif | Bin 830 -> 0 bytes src/reactviews/media/sort-asc.png | Bin 105 -> 0 bytes src/reactviews/media/sort-desc.gif | Bin 833 -> 0 bytes src/reactviews/media/sort-desc.png | Bin 107 -> 0 bytes src/reactviews/media/sort.svg | 3 + src/reactviews/media/sort_asc.svg | 9 +- src/reactviews/media/sort_asc_inverse.svg | 9 +- src/reactviews/media/sort_desc.svg | 9 +- src/reactviews/media/sort_desc_inverse.svg | 9 +- src/reactviews/media/sort_inverse.svg | 3 + src/reactviews/media/table.css | 27 +++ .../pages/QueryResult/table/asyncDataView.ts | 4 + .../pages/QueryResult/table/dataProvider.ts | 5 + .../QueryResult/table/hybridDataProvider.ts | 4 + .../pages/QueryResult/table/interfaces.ts | 6 + .../table/plugins/headerFilter.plugin.ts | 177 +++++++++++++++--- .../pages/QueryResult/table/tableDataView.ts | 66 ++++++- typings/slickgrid.d.ts | 2 +- 18 files changed, 272 insertions(+), 61 deletions(-) delete mode 100644 src/reactviews/media/sort-asc.gif delete mode 100644 src/reactviews/media/sort-asc.png delete mode 100644 src/reactviews/media/sort-desc.gif delete mode 100644 src/reactviews/media/sort-desc.png create mode 100644 src/reactviews/media/sort.svg create mode 100644 src/reactviews/media/sort_inverse.svg diff --git a/src/reactviews/media/sort-asc.gif b/src/reactviews/media/sort-asc.gif deleted file mode 100644 index 67a2a4c669fc5821a07fc486228d626e16d6ad9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 830 zcmZ?wbhEHb + + diff --git a/src/reactviews/media/sort_asc.svg b/src/reactviews/media/sort_asc.svg index c9c1838171..94f9cd580f 100644 --- a/src/reactviews/media/sort_asc.svg +++ b/src/reactviews/media/sort_asc.svg @@ -1,8 +1,3 @@ - - - + + diff --git a/src/reactviews/media/sort_asc_inverse.svg b/src/reactviews/media/sort_asc_inverse.svg index 2d40e6ca52..95cd58a56c 100644 --- a/src/reactviews/media/sort_asc_inverse.svg +++ b/src/reactviews/media/sort_asc_inverse.svg @@ -1,8 +1,3 @@ - - - + + diff --git a/src/reactviews/media/sort_desc.svg b/src/reactviews/media/sort_desc.svg index d045a30b02..5045e00674 100644 --- a/src/reactviews/media/sort_desc.svg +++ b/src/reactviews/media/sort_desc.svg @@ -1,8 +1,3 @@ - - - + + diff --git a/src/reactviews/media/sort_desc_inverse.svg b/src/reactviews/media/sort_desc_inverse.svg index 9184cf7626..4d0fed8a8d 100644 --- a/src/reactviews/media/sort_desc_inverse.svg +++ b/src/reactviews/media/sort_desc_inverse.svg @@ -1,8 +1,3 @@ - - - + + diff --git a/src/reactviews/media/sort_inverse.svg b/src/reactviews/media/sort_inverse.svg new file mode 100644 index 0000000000..87cfe0eeda --- /dev/null +++ b/src/reactviews/media/sort_inverse.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/reactviews/media/table.css b/src/reactviews/media/table.css index 7a03f0eaae..99eb0be944 100644 --- a/src/reactviews/media/table.css +++ b/src/reactviews/media/table.css @@ -61,6 +61,33 @@ flex: 0 0 auto; } +.slick-header-sort-button, +.slick-header-sortdesc-button, +.slick-header-sortasc-button { + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + display: inline-block; + width: 16px; + flex: 0 0 auto; + margin-right: 2px; + background-color: transparent; + border: 0; + padding: 0; +} + +.slick-header-sort-button { + background-image: url("sort_inverse.svg"); +} + +.slick-header-sortdesc-button { + background-image: url("sort_desc_inverse.svg"); +} + +.slick-header-sortasc-button { + background-image: url("sort_asc_inverse.svg"); +} + .slick-header-menubutton { background-position: center center; background-repeat: no-repeat; diff --git a/src/reactviews/pages/QueryResult/table/asyncDataView.ts b/src/reactviews/pages/QueryResult/table/asyncDataView.ts index 12fa1ce130..5006aca4a9 100644 --- a/src/reactviews/pages/QueryResult/table/asyncDataView.ts +++ b/src/reactviews/pages/QueryResult/table/asyncDataView.ts @@ -286,6 +286,10 @@ export class AsyncDataProvider throw new Error("Method not implemented."); } + resetSort(): void { + throw new Error("Method not implemented."); + } + public getLength(): number { return this.dataRows.getLength(); } diff --git a/src/reactviews/pages/QueryResult/table/dataProvider.ts b/src/reactviews/pages/QueryResult/table/dataProvider.ts index aeb80cc09f..f77f2eb97a 100644 --- a/src/reactviews/pages/QueryResult/table/dataProvider.ts +++ b/src/reactviews/pages/QueryResult/table/dataProvider.ts @@ -33,6 +33,11 @@ export interface IDisposableDataProvider */ sort(args: Slick.OnSortEventArgs): Promise; + /** + * Resets the sort + */ + resetSort(): void; + /** * Event fired when the filters changed */ diff --git a/src/reactviews/pages/QueryResult/table/hybridDataProvider.ts b/src/reactviews/pages/QueryResult/table/hybridDataProvider.ts index 4b595a91b3..bbbfcdc626 100644 --- a/src/reactviews/pages/QueryResult/table/hybridDataProvider.ts +++ b/src/reactviews/pages/QueryResult/table/hybridDataProvider.ts @@ -116,6 +116,10 @@ export class HybridDataProvider void this.provider.sort(options); } + public async resetSort() { + void this.provider.resetSort(); + } + private get thresholdReached(): boolean { return ( this._options.inMemoryDataCountThreshold !== undefined && diff --git a/src/reactviews/pages/QueryResult/table/interfaces.ts b/src/reactviews/pages/QueryResult/table/interfaces.ts index 7f7da3a34e..11ab38c7ee 100644 --- a/src/reactviews/pages/QueryResult/table/interfaces.ts +++ b/src/reactviews/pages/QueryResult/table/interfaces.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposableDataProvider } from "./dataProvider"; +import { SortDirection } from "./plugins/headerFilter.plugin"; export interface ITableMouseEvent { anchor: HTMLElement | { x: number; y: number }; @@ -80,6 +81,11 @@ export interface FilterableColumn sorted?: SortProperties; } +export interface ColumnSortState { + column: Slick.Column; + sortDirection: SortDirection; +} + export interface ITableKeyboardEvent { cell?: { row: number; cell: number }; event: KeyboardEvent; diff --git a/src/reactviews/pages/QueryResult/table/plugins/headerFilter.plugin.ts b/src/reactviews/pages/QueryResult/table/plugins/headerFilter.plugin.ts index d900d6e375..cf69301654 100644 --- a/src/reactviews/pages/QueryResult/table/plugins/headerFilter.plugin.ts +++ b/src/reactviews/pages/QueryResult/table/plugins/headerFilter.plugin.ts @@ -33,15 +33,16 @@ import { QueryResultWebviewState, } from "../../../../../sharedInterfaces/queryResult"; -export type HeaderFilterCommands = "sort-asc" | "sort-desc"; +export type SortDirection = "sort-asc" | "sort-desc" | "reset"; export interface CommandEventArgs { grid: Slick.Grid; column: Slick.Column; - command: HeaderFilterCommands; + command: SortDirection; } const ShowFilterText = locConstants.queryResult.showFilter; +const SortAscendingText = locConstants.queryResult.sortAscending; export const FilterButtonWidth: number = 34; @@ -58,14 +59,20 @@ export class HeaderFilter { private grid!: Slick.Grid; private handler = new Slick.EventHandler(); private columnDef!: FilterableColumn; - private columnButtonMapping: Map = new Map< + private columnFilterButtonMapping: Map = new Map< string, HTMLElement >(); + private columnSortButtonMapping: Map = new Map< + string, + SortProperties + >(); private _listData: TableFilterListElement[] = []; private _list!: VirtualizedList; private _eventManager = new EventManager(); + private currentSortColumn: string = ""; + private currentSortButton: JQuery | null = null; constructor( public theme: ColorThemeKind, @@ -127,33 +134,143 @@ export class HeaderFilter { const theme: string = resolveVscodeThemeType(this.theme); args.node.classList.add("slick-header-with-filter"); args.node.classList.add(theme); - const $el = jQuery( + const $filterButton = jQuery( ``, ) .addClass("slick-header-menubutton") .data("column", column); + const $sortButton = jQuery( + ``, + ) + .addClass("slick-header-sort-button") + .data("column", column); if (column.filterValues?.length) { - this.setButtonImage($el, column.filterValues?.length > 0); + this.setButtonImage($filterButton, column.filterValues?.length > 0); } - const elDivElement = $el.get(0); - if (elDivElement) { + const filterButton = $filterButton.get(0); + if (filterButton) { this._eventManager.addEventListener( - elDivElement, + filterButton, "click", async (e: Event) => { e.stopPropagation(); e.preventDefault(); - await this.showFilter(elDivElement); + await this.showFilter(filterButton); this.grid.onHeaderClick.notify(); }, ); } - $el.appendTo(args.node); + const sortButton = $sortButton.get(0); + if (sortButton) { + this._eventManager.addEventListener( + sortButton, + "click", + async (e: Event) => { + e.stopPropagation(); + e.preventDefault(); + this.columnDef = jQuery(sortButton).data("column"); //TODO: fix, shouldn't assign in the event handler + let columnFilterState: ColumnFilterState = { + columnDef: this.columnDef.id!, + filterValues: this.columnDef.filterValues!, + sorted: this.columnDef.sorted ?? SortProperties.NONE, + }; + let sortState = this.columnSortButtonMapping.get( + column.id!, + ); + + switch (sortState) { + case SortProperties.NONE: + if ( + this.currentSortColumn && + this.currentSortButton + ) { + const $prevSortButton = this.currentSortButton; + let prevColumnDef = + jQuery($prevSortButton).data("column"); + $prevSortButton.removeClass( + "slick-header-sortasc-button", + ); + $prevSortButton.removeClass( + "slick-header-sortdesc-button", + ); + $prevSortButton.addClass( + "slick-header-sort-button", + ); + this.columnSortButtonMapping.set( + this.currentSortColumn, + SortProperties.NONE, + ); + columnFilterState.sorted = SortProperties.NONE; + let prevFilterState: ColumnFilterState = { + columnDef: prevColumnDef.id!, + filterValues: prevColumnDef.filterValues!, + sorted: SortProperties.NONE, + }; + await this.updateState( + prevFilterState, + prevColumnDef.id!, + ); + } + $sortButton.removeClass("slick-header-sort-button"); + $sortButton.addClass("slick-header-sortasc-button"); + await this.handleMenuItemClick("sort-asc", column); + this.columnSortButtonMapping.set( + column.id!, + SortProperties.ASC, + ); + columnFilterState.sorted = SortProperties.ASC; + this.currentSortColumn = column.id!; + this.currentSortButton = $sortButton; + break; + case SortProperties.ASC: + $sortButton.removeClass( + "slick-header-sortasc-button", + ); + $sortButton.addClass( + "slick-header-sortdesc-button", + ); + await this.handleMenuItemClick("sort-desc", column); + this.columnSortButtonMapping.set( + column.id!, + SortProperties.DESC, + ); + columnFilterState.sorted = SortProperties.DESC; + break; + case SortProperties.DESC: + $sortButton.removeClass( + "slick-header-sortdesc-button", + ); + $sortButton.addClass("slick-header-sort-button"); + this.columnSortButtonMapping.set( + column.id!, + SortProperties.NONE, + ); + await this.handleMenuItemClick("reset", column); + columnFilterState.sorted = SortProperties.NONE; + await this.updateState( + columnFilterState, + this.columnDef.id!, + ); + this.currentSortColumn = ""; + break; + } + this.grid.onHeaderClick.notify(); - //@ts-ignore - this.columnButtonMapping[column.id] = $el[0]; + await this.updateState( + columnFilterState, + this.columnDef.id!, + ); + }, + ); + } + + $sortButton.appendTo(args.node); + $filterButton.appendTo(args.node); + + this.columnFilterButtonMapping.set(column.id!, filterButton); + this.columnSortButtonMapping.set(column.id!, SortProperties.NONE); } private async showFilter(filterButton: HTMLElement) { @@ -180,8 +297,6 @@ export class HeaderFilter { const offset = jQuery(filterButton).offset(); const $popup = jQuery( '