From ce878e1defb8bc209fa7f9be712292ab31f7f069 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Thu, 19 Jul 2018 14:05:54 -0700 Subject: [PATCH] Rework slickgrid keyboard navigation (#1930) * rewrite keybind nav to handle ctrl + home and end * testing different options * working on removed slickgrid changes we don't need * formatting * handle click handler to rowNumber * fixing various bugs * formatting * readd click column to select * add shift key to column select * added logic for additional keybindings on grid * add down and up arrow into keyboard navigation * update styling and update slickgrid * formatting * update angular-slickgrid version * remove index.js changes --- package.json | 4 +- .../plugins/additionalKeyBindings.plugin.ts | 64 +++ .../table/plugins/autoSizeColumns.plugin.ts | 2 +- .../plugins/cellSelectionModel.plugin.ts | 192 +++++++++ .../plugins/dragCellSelectionModel.plugin.ts | 364 ------------------ .../table/plugins/rowNumberColumn.plugin.ts | 70 ++++ .../insights/views/tableInsight.component.ts | 6 +- src/sql/parts/grid/common/interfaces.ts | 4 +- .../views/editData/editData.component.html | 3 +- .../grid/views/editData/editData.component.ts | 29 +- .../parts/grid/views/gridParentComponent.ts | 34 +- .../grid/views/query/query.component.html | 3 +- .../parts/grid/views/query/query.component.ts | 20 +- src/typings/globals/slickgrid/index.d.ts | 2 +- .../modules/angular2-slickgrid/index.d.ts | 5 + yarn.lock | 12 +- 16 files changed, 380 insertions(+), 434 deletions(-) create mode 100644 src/sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin.ts create mode 100644 src/sql/base/browser/ui/table/plugins/cellSelectionModel.plugin.ts delete mode 100644 src/sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin.ts create mode 100644 src/sql/base/browser/ui/table/plugins/rowNumberColumn.plugin.ts diff --git a/package.json b/package.json index 22551a75f6..12f25b5e6b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@angular/router": "~4.1.3", "@angular/upgrade": "~4.1.3", "angular2-grid": "2.0.6", - "angular2-slickgrid": "git://github.com/Microsoft/angular2-slickgrid.git#1.3.11", + "angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.3.12", "applicationinsights": "0.18.0", "chart.js": "^2.6.0", "fast-plist": "0.1.2", @@ -60,7 +60,7 @@ "reflect-metadata": "^0.1.8", "rxjs": "5.4.0", "semver": "4.3.6", - "slickgrid": "github:anthonydresser/SlickGrid#2.3.23", + "slickgrid": "github:anthonydresser/SlickGrid#2.3.24", "spdlog": "0.6.0", "sudo-prompt": "^8.0.0", "svg.js": "^2.2.5", diff --git a/src/sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin.ts b/src/sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin.ts new file mode 100644 index 0000000000..8ddf5f7a28 --- /dev/null +++ b/src/sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; + +/** + * Implements the various additional navigation keybindings we want out of slickgrid + */ +export class AdditionalKeyBindings implements Slick.Plugin { + private grid: Slick.Grid; + private handler = new Slick.EventHandler(); + + public init(grid: Slick.Grid) { + this.grid = grid; + this.handler.subscribe(this.grid.onKeyDown, (e, args) => this.handleKeyDown(e, args)); + } + + public destroy() { + this.handler.unsubscribeAll(); + } + + private handleKeyDown(e: KeyboardEvent, args: Slick.OnKeyDownEventArgs): void { + let event = new StandardKeyboardEvent(e); + let handled = true; + + if (event.equals(KeyCode.RightArrow | KeyMod.CtrlCmd)) { + this.grid.setActiveCell(args.row, this.grid.getColumns().length - 1); + } else if (event.equals(KeyCode.LeftArrow | KeyMod.CtrlCmd)) { + // account for row column + if (this.grid.canCellBeActive(args.row, 0)) { + this.grid.setActiveCell(args.row, 0); + } else { + this.grid.setActiveCell(args.row, 1); + } + } else if (event.equals(KeyCode.UpArrow | KeyMod.CtrlCmd)) { + this.grid.setActiveCell(0, args.cell); + } else if (event.equals(KeyCode.DownArrow | KeyMod.CtrlCmd)) { + this.grid.setActiveCell(this.grid.getDataLength() - 1, args.cell); + } else if (event.equals(KeyCode.Home | KeyMod.CtrlCmd)) { + // account for row column + if (this.grid.canCellBeActive(0, 0)) { + this.grid.setActiveCell(0, 0); + } else { + this.grid.setActiveCell(0, 1); + } + } else if (event.equals(KeyCode.End | KeyMod.CtrlCmd)) { + this.grid.setActiveCell(this.grid.getDataLength() - 1, this.grid.getColumns().length - 1); + } else { + handled = false; + } + + if (handled) { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + } + } + +} diff --git a/src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts b/src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts index 5ebf1dab0d..87539edf68 100644 --- a/src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts +++ b/src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts @@ -16,7 +16,7 @@ export class AutoColumnSize implements Slick.Plugin { private _context: CanvasRenderingContext2D; private _options: IAutoColumnSizeOptions; - constructor(options: IAutoColumnSizeOptions) { + constructor(options: IAutoColumnSizeOptions = defaultOptions) { this._options = mixin(options, defaultOptions, false); } diff --git a/src/sql/base/browser/ui/table/plugins/cellSelectionModel.plugin.ts b/src/sql/base/browser/ui/table/plugins/cellSelectionModel.plugin.ts new file mode 100644 index 0000000000..3108af4bbf --- /dev/null +++ b/src/sql/base/browser/ui/table/plugins/cellSelectionModel.plugin.ts @@ -0,0 +1,192 @@ +// Drag select selection model gist taken from https://gist.github.com/skoon/5312536 +// heavily modified + +import { mixin } from 'vs/base/common/objects'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; + +require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator'); +require.__$__nodeRequire('slickgrid/plugins/slick.cellrangeselector'); + +export interface ICellRangeSelector extends Slick.Plugin { + onCellRangeSelected: Slick.Event<{ range: Slick.Range }>; + onBeforeCellRangeSelected: Slick.Event; +} + +export interface ICellSelectionModelOptions { + cellRangeSelector?: any; + selectActiveCell?: boolean; +} + +const defaults: ICellSelectionModelOptions = { + selectActiveCell: true +}; + +export class CellSelectionModel implements Slick.SelectionModel> { + private grid: Slick.Grid; + private selector: ICellRangeSelector; + private ranges: Array = []; + + public onSelectedRangesChanged = new Slick.Event>(); + + constructor(private options: ICellSelectionModelOptions = defaults) { + this.options = mixin(this.options, defaults, false); + + if (this.options.cellRangeSelector) { + this.selector = this.options.cellRangeSelector; + } else { + // this is added by the noderequires above + this.selector = new (Slick).CellRangeSelector({ selectionCss: { 'border': '2px dashed grey' } }); + } + } + + public init(grid: Slick.Grid) { + this.grid = grid; + this.grid.onActiveCellChanged.subscribe((e, args) => this.handleActiveCellChange(e, args)); + this.grid.onKeyDown.subscribe(e => this.handleKeyDown(e)); + this.grid.onHeaderClick.subscribe((e: MouseEvent, args) => this.handleHeaderClick(e, args)); + this.grid.registerPlugin(this.selector); + this.selector.onCellRangeSelected.subscribe((e, args) => this.handleCellRangeSelected(e, args)); + this.selector.onBeforeCellRangeSelected.subscribe((e, args) => this.handleBeforeCellRangeSelected(e, args)); + } + + public destroy() { + this.grid.onActiveCellChanged.unsubscribe((e, args) => this.handleActiveCellChange(e, args)); + this.grid.onKeyDown.unsubscribe(e => this.handleKeyDown(e)); + this.selector.onCellRangeSelected.unsubscribe((e, args) => this.handleCellRangeSelected(e, args)); + this.selector.onBeforeCellRangeSelected.unsubscribe((e, args) => this.handleBeforeCellRangeSelected(e, args)); + this.grid.unregisterPlugin(this.selector); + } + + private removeInvalidRanges(ranges: Array): Array { + let result: Array = []; + + for (let i = 0; i < ranges.length; i++) { + let r = ranges[i]; + if (this.grid.canCellBeSelected(r.fromRow, r.fromCell) && this.grid.canCellBeSelected(r.toRow, r.toCell)) { + result.push(r); + } else if (this.grid.canCellBeSelected(r.fromRow, r.fromCell + 1) && this.grid.canCellBeSelected(r.toRow, r.toCell)) { + // account for number row + result.push(new Slick.Range(r.fromRow, r.fromCell + 1, r.toRow, r.toCell)); + } + } + + return result; + } + + public setSelectedRanges(ranges: Array): void { + // simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged + if ((!this.ranges || this.ranges.length === 0) && (!ranges || ranges.length === 0)) { + return; + } + + this.ranges = this.removeInvalidRanges(ranges); + this.onSelectedRangesChanged.notify(this.ranges); + } + + public getSelectedRanges() { + return this.ranges; + } + + private handleBeforeCellRangeSelected(e, args: Slick.Cell) { + if (this.grid.getEditorLock().isActive()) { + e.stopPropagation(); + return false; + } + return true; + } + + private handleCellRangeSelected(e, args: { range: Slick.Range }) { + this.grid.setActiveCell(args.range.fromRow, args.range.fromCell, false, false, true); + this.setSelectedRanges([args.range]); + } + + private handleActiveCellChange(e, args) { + if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell)) { + this.setSelectedRanges([new Slick.Range(args.row, args.cell)]); + } else if (!this.options.selectActiveCell) { + // clear the previous selection once the cell changes + this.setSelectedRanges([]); + } + } + + private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs) { + if (!isUndefinedOrNull(args.column)) { + let columnIndex = this.grid.getColumnIndex(args.column.id); + if (this.grid.canCellBeSelected(0, columnIndex)) { + let ranges: Array; + if (e.shiftKey) { + ranges = this.getSelectedRanges(); + ranges.push(new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex)); + } else { + ranges = [new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex)]; + } + this.grid.setActiveCell(0, columnIndex); + this.setSelectedRanges(ranges); + } + } + } + + private handleKeyDown(e) { + /*** + * Кey codes + * 37 left + * 38 up + * 39 right + * 40 down + */ + let ranges, last; + let active = this.grid.getActiveCell(); + let metaKey = e.ctrlKey || e.metaKey; + + if (active && e.shiftKey && !metaKey && !e.altKey && + (e.which === 37 || e.which === 39 || e.which === 38 || e.which === 40)) { + + ranges = this.getSelectedRanges(); + if (!ranges.length) { + ranges.push(new Slick.Range(active.row, active.cell)); + } + + // keyboard can work with last range only + last = ranges.pop(); + + // can't handle selection out of active cell + if (!last.contains(active.row, active.cell)) { + last = new Slick.Range(active.row, active.cell); + } + + let dRow = last.toRow - last.fromRow, + dCell = last.toCell - last.fromCell, + // walking direction + dirRow = active.row === last.fromRow ? 1 : -1, + dirCell = active.cell === last.fromCell ? 1 : -1; + + if (e.which === 37) { + dCell -= dirCell; + } else if (e.which === 39) { + dCell += dirCell; + } else if (e.which === 38) { + dRow -= dirRow; + } else if (e.which === 40) { + dRow += dirRow; + } + + // define new selection range + let new_last = new Slick.Range(active.row, active.cell, active.row + dirRow * dRow, active.cell + dirCell * dCell); + if (this.removeInvalidRanges([new_last]).length) { + ranges.push(new_last); + let viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow; + let viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell; + this.grid.scrollRowIntoView(viewRow, false); + this.grid.scrollCellIntoView(viewRow, viewCell, false); + } else { + ranges.push(last); + } + + this.setSelectedRanges(ranges); + + e.preventDefault(); + e.stopPropagation(); + } + } +} diff --git a/src/sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin.ts b/src/sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin.ts deleted file mode 100644 index b330b0ab5f..0000000000 --- a/src/sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin.ts +++ /dev/null @@ -1,364 +0,0 @@ -// Drag select selection model gist taken from https://gist.github.com/skoon/5312536 -// heavily modified - -import { clone } from 'sql/base/common/objects'; - -export class DragCellSelectionModel implements Slick.SelectionModel> { - private readonly keyColResizeIncr = 5; - - private _grid: Slick.Grid; - private _ranges: Array = []; - private _dragging = false; - private _handler = new Slick.EventHandler(); - - public onSelectedRangesChanged = new Slick.Event(); - - public init(grid: Slick.Grid): void { - this._grid = grid; - this._handler.subscribe(this._grid.onActiveCellChanged, (e: Event, data: Slick.OnActiveCellChangedEventArgs) => this.handleActiveCellChange(e, data)); - this._handler.subscribe(this._grid.onKeyDown, (e: JQueryInputEventObject) => this.handleKeyDown(e)); - this._handler.subscribe(this._grid.onClick, (e: MouseEvent) => this.handleClick(e)); - this._handler.subscribe(this._grid.onDrag, (e: MouseEvent) => this.handleDrag(e)); - this._handler.subscribe(this._grid.onDragInit, (e: MouseEvent) => this.handleDragInit(e)); - this._handler.subscribe(this._grid.onDragStart, (e: MouseEvent) => this.handleDragStart(e)); - this._handler.subscribe(this._grid.onDragEnd, (e: MouseEvent) => this.handleDragEnd(e)); - this._handler.subscribe(this._grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs) => this.handleHeaderClick(e, args)); - } - - public destroy(): void { - this._handler.unsubscribeAll(); - } - - private rangesToRows(ranges: Array): Array { - let rows = []; - for (let i = 0; i < ranges.length; i++) { - for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) { - rows.push(j); - } - } - return rows; - } - - private rowsToRanges(rows: Array): Array { - let ranges = []; - let lastCell = this._grid.getColumns().length - 1; - for (let i = 0; i < rows.length; i++) { - ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell)); - } - return ranges; - } - - public getSelectedRows(): Array { - return this.rangesToRows(this._ranges); - } - - public setSelectedRows(rows: Array) { - this.setSelectedRanges(this.rowsToRanges(rows)); - } - - public setSelectedRanges(ranges: Array) { - this._ranges = ranges; - this.onSelectedRangesChanged.notify(this._ranges); - } - - public getSelectedRanges(): Array { - return this._ranges; - } - - private handleActiveCellChange(e: Event, data: Slick.OnActiveCellChangedEventArgs) { } - - private isNavigationKey(e: BaseJQueryEventObject) { - // Nave keys (home, end, arrows) are all in sequential order so use a - switch (e.which) { - case $.ui.keyCode.HOME: - case $.ui.keyCode.END: - case $.ui.keyCode.LEFT: - case $.ui.keyCode.UP: - case $.ui.keyCode.RIGHT: - case $.ui.keyCode.DOWN: - return true; - default: - return false; - } - } - - private navigateLeft(e: JQueryInputEventObject, activeCell: Slick.Cell) { - if (activeCell.cell > 1) { - let isHome = e.which === $.ui.keyCode.HOME; - let newActiveCellColumn = isHome ? 1 : activeCell.cell - 1; - // Unsure why but for range, must record 1 index less than expected - let newRangeColumn = newActiveCellColumn - 1; - - if (e.shiftKey) { - let last = this._ranges.pop(); - - // If we are on the rightmost edge of the range and we navigate left, - // we want to deselect the rightmost cell - if (last.fromCell <= newRangeColumn) { last.toCell -= 1; } - - let fromRow = Math.min(activeCell.row, last.fromRow); - let fromCell = Math.min(newRangeColumn, last.fromCell); - let toRow = Math.max(activeCell.row, last.toRow); - let toCell = Math.max(newRangeColumn, last.toCell); - this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)]; - } else { - this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)]; - } - - this._grid.setActiveCell(activeCell.row, newActiveCellColumn); - this.setSelectedRanges(this._ranges); - } - } - - private navigateRight(e: JQueryInputEventObject, activeCell: Slick.Cell) { - let columnLength = this._grid.getColumns().length; - if (activeCell.cell < columnLength) { - let isEnd = e.which === $.ui.keyCode.END; - let newActiveCellColumn = isEnd ? columnLength : activeCell.cell + 1; - // Unsure why but for range, must record 1 index less than expected - let newRangeColumn = newActiveCellColumn - 1; - if (e.shiftKey) { - let last = this._ranges.pop(); - - // If we are on the leftmost edge of the range and we navigate right, - // we want to deselect the leftmost cell - if (newRangeColumn <= last.toCell) { last.fromCell += 1; } - - let fromRow = Math.min(activeCell.row, last.fromRow); - let fromCell = Math.min(newRangeColumn, last.fromCell); - let toRow = Math.max(activeCell.row, last.toRow); - let toCell = Math.max(newRangeColumn, last.toCell); - - this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)]; - } else { - this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)]; - } - this._grid.setActiveCell(activeCell.row, newActiveCellColumn); - this.setSelectedRanges(this._ranges); - } - } - - private handleKeyDown(e: JQueryInputEventObject) { - let activeCell = this._grid.getActiveCell(); - - if (activeCell) { - // navigation keys - if (this.isNavigationKey(e)) { - e.stopImmediatePropagation(); - if (e.ctrlKey || e.metaKey) { - let event = new CustomEvent('gridnav', { - detail: { - which: e.which, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - shiftKey: e.shiftKey, - altKey: e.altKey - } - }); - window.dispatchEvent(event); - return; - } - // end key - if (e.which === $.ui.keyCode.END) { - this.navigateRight(e, activeCell); - } - // home key - if (e.which === $.ui.keyCode.HOME) { - this.navigateLeft(e, activeCell); - } - // left arrow - if (e.which === $.ui.keyCode.LEFT) { - // column resize - if ((e.ctrlKey || e.metaKey) && e.shiftKey) { - let allColumns = clone(this._grid.getColumns()); - allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width - this.keyColResizeIncr; - this._grid.setColumnWidths(allColumns); - } else { - this.navigateLeft(e, activeCell); - } - // up arrow - } else if (e.which === $.ui.keyCode.UP && activeCell.row > 0) { - if (e.shiftKey) { - let last = this._ranges.pop(); - - // If we are on the bottommost edge of the range and we navigate up, - // we want to deselect the bottommost row - let newRangeRow = activeCell.row - 1; - if (last.fromRow <= newRangeRow) { last.toRow -= 1; } - - let fromRow = Math.min(activeCell.row - 1, last.fromRow); - let fromCell = Math.min(activeCell.cell - 1, last.fromCell); - let toRow = Math.max(newRangeRow, last.toRow); - let toCell = Math.max(activeCell.cell - 1, last.toCell); - this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)]; - } else { - this._ranges = [new Slick.Range(activeCell.row - 1, activeCell.cell - 1, activeCell.row - 1, activeCell.cell - 1)]; - } - this._grid.setActiveCell(activeCell.row - 1, activeCell.cell); - this.setSelectedRanges(this._ranges); - // right arrow - } else if (e.which === $.ui.keyCode.RIGHT) { - // column resize - if ((e.ctrlKey || e.metaKey) && e.shiftKey) { - let allColumns = clone(this._grid.getColumns()); - allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width + this.keyColResizeIncr; - this._grid.setColumnWidths(allColumns); - } else { - this.navigateRight(e, activeCell); - } - // down arrow - } else if (e.which === $.ui.keyCode.DOWN && activeCell.row < this._grid.getDataLength() - 1) { - if (e.shiftKey) { - let last = this._ranges.pop(); - - // If we are on the topmost edge of the range and we navigate down, - // we want to deselect the topmost row - let newRangeRow = activeCell.row + 1; - if (newRangeRow <= last.toRow) { last.fromRow += 1; } - - let fromRow = Math.min(activeCell.row + 1, last.fromRow); - let fromCell = Math.min(activeCell.cell - 1, last.fromCell); - let toRow = Math.max(activeCell.row + 1, last.toRow); - let toCell = Math.max(activeCell.cell - 1, last.toCell); - this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)]; - } else { - this._ranges = [new Slick.Range(activeCell.row + 1, activeCell.cell - 1, activeCell.row + 1, activeCell.cell - 1)]; - } - this._grid.setActiveCell(activeCell.row + 1, activeCell.cell); - this.setSelectedRanges(this._ranges); - } - } - } - } - - private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs) { - let columnIndex = this._grid.getColumnIndex(args.column.id); - if (e.ctrlKey || e.metaKey) { - this._ranges.push(new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex)); - this._grid.setActiveCell(0, columnIndex + 1); - } else if (e.shiftKey && this._ranges.length) { - let last = this._ranges.pop().fromCell; - let from = Math.min(columnIndex, last); - let to = Math.max(columnIndex, last); - this._ranges = []; - for (let i = from; i <= to; i++) { - if (i !== last) { - this._ranges.push(new Slick.Range(0, i, this._grid.getDataLength() - 1, i)); - } - } - this._ranges.push(new Slick.Range(0, last, this._grid.getDataLength() - 1, last)); - } else { - this._ranges = [new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex)]; - this._grid.resetActiveCell(); - } - this.setSelectedRanges(this._ranges); - e.stopImmediatePropagation(); - return true; - } - - private handleClick(e: MouseEvent) { - let cell = this._grid.getCellFromEvent(e); - if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) { - return false; - } - - if (!e.ctrlKey && !e.shiftKey && !e.metaKey) { - if (cell.cell !== 0) { - this._ranges = [new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1)]; - this.setSelectedRanges(this._ranges); - this._grid.setActiveCell(cell.row, cell.cell); - return true; - } else { - this._ranges = [new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1)]; - this.setSelectedRanges(this._ranges); - this._grid.setActiveCell(cell.row, 1); - return true; - } - } - else if (this._grid.getOptions().multiSelect) { - if (e.ctrlKey || e.metaKey) { - if (cell.cell === 0) { - this._ranges.push(new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1)); - this._grid.setActiveCell(cell.row, 1); - } else { - this._ranges.push(new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1)); - this._grid.setActiveCell(cell.row, cell.cell); - } - } else if (this._ranges.length && e.shiftKey) { - let last = this._ranges.pop(); - if (cell.cell === 0) { - let fromRow = Math.min(cell.row, last.fromRow); - let toRow = Math.max(cell.row, last.fromRow); - this._ranges = [new Slick.Range(fromRow, 0, toRow, this._grid.getColumns().length - 1)]; - } else { - let fromRow = Math.min(cell.row, last.fromRow); - let fromCell = Math.min(cell.cell - 1, last.fromCell); - let toRow = Math.max(cell.row, last.toRow); - let toCell = Math.max(cell.cell - 1, last.toCell); - this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)]; - } - } - } - - this.setSelectedRanges(this._ranges); - - return true; - } - - private handleDragInit(e: MouseEvent) { - e.stopImmediatePropagation(); - } - - private handleDragStart(e: MouseEvent) { - let cell = this._grid.getCellFromEvent(e); - e.stopImmediatePropagation(); - this._dragging = true; - if (e.ctrlKey || e.metaKey) { - this._ranges.push(new Slick.Range(cell.row, cell.cell)); - this._grid.setActiveCell(cell.row, cell.cell); - } else if (this._ranges.length && e.shiftKey) { - let last = this._ranges.pop(); - let fromRow = Math.min(cell.row, last.fromRow); - let fromCell = Math.min(cell.cell - 1, last.fromCell); - let toRow = Math.max(cell.row, last.toRow); - let toCell = Math.max(cell.cell - 1, last.toCell); - this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)]; - } else { - this._ranges = [new Slick.Range(cell.row, cell.cell)]; - this._grid.setActiveCell(cell.row, cell.cell); - } - this.setSelectedRanges(this._ranges); - } - - private handleDrag(e: MouseEvent) { - if (this._dragging) { - let cell = this._grid.getCellFromEvent(e); - let activeCell = this._grid.getActiveCell(); - if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) { - return false; - } - - this._ranges.pop(); - - if (activeCell.cell === 0) { - let lastCell = this._grid.getColumns().length - 1; - let firstRow = Math.min(cell.row, activeCell.row); - let lastRow = Math.max(cell.row, activeCell.row); - this._ranges.push(new Slick.Range(firstRow, 0, lastRow, lastCell)); - } else { - let firstRow = Math.min(cell.row, activeCell.row); - let lastRow = Math.max(cell.row, activeCell.row); - let firstColumn = Math.min(cell.cell - 1, activeCell.cell - 1); - let lastColumn = Math.max(cell.cell - 1, activeCell.cell - 1); - this._ranges.push(new Slick.Range(firstRow, firstColumn, lastRow, lastColumn)); - } - this.setSelectedRanges(this._ranges); - return true; - } - return false; - } - - private handleDragEnd(e: MouseEvent) { - this._dragging = false; - } -} \ No newline at end of file diff --git a/src/sql/base/browser/ui/table/plugins/rowNumberColumn.plugin.ts b/src/sql/base/browser/ui/table/plugins/rowNumberColumn.plugin.ts new file mode 100644 index 0000000000..0c585c4433 --- /dev/null +++ b/src/sql/base/browser/ui/table/plugins/rowNumberColumn.plugin.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { range } from 'vs/base/common/arrays'; + +export interface IRowNumberColumnOptions { + numberOfRows: number; + cssClass?: string; +} + +const sizePerDigit = 15; + +export class RowNumberColumn implements Slick.Plugin { + private handler = new Slick.EventHandler(); + private grid: Slick.Grid; + + + constructor(private options: IRowNumberColumnOptions) { + } + + public init(grid: Slick.Grid) { + this.grid = grid; + this.handler + .subscribe(this.grid.onClick, (e, args) => this.handleClick(e, args)) + .subscribe(this.grid.onHeaderClick, (e, args) => this.handleHeaderClick(e, args)); + } + + public destroy() { + this.handler.unsubscribeAll(); + } + + private handleClick(e: MouseEvent, args: Slick.OnClickEventArgs): void { + if (this.grid.getColumns()[args.cell].id === 'rowNumber') { + this.grid.setActiveCell(args.row, 1); + this.grid.setSelectedRows([args.row]); + } + } + + private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs): void { + if (args.column.id === 'rowNumber') { + this.grid.setActiveCell(0, 1); + this.grid.setSelectedRows(range(this.grid.getDataLength())); + } + } + + public getColumnDefinition(): Slick.Column { + return { + id: 'rowNumber', + name: '', + field: 'rowNumber', + width: this.options.numberOfRows.toString().length * sizePerDigit, + resizable: false, + cssClass: this.options.cssClass, + focusable: false, + selectable: false, + formatter: (r, c, v, cd, dc) => this.formatter(r, c, v, cd, dc) + }; + } + + private formatter(row, cell, value, columnDef: Slick.Column, dataContext): string { + if (dataContext) { + return `${row}`; + } + return null; + } +} diff --git a/src/sql/parts/dashboard/widgets/insights/views/tableInsight.component.ts b/src/sql/parts/dashboard/widgets/insights/views/tableInsight.component.ts index d7755ea3bd..5ac016b24f 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/tableInsight.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/tableInsight.component.ts @@ -11,8 +11,8 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { Table } from 'sql/base/browser/ui/table/table'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; -import { DragCellSelectionModel } from 'sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin'; -import { attachTableStyler} from 'sql/common/theme/styler'; +import { attachTableStyler } from 'sql/common/theme/styler'; +import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin'; @Component({ template: '' @@ -63,7 +63,7 @@ export default class TableInsight extends Disposable implements IInsightsView, O private createTable() { if (!this.table) { this.table = new Table(this._elementRef.nativeElement, this.dataView, this.columns, { showRowNumber: true }); - this.table.setSelectionModel(new DragCellSelectionModel()); + this.table.setSelectionModel(new CellSelectionModel()); this._register(attachTableStyler(this.table, this.themeService)); } } diff --git a/src/sql/parts/grid/common/interfaces.ts b/src/sql/parts/grid/common/interfaces.ts index 2df6d805b8..e448ba4949 100644 --- a/src/sql/parts/grid/common/interfaces.ts +++ b/src/sql/parts/grid/common/interfaces.ts @@ -4,7 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IColumnDefinition, IObservableCollection, IGridDataRow } from 'angular2-slickgrid'; +import { ISlickColumn, IObservableCollection, IGridDataRow } from 'angular2-slickgrid'; export interface ISlickRange { fromCell: number; @@ -42,7 +42,7 @@ export interface IGridIcon { export interface IGridDataSet { dataRows: IObservableCollection; - columnDefinitions: IColumnDefinition[]; + columnDefinitions: ISlickColumn[]; resized: any; // EventEmitter; totalRows: number; batchId: number; diff --git a/src/sql/parts/grid/views/editData/editData.component.html b/src/sql/parts/grid/views/editData/editData.component.html index 10706a1fdc..b0c40be626 100644 --- a/src/sql/parts/grid/views/editData/editData.component.html +++ b/src/sql/parts/grid/views/editData/editData.component.html @@ -17,14 +17,13 @@ showDataTypeIcon="false" showHeader="true" [resized]="dataSet.resized" - [plugins]="slickgridPlugins" + [plugins]="plugins[i]" (activeCellChanged)="onActiveCellChanged($event)" (cellEditBegin)="onCellEditBegin($event)" (cellEditExit)="onCellEditEnd($event)" (rowEditBegin)="onRowEditBegin($event)" (rowEditExit)="onRowEditEnd($event)" (contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)" - [isColumnEditable]="onIsColumnEditable" [isCellEditValid]="onIsCellEditValid" [overrideCellFn]="overrideCellFn" enableEditing="true" diff --git a/src/sql/parts/grid/views/editData/editData.component.ts b/src/sql/parts/grid/views/editData/editData.component.ts index 50afab7406..1f93368140 100644 --- a/src/sql/parts/grid/views/editData/editData.component.ts +++ b/src/sql/parts/grid/views/editData/editData.component.ts @@ -24,6 +24,9 @@ import { error } from 'sql/base/common/log'; import { clone, mixin } from 'sql/base/common/objects'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; +import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin'; +import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin'; +import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin'; import { escape } from 'sql/base/common/strings'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -36,7 +39,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; - export const EDITDATA_SELECTOR: string = 'editdata-component'; @Component({ @@ -66,6 +68,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On private newRowVisible: boolean; private removingNewRow: boolean; private rowIdMappings: { [gridRowId: number]: number } = {}; + protected plugins = new Array>>(); // Edit Data functions public onActiveCellChanged: (event: { row: number, column: number }) => void; @@ -167,17 +170,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On this.onRowEditEnd = (event: { row: number }): void => { }; - this.onIsColumnEditable = (column: number): boolean => { - let result = false; - // Check that our variables exist - if (column !== undefined && !!this.dataSet && !!this.dataSet.columnDefinitions[column]) { - result = this.dataSet.columnDefinitions[column].isEditable; - } - - // If no column definition exists then the row is not editable - return result; - }; - this.overrideCellFn = (rowNumber, columnId, value?, data?): string => { let returnVal = ''; if (Services.DBCellValue.isDBCellValue(value)) { @@ -197,9 +189,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On self.idMapping[rowIndex] = row.id; rowIndex++; return { - values: row.cells.map(c => { + values: [{}].concat(row.cells.map(c => { return mixin({ ariaLabel: escape(c.displayValue) }, c); - }), row: row.id + })), row: row.id }; }); @@ -351,6 +343,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On let maxHeight = this.getMaxHeight(resultSet.rowCount); let minHeight = this.getMinHeight(resultSet.rowCount); + let rowNumberColumn = new RowNumberColumn({ numberOfRows: resultSet.rowCount }); + // Store the result set from the event let dataSet: IGridDataSet = { resized: undefined, @@ -365,7 +359,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On this.loadDataFunction, index => { return { values: [] }; } ), - columnDefinitions: resultSet.columnInfo.map((c, i) => { + columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => { let isLinked = c.isXml || c.isJson; let linkType = c.isXml ? 'xml' : 'json'; @@ -374,13 +368,14 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan' ? 'XML Showplan' : escape(c.columnName), - type: self.stringToFieldType('string'), + field: i.toString(), formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter, asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined, isEditable: c.isUpdatable }; - }) + })) }; + self.plugins.push([rowNumberColumn, new AutoColumnSize(), new AdditionalKeyBindings()]); self.dataSet = dataSet; // Create a dataSet to render without rows to reduce DOM size diff --git a/src/sql/parts/grid/views/gridParentComponent.ts b/src/sql/parts/grid/views/gridParentComponent.ts index 60f6e2a25c..fa55105e4d 100644 --- a/src/sql/parts/grid/views/gridParentComponent.ts +++ b/src/sql/parts/grid/views/gridParentComponent.ts @@ -26,6 +26,7 @@ import * as GridContentEvents from 'sql/parts/grid/common/gridContentEvents'; import { ResultsVisibleContext, ResultsGridFocussedContext, ResultsMessagesFocussedContext, QueryEditorVisibleContext } from 'sql/parts/query/common/queryContext'; import { error } from 'sql/base/common/log'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; +import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin'; import { IAction } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; @@ -33,8 +34,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin'; -import { DragCellSelectionModel } from 'sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -42,14 +41,8 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; export abstract class GridParentComponent { // CONSTANTS // tslint:disable:no-unused-variable - protected get selectionModel(): DragCellSelectionModel { - return new DragCellSelectionModel(); - } - protected get slickgridPlugins(): Array { - return [ - new AutoColumnSize({}) - ]; - } + + protected get selectionModel() { return new CellSelectionModel(); } protected _rowHeight = 29; protected _defaultNumShowingRows = 8; protected Constants = Constants; @@ -94,7 +87,6 @@ export abstract class GridParentComponent { public onRowEditBegin: (event: { row: number }) => void; public onRowEditEnd: (event: { row: number }) => void; public onIsCellEditValid: (row: number, column: number, newValue: any) => boolean; - public onIsColumnEditable: (column: number) => boolean; public overrideCellFn: (rowNumber, columnId, value?, data?) => string; public loadDataFunction: (offset: number, count: number) => Promise; @@ -423,7 +415,9 @@ export abstract class GridParentComponent { let self = this; return (gridIndex: number) => { self.activeGrid = gridIndex; - self.slickgrids.toArray()[this.activeGrid].selection = true; + let grid = self.slickgrids.toArray()[self.activeGrid]; + grid.setActive(); + grid.selection = true; }; } @@ -433,22 +427,6 @@ export abstract class GridParentComponent { } } - /** - * Used to convert the string to a enum compatible with SlickGrid - */ - protected stringToFieldType(input: string): FieldType { - let fieldtype: FieldType; - switch (input) { - case 'string': - fieldtype = FieldType.String; - break; - default: - fieldtype = FieldType.String; - break; - } - return fieldtype; - } - /** * Makes a resultset take up the full result height if this is not already true * Otherwise rerenders the result sets from default diff --git a/src/sql/parts/grid/views/query/query.component.html b/src/sql/parts/grid/views/query/query.component.html index 869d1b67c0..150dee3124 100644 --- a/src/sql/parts/grid/views/query/query.component.html +++ b/src/sql/parts/grid/views/query/query.component.html @@ -20,12 +20,11 @@ [dataRows]="dataSet.dataRows" (contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)" enableAsyncPostRender="true" - showDataTypeIcon="false" showHeader="true" [resized]="dataSet.resized" (mousedown)="navigateToGrid(i)" [selectionModel]="selectionModel" - [plugins]="slickgridPlugins" + [plugins]="plugins[i]" class="boxCol content vertBox slickgrid" [rowHeight]="rowHeight"> diff --git a/src/sql/parts/grid/views/query/query.component.ts b/src/sql/parts/grid/views/query/query.component.ts index 78683f9e8b..0f9ecea3ca 100644 --- a/src/sql/parts/grid/views/query/query.component.ts +++ b/src/sql/parts/grid/views/query/query.component.ts @@ -28,6 +28,9 @@ import { TabChild } from 'sql/base/browser/ui/panel/tab.component'; import { clone, mixin } from 'sql/base/common/objects'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { escape } from 'sql/base/common/strings'; +import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin'; +import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin'; +import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin'; import { format } from 'vs/base/common/strings'; import * as DOM from 'vs/base/browser/dom'; @@ -61,7 +64,9 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes // create a function alias to use inside query.component // tslint:disable-next-line:no-unused-variable - private stringsFormat: any = format; + protected stringsFormat: any = format; + + protected plugins = new Array>>(); // tslint:disable-next-line:no-unused-variable private dataIcons: IGridIcon[] = [ @@ -302,9 +307,9 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes for (let row = 0; row < rows.rows.length; row++) { // Push row values onto end of gridData for slickgrid gridData.push({ - values: rows.rows[row].map(c => { + values: [{}].concat(rows.rows[row].map(c => { return mixin({ ariaLabel: escape(c.displayValue) }, c); - }) + })) }); } @@ -331,6 +336,8 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes minHeight = minHeightNumber.toString() + 'px'; } + let rowNumberColumn = new RowNumberColumn({ numberOfRows: resultSet.rowCount }); + // Store the result set from the event let dataSet: IGridDataSet = { resized: undefined, @@ -345,7 +352,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes loadDataFunction, index => { return { values: [] }; } ), - columnDefinitions: resultSet.columnInfo.map((c, i) => { + columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => { let isLinked = c.isXml || c.isJson; let linkType = c.isXml ? 'xml' : 'json'; @@ -354,12 +361,13 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan' ? 'XML Showplan' : escape(c.columnName), - type: self.stringToFieldType('string'), + field: i.toString(), formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter, asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined }; - }) + })) }; + self.plugins.push([rowNumberColumn, new AutoColumnSize(), new AdditionalKeyBindings()]); self.dataSets.push(dataSet); // check if the resultset is for a query plan diff --git a/src/typings/globals/slickgrid/index.d.ts b/src/typings/globals/slickgrid/index.d.ts index d6d46c5ed2..1470423119 100644 --- a/src/typings/globals/slickgrid/index.d.ts +++ b/src/typings/globals/slickgrid/index.d.ts @@ -1149,7 +1149,7 @@ declare namespace Slick { * @param row A row index. * @param cell A column index. **/ - public setActiveCell(row: number, cell: number): void; + public setActiveCell(row: number, cell: number, opt_editMode?: boolean, preClickModeOn?: boolean, suppressActiveCellChangedEvent?: boolean): void; /** * Sets CSS classes to specific grid cells by calling removeCellCssStyles(key) followed by addCellCssStyles(key, hash). key is name for this set of styles so you can reference it later - to modify it or remove it, for example. hash is a per-row-index, per-column-name nested hash of CSS classes to apply. diff --git a/src/typings/modules/angular2-slickgrid/index.d.ts b/src/typings/modules/angular2-slickgrid/index.d.ts index ecdbf3b92b..7e55ecd755 100644 --- a/src/typings/modules/angular2-slickgrid/index.d.ts +++ b/src/typings/modules/angular2-slickgrid/index.d.ts @@ -81,6 +81,11 @@ export interface IColumnDefinition { formatter?: (row: number, cell: any, value: any, columnDef: any, dataContext: any) => string; isEditable?: boolean; } + +export interface ISlickColumn extends Slick.Column { + isEditable?: boolean; +} + export interface IGridColumnDefinition { id: string; type: number; diff --git a/yarn.lock b/yarn.lock index 264d3c0116..317a5b5265 100644 --- a/yarn.lock +++ b/yarn.lock @@ -172,9 +172,9 @@ angular2-grid@2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/angular2-grid/-/angular2-grid-2.0.6.tgz#01fe225dc13b2822370b6c61f9a6913b3a26f989" -"angular2-slickgrid@git://github.com/Microsoft/angular2-slickgrid.git#1.3.11": - version "1.3.10" - resolved "git://github.com/Microsoft/angular2-slickgrid.git#35f00750ef2f544b17744cc167c0ff7997c114b4" +"angular2-slickgrid@github:Microsoft/angular2-slickgrid#1.3.12": + version "1.3.12" + resolved "https://codeload.github.com/Microsoft/angular2-slickgrid/tar.gz/19aafe8888d2f2eb70aec858e4b86e3c1b7b3fc8" ansi-colors@^1.0.1: version "1.1.0" @@ -5889,9 +5889,9 @@ slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" -"slickgrid@github:anthonydresser/SlickGrid#2.3.23": - version "2.3.23" - resolved "https://codeload.github.com/anthonydresser/SlickGrid/tar.gz/a88c0c25fd6cbe01be86b3018f523987a198a6b6" +"slickgrid@github:anthonydresser/SlickGrid#2.3.24": + version "2.3.24" + resolved "https://codeload.github.com/anthonydresser/SlickGrid/tar.gz/52a87398330a4751088e3e59c37ef50f17179e09" dependencies: jquery ">=1.8.0" jquery-ui ">=1.8.0"