From cbdffdfff88b64cfe56194b24b3bf3a9140f05b8 Mon Sep 17 00:00:00 2001 From: Philip Standt Date: Mon, 14 Aug 2023 18:28:41 +0200 Subject: [PATCH] Support grid selection with Ctrl+Shift+Arrow (#615) --- app/client/components/GridView.js | 140 +++++++++++++++- app/client/components/commandList.ts | 20 +++ test/fixtures/docs/ShiftSelection.grist | Bin 0 -> 286720 bytes test/nbrowser/ShiftSelection.ts | 213 ++++++++++++++++++++++++ 4 files changed, 367 insertions(+), 6 deletions(-) create mode 100644 test/fixtures/docs/ShiftSelection.grist create mode 100644 test/nbrowser/ShiftSelection.ts diff --git a/app/client/components/GridView.js b/app/client/components/GridView.js index 1e567508..b0517bbf 100644 --- a/app/client/components/GridView.js +++ b/app/client/components/GridView.js @@ -316,6 +316,20 @@ GridView.gridCommands = { this._shiftSelect(-1, this.cellSelector.col.end, selector.ROW, this.viewSection.viewFields().peekLength - 1); }, + ctrlShiftDown: function () { + this._shiftSelectUntilContent(selector.COL, 1, this.cellSelector.row.end, this.getLastDataRowIndex()); + }, + ctrlShiftUp: function () { + this._shiftSelectUntilContent(selector.COL, -1, this.cellSelector.row.end, this.getLastDataRowIndex()); + }, + ctrlShiftRight: function () { + this._shiftSelectUntilContent(selector.ROW, 1, this.cellSelector.col.end, + this.viewSection.viewFields().peekLength - 1); + }, + ctrlShiftLeft: function () { + this._shiftSelectUntilContent(selector.ROW, -1, this.cellSelector.col.end, + this.viewSection.viewFields().peekLength - 1); + }, fillSelectionDown: function() { this.fillSelectionDown(); }, selectAll: function() { this.selectAll(); }, @@ -403,6 +417,120 @@ GridView.prototype._shiftSelect = function(step, selectObs, exemptType, maxVal) selectObs(newVal); }; +GridView.prototype._shiftSelectUntilContent = function(type, direction, selectObs, maxVal) { + const selection = { + colStart: this.cellSelector.col.start(), + colEnd: this.cellSelector.col.end(), + rowStart: this.cellSelector.row.start(), + rowEnd: this.cellSelector.row.end(), + }; + + const steps = this._stepsToContent(type, direction, selection, maxVal); + if (steps > 0) { this._shiftSelect(direction * steps, selectObs, type, maxVal); } +} + +GridView.prototype._stepsToContent = function (type, direction, selection, maxVal) { + const {colEnd: colEnd, rowEnd: rowEnd} = selection; + let selectionData; + + const cursorCol = this.cursor.fieldIndex(); + const cursorRow = this.cursor.rowIndex(); + + if (type === selector.ROW && direction > 0) { + if (colEnd + 1 > maxVal) { return 0; } + + selectionData = this._selectionData({colStart: colEnd, colEnd: maxVal, rowStart: cursorRow, rowEnd: cursorRow}); + } else if (type === selector.ROW && direction < 0) { + if (colEnd - 1 < 0) { return 0; } + + selectionData = this._selectionData({colStart: 0, colEnd, rowStart: cursorRow, rowEnd: cursorRow}); + } else if (type === selector.COL && direction > 0) { + if (rowEnd + 1 > maxVal) { return 0; } + + selectionData = this._selectionData({colStart: cursorCol, colEnd: cursorCol, rowStart: rowEnd, rowEnd: maxVal}); + } else if (type === selector.COL && direction < 0) { + if (rowEnd - 1 > maxVal) { return 0; } + + selectionData = this._selectionData({colStart: cursorCol, colEnd: cursorCol, rowStart: 0, rowEnd}); + } + + const {fields, rowIndices} = selectionData; + if (type === selector.ROW && direction < 0) { + // When moving selection left, we step through fields in reverse order. + fields.reverse(); + } + if (type === selector.COL && direction < 0) { + // When moving selection up, we step through rows in reverse order. + rowIndices.reverse(); + } + + const colValuesByIndex = {}; + for (const field of fields) { + const displayColId = field.displayColModel.peek().colId.peek(); + colValuesByIndex[field._index()] = this.tableModel.tableData.getColValues(displayColId); + } + + let steps = 0; + + if (type === selector.ROW) { + const rowIndex = rowIndices[0]; + const isLastColEmpty = this._isCellValueEmpty(colValuesByIndex[colEnd][rowIndex]); + const isNextColEmpty = this._isCellValueEmpty(colValuesByIndex[colEnd + direction][rowIndex]); + const shouldStopOnEmptyValue = !isLastColEmpty && !isNextColEmpty; + for (let i = 1; i < fields.length; i++) { + const hasEmptyValues = this._isCellValueEmpty(colValuesByIndex[fields[i]._index()][rowIndex]); + if (hasEmptyValues && shouldStopOnEmptyValue) { + return steps; + } else if (!hasEmptyValues && !shouldStopOnEmptyValue) { + return steps + 1; + } + + steps += 1; + } + } else { + const colValues = colValuesByIndex[fields[0]._index()]; + const isLastRowEmpty = this._isCellValueEmpty(colValues[rowIndices[0]]); + const isNextRowEmpty = this._isCellValueEmpty(colValues[rowIndices[1]]); + const shouldStopOnEmptyValue = !isLastRowEmpty && !isNextRowEmpty; + for (let i = 1; i < rowIndices.length; i++) { + const hasEmptyValues = this._isCellValueEmpty(colValues[rowIndices[i]]); + if (hasEmptyValues && shouldStopOnEmptyValue) { + return steps; + } else if (!hasEmptyValues && !shouldStopOnEmptyValue) { + return steps + 1; + } + + steps += 1; + } + } + + return steps; +} + +GridView.prototype._selectionData = function({colStart, colEnd, rowStart, rowEnd}) { + const fields = []; + for (let i = colStart; i <= colEnd; i++) { + const field = this.viewSection.viewFields().at(i); + if (!field) { continue; } + + fields.push(field); + } + + const rowIndices = []; + for (let i = rowStart; i <= rowEnd; i++) { + const rowId = this.viewData.getRowId(i); + if (!rowId) { continue; } + + rowIndices.push(this.tableModel.tableData.getRowIdIndex(rowId)); + } + + return {fields, rowIndices}; +} + +GridView.prototype._isCellValueEmpty = function(value) { + return value === null || value === undefined || value === '' || value === 'false'; +} + /** * Pastes the provided data at the current cursor. * @@ -532,15 +660,15 @@ GridView.prototype.fillSelectionDown = function() { /** - * Returns a GridSelection of the selected rows and cols + * Returns a CopySelection of the selected rows and cols * @returns {Object} CopySelection */ GridView.prototype.getSelection = function() { - var rowIds = [], fields = [], rowStyle = {}, colStyle = {}; - var colStart = this.cellSelector.colLower(); - var colEnd = this.cellSelector.colUpper(); - var rowStart = this.cellSelector.rowLower(); - var rowEnd = this.cellSelector.rowUpper(); + let rowIds = [], fields = [], rowStyle = {}, colStyle = {}; + let colStart = this.cellSelector.colLower(); + let colEnd = this.cellSelector.colUpper(); + let rowStart = this.cellSelector.rowLower(); + let rowEnd = this.cellSelector.rowUpper(); // If there is no selection, just copy/paste the cursor cell if (this.cellSelector.isCurrentSelectType(selector.NONE)) { diff --git a/app/client/components/commandList.ts b/app/client/components/commandList.ts index 65f03399..54a972df 100644 --- a/app/client/components/commandList.ts +++ b/app/client/components/commandList.ts @@ -50,6 +50,10 @@ export type CommandName = | 'shiftUp' | 'shiftRight' | 'shiftLeft' + | 'ctrlShiftDown' + | 'ctrlShiftUp' + | 'ctrlShiftRight' + | 'ctrlShiftLeft' | 'selectAll' | 'copyLink' | 'editField' @@ -373,6 +377,22 @@ export const groups: CommendGroupDef[] = [{ name: 'shiftLeft', keys: ['Shift+Left'], desc: 'Adds the element to the left of the cursor to the selected range' + }, { + name: 'ctrlShiftDown', + keys: ['Mod+Shift+Down'], + desc: 'Adds all elements below the cursor to the selected range' + }, { + name: 'ctrlShiftUp', + keys: ['Mod+Shift+Up'], + desc: 'Adds all elements above the cursor to the selected range' + }, { + name: 'ctrlShiftRight', + keys: ['Mod+Shift+Right'], + desc: 'Adds all elements to the right of the cursor to the selected range' + }, { + name: 'ctrlShiftLeft', + keys: ['Mod+Shift+Left'], + desc: 'Adds all elements to the left of the cursor to the selected range' }, { name: 'selectAll', keys: ['Mod+A'], diff --git a/test/fixtures/docs/ShiftSelection.grist b/test/fixtures/docs/ShiftSelection.grist new file mode 100644 index 0000000000000000000000000000000000000000..083021d4d5ee338e5e942e8e04f46c787cf44b97 GIT binary patch literal 286720 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

!}`ieQg3&qjh7^2`=_^R-CW5*K`I)eFy!ETYh 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 gcUJ=3D_; { + let start, end; + + let selectionActive = false; + const elements = await parent.findAll(selector); + for (let i = 0; i < elements.length; i++) { + const element = elements[i]; + const isSelected = await element.matches('.selected'); + + if (isSelected && !selectionActive) { + start = i; + selectionActive = true; + continue; + } + + if (!isSelected && selectionActive) { + end = i - 1; + break; + } + } + + if (start === undefined) { + return undefined; + } + + if (end === undefined) { + end = elements.length - 1; + } + + return { + start: start, + end: end, + }; + } + + async function getSelectedCells(): Promise { + const activeSection = await driver.find('.active_section'); + + const colSelection = await getSelectionRange(activeSection, '.column_names .column_name'); + if (!colSelection) { + return undefined; + } + + const rowSelection = await getSelectionRange(activeSection, '.gridview_row .gridview_data_row_num'); + if (!rowSelection) { + // Edge case if only a cell in the "new" row is selected + // Not relevant for our tests + return undefined; + } + + return { + colStart: colSelection.start, + colEnd: colSelection.end, + rowStart: rowSelection.start, + rowEnd: rowSelection.end, + }; + } + + async function assertCellSelection(expected: CellSelection|undefined) { + const currentSelection = await getSelectedCells(); + assert.deepEqual(currentSelection, expected); + } + + + it('Shift+Up extends the selection up', async function () { + await gu.getCell(1, 2).click(); + await gu.sendKeys(Key.chord(Key.SHIFT, Key.UP)); + await assertCellSelection({colStart: 1, colEnd: 1, rowStart: 0, rowEnd: 1}); + }); + + it('Shift+Down extends the selection down', async function () { + await gu.getCell(1, 2).click(); + await gu.sendKeys(Key.chord(Key.SHIFT, Key.DOWN)); + await assertCellSelection({colStart: 1, colEnd: 1, rowStart: 1, rowEnd: 2}); + }); + + it('Shift+Left extends the selection left', async function () { + await gu.getCell(1, 2).click(); + await gu.sendKeys(Key.chord(Key.SHIFT, Key.LEFT)); + await assertCellSelection({colStart: 0, colEnd: 1, rowStart: 1, rowEnd: 1}); + }); + + it('Shift+Right extends the selection right', async function () { + await gu.getCell(1, 2).click(); + await gu.sendKeys(Key.chord(Key.SHIFT, Key.RIGHT)); + await assertCellSelection({colStart: 1, colEnd: 2, rowStart: 1, rowEnd: 1}); + }); + + it('Shift+Right + Shift+Left leads to the initial selection', async function () { + await gu.getCell(1, 2).click(); + await gu.sendKeys(Key.chord(Key.SHIFT, Key.RIGHT)); + await gu.sendKeys(Key.chord(Key.SHIFT, Key.LEFT)); + await assertCellSelection({colStart: 1, colEnd: 1, rowStart: 1, rowEnd: 1}); + }); + + it('Shift+Up + Shift+Down leads to the initial selection', async function () { + await gu.getCell(1, 2).click(); + await gu.sendKeys(Key.chord(Key.SHIFT, Key.UP)); + await gu.sendKeys(Key.chord(Key.SHIFT, Key.DOWN)); + await assertCellSelection({colStart: 1, colEnd: 1, rowStart: 1, rowEnd: 1}); + }); + + it('Ctrl+Shift+Up extends the selection blockwise up', async function () { + await gu.getCell(5, 7).click(); + + const ctrlKey = await gu.modKey(); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.UP)); + await assertCellSelection({colStart: 5, colEnd: 5, rowStart: 4, rowEnd: 6}); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.UP)); + await assertCellSelection({colStart: 5, colEnd: 5, rowStart: 2, rowEnd: 6}); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.UP)); + await assertCellSelection({colStart: 5, colEnd: 5, rowStart: 0, rowEnd: 6}); + }); + + it('Ctrl+Shift+Down extends the selection blockwise down', async function () { + await gu.getCell(5, 5).click(); + + const ctrlKey = await gu.modKey(); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.DOWN)); + await assertCellSelection({colStart: 5, colEnd: 5, rowStart: 4, rowEnd: 6}); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.DOWN)); + await assertCellSelection({colStart: 5, colEnd: 5, rowStart: 4, rowEnd: 8}); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.DOWN)); + await assertCellSelection({colStart: 5, colEnd: 5, rowStart: 4, rowEnd: 10}); + }); + + it('Ctrl+Shift+Left extends the selection blockwise left', async function () { + await gu.getCell(6, 5).click(); + + const ctrlKey = await gu.modKey(); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.LEFT)); + await assertCellSelection({colStart: 4, colEnd: 6, rowStart: 4, rowEnd: 4}); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.LEFT)); + await assertCellSelection({colStart: 2, colEnd: 6, rowStart: 4, rowEnd: 4}); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.LEFT)); + await assertCellSelection({colStart: 0, colEnd: 6, rowStart: 4, rowEnd: 4}); + }); + + it('Ctrl+Shift+Right extends the selection blockwise right', async function () { + await gu.getCell(4, 5).click(); + + const ctrlKey = await gu.modKey(); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.RIGHT)); + await assertCellSelection({colStart: 4, colEnd: 6, rowStart: 4, rowEnd: 4}); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.RIGHT)); + await assertCellSelection({colStart: 4, colEnd: 8, rowStart: 4, rowEnd: 4}); + + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.RIGHT)); + await assertCellSelection({colStart: 4, colEnd: 10, rowStart: 4, rowEnd: 4}); + }); + + it('Ctrl+Shift+* extends the selection until all the next cells are empty', async function () { + await gu.getCell(3, 7).click(); + + const ctrlKey = await gu.modKey(); + + await gu.sendKeys(Key.chord(Key.SHIFT, Key.RIGHT)); + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.UP)); + await assertCellSelection({colStart: 3, colEnd: 4, rowStart: 2, rowEnd: 6}); + + await gu.getCell(4, 7).click(); + await gu.sendKeys(Key.chord(Key.SHIFT, Key.LEFT)); + await gu.sendKeys(Key.chord(ctrlKey, Key.SHIFT, Key.UP)); + await assertCellSelection({colStart: 3, colEnd: 4, rowStart: 4, rowEnd: 6}); + }); + });