From 07585b17f46998bdd5a2c081405234c65662986b Mon Sep 17 00:00:00 2001 From: georgecwan Date: Thu, 14 Dec 2023 10:17:06 -0800 Subject: [PATCH 1/5] Add alt+click shortcut --- .../src/assets/svg/cursor-copy.svg | 15 ++++++ .../src/main/AppMainContainer.scss | 6 +++ .../src/context-actions/ContextActionUtils.ts | 1 + .../src/context-actions/ContextMenuItem.tsx | 3 +- .../src/panels/IrisGridPanel.tsx | 1 + packages/grid/src/Grid.tsx | 20 ++++++++ packages/grid/src/KeyHandler.ts | 10 ++++ packages/iris-grid/src/IrisGrid.tsx | 44 ++++++++++++++++ .../src/key-handlers/CopyCursorKeyHandler.ts | 33 ++++++++++++ packages/iris-grid/src/key-handlers/index.ts | 1 + .../IrisGridContextMenuHandler.tsx | 6 +++ .../IrisGridCopyCellMouseHandler.ts | 51 +++++++++++++++++++ packages/iris-grid/src/mousehandlers/index.ts | 1 + 13 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 packages/code-studio/src/assets/svg/cursor-copy.svg create mode 100644 packages/iris-grid/src/key-handlers/CopyCursorKeyHandler.ts create mode 100644 packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts diff --git a/packages/code-studio/src/assets/svg/cursor-copy.svg b/packages/code-studio/src/assets/svg/cursor-copy.svg new file mode 100644 index 0000000000..28421e86b4 --- /dev/null +++ b/packages/code-studio/src/assets/svg/cursor-copy.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/code-studio/src/main/AppMainContainer.scss b/packages/code-studio/src/main/AppMainContainer.scss index 00182442e7..59355658fa 100644 --- a/packages/code-studio/src/main/AppMainContainer.scss +++ b/packages/code-studio/src/main/AppMainContainer.scss @@ -248,6 +248,12 @@ $nav-space: 4px; // give a gap around some buttons for focus area that are in na } } +.grid-cursor-copy { + cursor: + url('../assets/svg/cursor-copy.svg') 8 8, + copy; +} + .grid-cursor-linker { cursor: url('../assets/svg/cursor-linker.svg') 8 8, diff --git a/packages/components/src/context-actions/ContextActionUtils.ts b/packages/components/src/context-actions/ContextActionUtils.ts index 4e76570dea..edec5846f2 100644 --- a/packages/components/src/context-actions/ContextActionUtils.ts +++ b/packages/components/src/context-actions/ContextActionUtils.ts @@ -17,6 +17,7 @@ export interface ContextAction { icon?: IconDefinition | React.ReactElement; iconColor?: string; shortcut?: Shortcut; + shortcutText?: string; isGlobal?: boolean; group?: number; order?: number; diff --git a/packages/components/src/context-actions/ContextMenuItem.tsx b/packages/components/src/context-actions/ContextMenuItem.tsx index febf5c72ab..9af75cfef3 100644 --- a/packages/components/src/context-actions/ContextMenuItem.tsx +++ b/packages/components/src/context-actions/ContextMenuItem.tsx @@ -71,7 +71,8 @@ const ContextMenuItem = React.forwardRef( 'data-testid': dataTestId, } = props; - const displayShortcut = menuItem.shortcut?.getDisplayText(); + const displayShortcut = + menuItem.shortcutText ?? menuItem.shortcut?.getDisplayText(); let icon: IconDefinition | React.ReactElement | null = null; if (menuItem.icon) { const menuItemIcon = menuItem.icon; diff --git a/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx b/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx index 4101c6a68e..1b895ab508 100644 --- a/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx @@ -1308,6 +1308,7 @@ export class IrisGridPanel extends PureComponent< )} columnAllowedCursor="linker" columnNotAllowedCursor="linker-not-allowed" + copyCursor="copy" customColumns={customColumns} customColumnFormatMap={customColumnFormatMap} columnSelectionValidator={this.isColumnSelectionValid} diff --git a/packages/grid/src/Grid.tsx b/packages/grid/src/Grid.tsx index fed4bd08c7..e9b6482847 100644 --- a/packages/grid/src/Grid.tsx +++ b/packages/grid/src/Grid.tsx @@ -343,6 +343,7 @@ class Grid extends PureComponent { this.handleEditCellCommit = this.handleEditCellCommit.bind(this); this.handleDoubleClick = this.handleDoubleClick.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleKeyUp = this.handleKeyUp.bind(this); this.handleMouseDown = this.handleMouseDown.bind(this); this.handleMouseDrag = this.handleMouseDrag.bind(this); this.handleMouseMove = this.handleMouseMove.bind(this); @@ -1732,6 +1733,24 @@ class Grid extends PureComponent { } } + /** + * Handle a key up event from the keyboard. Pass the event to the registered keyboard handlers until one handles it. + * @param event Keyboard event + */ + handleKeyUp(event: GridKeyboardEvent): void { + const keyHandlers = this.getKeyHandlers(); + for (let i = 0; i < keyHandlers.length; i += 1) { + const keyHandler = keyHandlers[i]; + const result = keyHandler.onUp(event, this); + if (result !== false) { + const options = result as EventHandlerResultOptions; + if (options?.stopPropagation ?? true) event.stopPropagation(); + if (options?.preventDefault ?? true) event.preventDefault(); + break; + } + } + } + /** * Notify all of the mouse handlers for this grid of a mouse event. * @param functionName The name of the function in the mouse handler to call @@ -2229,6 +2248,7 @@ class Grid extends PureComponent { onContextMenu={this.handleContextMenu} onDoubleClick={this.handleDoubleClick} onKeyDown={this.handleKeyDown} + onKeyUp={this.handleKeyUp} onMouseDown={this.handleMouseDown} onMouseMove={this.handleMouseMove} onMouseLeave={this.handleMouseLeave} diff --git a/packages/grid/src/KeyHandler.ts b/packages/grid/src/KeyHandler.ts index 8b4138f52d..0d02432660 100644 --- a/packages/grid/src/KeyHandler.ts +++ b/packages/grid/src/KeyHandler.ts @@ -33,6 +33,16 @@ export class KeyHandler { onDown(event: GridKeyboardEvent, grid: Grid): EventHandlerResult { return false; } + + /** + * Handle a keyup event on the grid. + * @param event The keyboard event + * @param grid The grid component the key press is on + * @returns Response indicating if the key was consumed + */ + onUp(event: GridKeyboardEvent, grid: Grid): EventHandlerResult { + return false; + } } export default KeyHandler; diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index b4bd389e17..e19cd6ca6f 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -113,6 +113,7 @@ import FilterInputField from './FilterInputField'; import { ClearFilterKeyHandler, CopyKeyHandler, + CopyCursorKeyHandler, ReverseKeyHandler, } from './key-handlers'; import { @@ -120,6 +121,7 @@ import { IrisGridColumnSelectMouseHandler, IrisGridColumnTooltipMouseHandler, IrisGridContextMenuHandler, + IrisGridCopyCellMouseHandler, IrisGridDataSelectMouseHandler, IrisGridFilterMouseHandler, IrisGridRowTreeMouseHandler, @@ -309,6 +311,7 @@ export interface IrisGridProps { // eslint-disable-next-line react/no-unused-prop-types columnNotAllowedCursor: string; + copyCursor: string; name: string; onlyFetchVisibleColumns: boolean; @@ -481,6 +484,7 @@ export class IrisGrid extends Component { columnSelectionValidator: null, columnAllowedCursor: null, columnNotAllowedCursor: null, + copyCursor: null, name: 'table', onlyFetchVisibleColumns: true, showSearchBar: false, @@ -522,6 +526,8 @@ export class IrisGrid extends Component { this.handleAdvancedMenuOpened = this.handleAdvancedMenuOpened.bind(this); this.handleGotoRowOpened = this.handleGotoRowOpened.bind(this); this.handleGotoRowClosed = this.handleGotoRowClosed.bind(this); + this.handleShowCopyCursor = this.handleShowCopyCursor.bind(this); + this.handleHideCopyCursor = this.handleHideCopyCursor.bind(this); this.handleAdvancedMenuClosed = this.handleAdvancedMenuClosed.bind(this); this.handleAggregationChange = this.handleAggregationChange.bind(this); this.handleAggregationsChange = this.handleAggregationsChange.bind(this); @@ -613,6 +619,8 @@ export class IrisGrid extends Component { this.gotoRowRef = React.createRef(); + this.isCopying = false; + this.toggleFilterBarAction = { action: () => this.toggleFilterBar(), shortcut: SHORTCUTS.TABLE.TOGGLE_QUICK_FILTER, @@ -666,6 +674,7 @@ export class IrisGrid extends Component { const { aggregationSettings, conditionalFormats, + copyCursor, customColumnFormatMap, isFilterBarShown, isSelectingPartition, @@ -696,9 +705,11 @@ export class IrisGrid extends Component { ]; if (canCopy) { keyHandlers.push(new CopyKeyHandler(this)); + keyHandlers.push(new CopyCursorKeyHandler(this)); } const { dh } = model; const mouseHandlers = [ + new IrisGridCopyCellMouseHandler(this), new IrisGridCellOverflowMouseHandler(this), new IrisGridRowTreeMouseHandler(this), new IrisGridTokenMouseHandler(this), @@ -1006,6 +1017,8 @@ export class IrisGrid extends Component { gotoRowRef: React.RefObject; + isCopying: boolean; + toggleFilterBarAction: Action; toggleSearchBarAction: Action; @@ -2009,6 +2022,17 @@ export class IrisGrid extends Component { } } + copyColumnHeader(columnIndex: GridRangeIndex): void { + if (columnIndex === null) { + return; + } + const { model } = this.props; + + copyToClipboard(model.textForColumnHeader(columnIndex) ?? '').catch(e => + log.error('Unable to copy header', e) + ); + } + /** * Copy the provided ranges to the clipboard * @paramranges The ranges to copy @@ -2759,6 +2783,26 @@ export class IrisGrid extends Component { this.setState({ isGotoShown: false }); } + handleShowCopyCursor(): void { + if (!this.grid) { + return; + } + const { copyCursor } = this.props; + const { cursor } = this.grid.state; + if (cursor !== copyCursor) { + this.isCopying = true; + this.grid.setState({ cursor: copyCursor }); + } + } + + handleHideCopyCursor(): void { + if (!this.grid) { + return; + } + this.isCopying = false; + this.grid.setState({ cursor: null }); + } + handleAdvancedMenuClosed(columnIndex: number): void { const { focusedFilterBarColumn, isFilterBarShown } = this.state; if ( diff --git a/packages/iris-grid/src/key-handlers/CopyCursorKeyHandler.ts b/packages/iris-grid/src/key-handlers/CopyCursorKeyHandler.ts new file mode 100644 index 0000000000..ec2c1b2f60 --- /dev/null +++ b/packages/iris-grid/src/key-handlers/CopyCursorKeyHandler.ts @@ -0,0 +1,33 @@ +/* eslint class-methods-use-this: "off" */ +import { KeyboardEvent } from 'react'; +import { KeyHandler } from '@deephaven/grid'; +import { ContextActionUtils } from '@deephaven/components'; +import { IrisGrid } from '../IrisGrid'; + +class CopyCursorKeyHandler extends KeyHandler { + private irisGrid: IrisGrid; + + constructor(irisGrid: IrisGrid) { + super(); + + this.irisGrid = irisGrid; + } + + onDown(event: KeyboardEvent): boolean { + if (ContextActionUtils.isModifierKeyDown(event) || event.shiftKey) { + this.irisGrid.handleHideCopyCursor(); + } else if (event.altKey) { + this.irisGrid.handleShowCopyCursor(); + } + return false; + } + + onUp(event: KeyboardEvent): boolean { + if (event.key === 'Alt') { + this.irisGrid.handleHideCopyCursor(); + } + return false; + } +} + +export default CopyCursorKeyHandler; diff --git a/packages/iris-grid/src/key-handlers/index.ts b/packages/iris-grid/src/key-handlers/index.ts index 2ddf3e7624..c732760bb7 100644 --- a/packages/iris-grid/src/key-handlers/index.ts +++ b/packages/iris-grid/src/key-handlers/index.ts @@ -1,3 +1,4 @@ export { default as CopyKeyHandler } from './CopyKeyHandler'; +export { default as CopyCursorKeyHandler } from './CopyCursorKeyHandler'; export { default as ReverseKeyHandler } from './ReverseKeyHandler'; export { default as ClearFilterKeyHandler } from './ClearFilterKeyHandler'; diff --git a/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx b/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx index 5833f4096b..b3b7345437 100644 --- a/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx +++ b/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx @@ -337,6 +337,9 @@ class IrisGridContextMenuHandler extends GridMouseHandler { actions.push({ title: 'Copy Column Name', group: IrisGridContextMenuHandler.GROUP_COPY, + shortcutText: ContextActionUtils.isMacPlatform() + ? 'Opt+Click' + : 'Alt+Click', action: () => { copyToClipboard(model.textForColumnHeader(modelIndex) ?? '').catch(e => log.error('Unable to copy header', e) @@ -632,6 +635,9 @@ class IrisGridContextMenuHandler extends GridMouseHandler { actions.push({ title: 'Copy Cell', group: IrisGridContextMenuHandler.GROUP_COPY, + shortcutText: ContextActionUtils.isMacPlatform() + ? 'Opt+Click' + : 'Alt+Click', order: 10, action: () => { irisGrid.copyCell(columnIndex, rowIndex); diff --git a/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts b/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts new file mode 100644 index 0000000000..58d62c1cf0 --- /dev/null +++ b/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts @@ -0,0 +1,51 @@ +import { + Grid, + GridMouseHandler, + GridPoint, + GridUtils, + EventHandlerResult, + GridMouseEvent, +} from '@deephaven/grid'; +import { ContextActionUtils } from '@deephaven/components'; +import IrisGrid from '../IrisGrid'; + +class IrisGridCopyCellMouseHandler extends GridMouseHandler { + private irisGrid: IrisGrid; + + constructor(irisGrid: IrisGrid) { + super(); + + this.irisGrid = irisGrid; + this.cursor = null; + } + + onClick( + gridPoint: GridPoint, + grid: Grid, + event: GridMouseEvent + ): EventHandlerResult { + if ( + event.altKey && + !ContextActionUtils.isModifierKeyDown(event) && + !event.shiftKey + ) { + this.cursor = null; + if (gridPoint.columnHeaderDepth !== undefined) { + this.irisGrid.copyColumnHeader(gridPoint.column); + } else { + this.irisGrid.copyCell(gridPoint.column, gridPoint.row); + } + return true; + } + return false; + } + + onMove(): EventHandlerResult { + if (this.irisGrid.isCopying) { + this.cursor = this.irisGrid.props.copyCursor; + return true; + } + return false; + } +} +export default IrisGridCopyCellMouseHandler; diff --git a/packages/iris-grid/src/mousehandlers/index.ts b/packages/iris-grid/src/mousehandlers/index.ts index 95c2faf112..a0bcb101a8 100644 --- a/packages/iris-grid/src/mousehandlers/index.ts +++ b/packages/iris-grid/src/mousehandlers/index.ts @@ -2,6 +2,7 @@ export { default as IrisGridCellOverflowMouseHandler } from './IrisGridCellOverf export { default as IrisGridColumnSelectMouseHandler } from './IrisGridColumnSelectMouseHandler'; export { default as IrisGridColumnTooltipMouseHandler } from './IrisGridColumnTooltipMouseHandler'; export { default as IrisGridContextMenuHandler } from './IrisGridContextMenuHandler'; +export { default as IrisGridCopyCellMouseHandler } from './IrisGridCopyCellMouseHandler'; export { default as IrisGridDataSelectMouseHandler } from './IrisGridDataSelectMouseHandler'; export { default as IrisGridFilterMouseHandler } from './IrisGridFilterMouseHandler'; export { default as IrisGridRowTreeMouseHandler } from './IrisGridRowTreeMouseHandler'; From 620949698ad3be582fde420f2114f7f1316c0f1f Mon Sep 17 00:00:00 2001 From: georgecwan Date: Thu, 14 Dec 2023 10:34:31 -0800 Subject: [PATCH 2/5] Fix unit tests --- packages/iris-grid/src/IrisGrid.tsx | 1 - .../iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index f89efd5d8a..7c9cda3b86 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -677,7 +677,6 @@ export class IrisGrid extends Component { const { aggregationSettings, conditionalFormats, - copyCursor, customColumnFormatMap, isFilterBarShown, isSelectingPartition, diff --git a/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts b/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts index 58d62c1cf0..f7768106a8 100644 --- a/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts +++ b/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts @@ -2,7 +2,6 @@ import { Grid, GridMouseHandler, GridPoint, - GridUtils, EventHandlerResult, GridMouseEvent, } from '@deephaven/grid'; From 4e43d3750bd7061a896e878488663b8ec5c23604 Mon Sep 17 00:00:00 2001 From: georgecwan Date: Fri, 15 Dec 2023 12:43:27 -0800 Subject: [PATCH 3/5] Remove key handler --- .../src/panels/IrisGridPanel.scss | 4 +++ packages/iris-grid/src/IrisGrid.tsx | 26 ++------------- .../src/key-handlers/CopyCursorKeyHandler.ts | 33 ------------------- packages/iris-grid/src/key-handlers/index.ts | 1 - .../IrisGridContextMenuHandler.tsx | 6 ++-- .../IrisGridCopyCellMouseHandler.ts | 14 ++++++-- 6 files changed, 20 insertions(+), 64 deletions(-) delete mode 100644 packages/iris-grid/src/key-handlers/CopyCursorKeyHandler.ts diff --git a/packages/dashboard-core-plugins/src/panels/IrisGridPanel.scss b/packages/dashboard-core-plugins/src/panels/IrisGridPanel.scss index 457c74e1bb..e5235adc1d 100644 --- a/packages/dashboard-core-plugins/src/panels/IrisGridPanel.scss +++ b/packages/dashboard-core-plugins/src/panels/IrisGridPanel.scss @@ -31,3 +31,7 @@ $panel-message-overlay-top: 30px; .grid-cursor-linker { cursor: crosshair; } + +.grid-cursor-copy { + cursor: copy; +} diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index 7c9cda3b86..3d197bb653 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -113,7 +113,6 @@ import FilterInputField from './FilterInputField'; import { ClearFilterKeyHandler, CopyKeyHandler, - CopyCursorKeyHandler, ReverseKeyHandler, } from './key-handlers'; import { @@ -312,6 +311,8 @@ export interface IrisGridProps { // eslint-disable-next-line react/no-unused-prop-types columnNotAllowedCursor: string; + + // eslint-disable-next-line react/no-unused-prop-types copyCursor: string; name: string; onlyFetchVisibleColumns: boolean; @@ -529,8 +530,6 @@ export class IrisGrid extends Component { this.handleAdvancedMenuOpened = this.handleAdvancedMenuOpened.bind(this); this.handleGotoRowOpened = this.handleGotoRowOpened.bind(this); this.handleGotoRowClosed = this.handleGotoRowClosed.bind(this); - this.handleShowCopyCursor = this.handleShowCopyCursor.bind(this); - this.handleHideCopyCursor = this.handleHideCopyCursor.bind(this); this.handleAdvancedMenuClosed = this.handleAdvancedMenuClosed.bind(this); this.handleAggregationChange = this.handleAggregationChange.bind(this); this.handleAggregationsChange = this.handleAggregationsChange.bind(this); @@ -707,7 +706,6 @@ export class IrisGrid extends Component { ]; if (canCopy) { keyHandlers.push(new CopyKeyHandler(this)); - keyHandlers.push(new CopyCursorKeyHandler(this)); } const { dh } = model; const mouseHandlers = [ @@ -2791,26 +2789,6 @@ export class IrisGrid extends Component { this.setState({ isGotoShown: false }); } - handleShowCopyCursor(): void { - if (!this.grid) { - return; - } - const { copyCursor } = this.props; - const { cursor } = this.grid.state; - if (cursor !== copyCursor) { - this.isCopying = true; - this.grid.setState({ cursor: copyCursor }); - } - } - - handleHideCopyCursor(): void { - if (!this.grid) { - return; - } - this.isCopying = false; - this.grid.setState({ cursor: null }); - } - handleAdvancedMenuClosed(columnIndex: number): void { const { focusedFilterBarColumn, isFilterBarShown } = this.state; if ( diff --git a/packages/iris-grid/src/key-handlers/CopyCursorKeyHandler.ts b/packages/iris-grid/src/key-handlers/CopyCursorKeyHandler.ts deleted file mode 100644 index ec2c1b2f60..0000000000 --- a/packages/iris-grid/src/key-handlers/CopyCursorKeyHandler.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* eslint class-methods-use-this: "off" */ -import { KeyboardEvent } from 'react'; -import { KeyHandler } from '@deephaven/grid'; -import { ContextActionUtils } from '@deephaven/components'; -import { IrisGrid } from '../IrisGrid'; - -class CopyCursorKeyHandler extends KeyHandler { - private irisGrid: IrisGrid; - - constructor(irisGrid: IrisGrid) { - super(); - - this.irisGrid = irisGrid; - } - - onDown(event: KeyboardEvent): boolean { - if (ContextActionUtils.isModifierKeyDown(event) || event.shiftKey) { - this.irisGrid.handleHideCopyCursor(); - } else if (event.altKey) { - this.irisGrid.handleShowCopyCursor(); - } - return false; - } - - onUp(event: KeyboardEvent): boolean { - if (event.key === 'Alt') { - this.irisGrid.handleHideCopyCursor(); - } - return false; - } -} - -export default CopyCursorKeyHandler; diff --git a/packages/iris-grid/src/key-handlers/index.ts b/packages/iris-grid/src/key-handlers/index.ts index c732760bb7..2ddf3e7624 100644 --- a/packages/iris-grid/src/key-handlers/index.ts +++ b/packages/iris-grid/src/key-handlers/index.ts @@ -1,4 +1,3 @@ export { default as CopyKeyHandler } from './CopyKeyHandler'; -export { default as CopyCursorKeyHandler } from './CopyCursorKeyHandler'; export { default as ReverseKeyHandler } from './ReverseKeyHandler'; export { default as ClearFilterKeyHandler } from './ClearFilterKeyHandler'; diff --git a/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx b/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx index b3b7345437..8762e0c074 100644 --- a/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx +++ b/packages/iris-grid/src/mousehandlers/IrisGridContextMenuHandler.tsx @@ -337,9 +337,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler { actions.push({ title: 'Copy Column Name', group: IrisGridContextMenuHandler.GROUP_COPY, - shortcutText: ContextActionUtils.isMacPlatform() - ? 'Opt+Click' - : 'Alt+Click', + shortcutText: ContextActionUtils.isMacPlatform() ? '⌥Click' : 'Alt+Click', action: () => { copyToClipboard(model.textForColumnHeader(modelIndex) ?? '').catch(e => log.error('Unable to copy header', e) @@ -636,7 +634,7 @@ class IrisGridContextMenuHandler extends GridMouseHandler { title: 'Copy Cell', group: IrisGridContextMenuHandler.GROUP_COPY, shortcutText: ContextActionUtils.isMacPlatform() - ? 'Opt+Click' + ? '⌥Click' : 'Alt+Click', order: 10, action: () => { diff --git a/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts b/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts index f7768106a8..96b022e58a 100644 --- a/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts +++ b/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts @@ -39,8 +39,18 @@ class IrisGridCopyCellMouseHandler extends GridMouseHandler { return false; } - onMove(): EventHandlerResult { - if (this.irisGrid.isCopying) { + onMove( + gridPoint: GridPoint, + _grid: Grid, + event: GridMouseEvent + ): EventHandlerResult { + if ( + event.altKey && + !ContextActionUtils.isModifierKeyDown(event) && + !event.shiftKey && + gridPoint.column != null && + (gridPoint.row != null || gridPoint.columnHeaderDepth != null) + ) { this.cursor = this.irisGrid.props.copyCursor; return true; } From 4678f653508ab027eee589850d7721fc19893d33 Mon Sep 17 00:00:00 2001 From: georgecwan Date: Thu, 21 Dec 2023 15:03:36 -0800 Subject: [PATCH 4/5] Use IrisGridCopyHandler to handle copying --- .../src/context-actions/ContextActionUtils.ts | 2 + packages/grid/src/Grid.test.tsx | 3 +- packages/grid/src/Grid.tsx | 44 +++--- packages/grid/src/KeyHandler.ts | 2 + packages/iris-grid/src/IrisGrid.test.tsx | 5 +- packages/iris-grid/src/IrisGrid.tsx | 31 ++-- .../iris-grid/src/IrisGridCopyHandler.tsx | 137 ++++++++++++------ .../IrisGridCopyCellMouseHandler.ts | 12 +- 8 files changed, 149 insertions(+), 87 deletions(-) diff --git a/packages/components/src/context-actions/ContextActionUtils.ts b/packages/components/src/context-actions/ContextActionUtils.ts index edec5846f2..49c1872279 100644 --- a/packages/components/src/context-actions/ContextActionUtils.ts +++ b/packages/components/src/context-actions/ContextActionUtils.ts @@ -17,6 +17,8 @@ export interface ContextAction { icon?: IconDefinition | React.ReactElement; iconColor?: string; shortcut?: Shortcut; + + /* Display text for the shortcut if the shortcut is not wired up through the Shortcut class */ shortcutText?: string; isGlobal?: boolean; group?: number; diff --git a/packages/grid/src/Grid.test.tsx b/packages/grid/src/Grid.test.tsx index e85dc2cd66..ffacabe39f 100644 --- a/packages/grid/src/Grid.test.tsx +++ b/packages/grid/src/Grid.test.tsx @@ -220,7 +220,8 @@ function mouseDoubleClick( function keyDown(key: string, component: Grid, extraArgs?: KeyboardEventInit) { const args = { key, ...extraArgs }; - component.handleKeyDown( + component.notifyKeyboardHandlers( + 'onDown', new KeyboardEvent('keydown', args) as unknown as React.KeyboardEvent ); } diff --git a/packages/grid/src/Grid.tsx b/packages/grid/src/Grid.tsx index e9b6482847..b3529dc81c 100644 --- a/packages/grid/src/Grid.tsx +++ b/packages/grid/src/Grid.tsx @@ -34,7 +34,10 @@ import { GridTokenMouseHandler, } from './mouse-handlers'; import './Grid.scss'; -import KeyHandler, { GridKeyboardEvent } from './KeyHandler'; +import KeyHandler, { + GridKeyHandlerFunctionName, + GridKeyboardEvent, +} from './KeyHandler'; import { EditKeyHandler, PasteKeyHandler, @@ -342,8 +345,7 @@ class Grid extends PureComponent { this.handleEditCellChange = this.handleEditCellChange.bind(this); this.handleEditCellCommit = this.handleEditCellCommit.bind(this); this.handleDoubleClick = this.handleDoubleClick.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleKeyUp = this.handleKeyUp.bind(this); + this.notifyKeyboardHandlers = this.notifyKeyboardHandlers.bind(this); this.handleMouseDown = this.handleMouseDown.bind(this); this.handleMouseDrag = this.handleMouseDrag.bind(this); this.handleMouseMove = this.handleMouseMove.bind(this); @@ -1716,32 +1718,20 @@ class Grid extends PureComponent { } /** - * Handle a key down event from the keyboard. Pass the event to the registered keyboard handlers until one handles it. - * @param event Keyboard event + * Notify all of the keyboard handlers for this grid of a keyboard event. + * @param functionName The name of the function in the keyboard handler to call + * @param event The keyboard event to notify */ - handleKeyDown(event: GridKeyboardEvent): void { - const keyHandlers = this.getKeyHandlers(); - for (let i = 0; i < keyHandlers.length; i += 1) { - const keyHandler = keyHandlers[i]; - const result = keyHandler.onDown(event, this); - if (result !== false) { - const options = result as EventHandlerResultOptions; - if (options?.stopPropagation ?? true) event.stopPropagation(); - if (options?.preventDefault ?? true) event.preventDefault(); - break; - } - } - } - - /** - * Handle a key up event from the keyboard. Pass the event to the registered keyboard handlers until one handles it. - * @param event Keyboard event - */ - handleKeyUp(event: GridKeyboardEvent): void { + notifyKeyboardHandlers( + functionName: GridKeyHandlerFunctionName, + event: GridKeyboardEvent + ): void { const keyHandlers = this.getKeyHandlers(); for (let i = 0; i < keyHandlers.length; i += 1) { const keyHandler = keyHandlers[i]; - const result = keyHandler.onUp(event, this); + const result = + keyHandler[functionName] != null && + keyHandler[functionName](event, this); if (result !== false) { const options = result as EventHandlerResultOptions; if (options?.stopPropagation ?? true) event.stopPropagation(); @@ -2247,8 +2237,8 @@ class Grid extends PureComponent { onClick={this.handleClick} onContextMenu={this.handleContextMenu} onDoubleClick={this.handleDoubleClick} - onKeyDown={this.handleKeyDown} - onKeyUp={this.handleKeyUp} + onKeyDown={e => this.notifyKeyboardHandlers('onDown', e)} + onKeyUp={e => this.notifyKeyboardHandlers('onUp', e)} onMouseDown={this.handleMouseDown} onMouseMove={this.handleMouseMove} onMouseLeave={this.handleMouseLeave} diff --git a/packages/grid/src/KeyHandler.ts b/packages/grid/src/KeyHandler.ts index 0d02432660..f6216f5adf 100644 --- a/packages/grid/src/KeyHandler.ts +++ b/packages/grid/src/KeyHandler.ts @@ -15,6 +15,8 @@ import type Grid from './Grid'; */ export type GridKeyboardEvent = KeyboardEvent | React.KeyboardEvent; +export type GridKeyHandlerFunctionName = 'onDown' | 'onUp'; + export class KeyHandler { order: number; diff --git a/packages/iris-grid/src/IrisGrid.test.tsx b/packages/iris-grid/src/IrisGrid.test.tsx index d255416fe8..059e8fb9ba 100644 --- a/packages/iris-grid/src/IrisGrid.test.tsx +++ b/packages/iris-grid/src/IrisGrid.test.tsx @@ -80,7 +80,10 @@ function makeComponent( function keyDown(key, component, extraArgs?) { const args = { key, ...extraArgs }; - component.grid.handleKeyDown(new KeyboardEvent('keydown', args)); + component.grid.notifyKeyboardHandlers( + 'onDown', + new KeyboardEvent('keydown', args) + ); } it('renders without crashing', () => { diff --git a/packages/iris-grid/src/IrisGrid.tsx b/packages/iris-grid/src/IrisGrid.tsx index 3d197bb653..db33aa6db4 100644 --- a/packages/iris-grid/src/IrisGrid.tsx +++ b/packages/iris-grid/src/IrisGrid.tsx @@ -700,16 +700,12 @@ export class IrisGrid extends Component { columnHeaderGroups, } = props; + const { dh } = model; const keyHandlers: KeyHandler[] = [ new ReverseKeyHandler(this), new ClearFilterKeyHandler(this), ]; - if (canCopy) { - keyHandlers.push(new CopyKeyHandler(this)); - } - const { dh } = model; - const mouseHandlers = [ - new IrisGridCopyCellMouseHandler(this), + const mouseHandlers: GridMouseHandler[] = [ new IrisGridCellOverflowMouseHandler(this), new IrisGridRowTreeMouseHandler(this), new IrisGridTokenMouseHandler(this), @@ -721,7 +717,10 @@ export class IrisGrid extends Component { new IrisGridDataSelectMouseHandler(this), new PendingMouseHandler(this), ]; - + if (canCopy) { + keyHandlers.push(new CopyKeyHandler(this)); + mouseHandlers.push(new IrisGridCopyCellMouseHandler(this)); + } const movedColumns = movedColumnsProp.length > 0 ? movedColumnsProp @@ -2028,15 +2027,23 @@ export class IrisGrid extends Component { } } - copyColumnHeader(columnIndex: GridRangeIndex): void { + copyColumnHeader(columnIndex: GridRangeIndex, columnDepth = 0): void { if (columnIndex === null) { return; } - const { model } = this.props; + const { canCopy } = this.props; + const { movedColumns } = this.state; - copyToClipboard(model.textForColumnHeader(columnIndex) ?? '').catch(e => - log.error('Unable to copy header', e) - ); + if (canCopy) { + const copyOperation = { + columnIndex, + columnDepth, + movedColumns, + }; + this.setState({ copyOperation }); + } else { + log.error('Attempted copyColumnHeader for user without copy permission.'); + } } /** diff --git a/packages/iris-grid/src/IrisGridCopyHandler.tsx b/packages/iris-grid/src/IrisGridCopyHandler.tsx index f87b87b1af..e00215889c 100644 --- a/packages/iris-grid/src/IrisGridCopyHandler.tsx +++ b/packages/iris-grid/src/IrisGridCopyHandler.tsx @@ -27,15 +27,37 @@ type Values = T[keyof T]; type ButtonStateType = Values; -export type CopyOperation = { +type CommonCopyOperation = { + movedColumns: readonly MoveOperation[]; + error?: string; +}; + +export type CopyRangesOperation = CommonCopyOperation & { ranges: readonly GridRange[]; includeHeaders: boolean; formatValues?: boolean; - movedColumns: readonly MoveOperation[]; userColumnWidths: ModelSizeMap; - error?: string; }; +export type CopyHeaderOperation = CommonCopyOperation & { + columnIndex: number; + columnDepth: number; +}; + +export type CopyOperation = CopyRangesOperation | CopyHeaderOperation; + +function isCopyRangesOperation( + copyOperation: CopyOperation +): copyOperation is CopyRangesOperation { + return (copyOperation as CopyRangesOperation).ranges != null; +} + +function isCopyHeaderOperation( + copyOperation: CopyOperation +): copyOperation is CopyHeaderOperation { + return (copyOperation as CopyHeaderOperation).columnIndex != null; +} + interface IrisGridCopyHandlerProps { model: IrisGridModel; copyOperation: CopyOperation; @@ -75,8 +97,11 @@ class IrisGridCopyHandler extends Component< // Large copy operation, confirmation required CONFIRMATION_REQUIRED: 'CONFIRMATION_REQUIRED', - // Fetch is currently in progress - FETCH_IN_PROGRESS: 'FETCH_IN_PROGRESS', + // Fetch is currently in progress for copy ranges operation + FETCH_RANGES_IN_PROGRESS: 'FETCH_RANGES_IN_PROGRESS', + + // Fetch is currently in progress for copy header operation + FETCH_HEADER_IN_PROGRESS: 'FETCH_HEADER_IN_PROGRESS', // There was an error fetching the data FETCH_ERROR: 'FETCH_ERROR', @@ -111,8 +136,10 @@ class IrisGridCopyHandler extends Component< return `Fetched ${rowCount.toLocaleString()} rows!`; case IrisGridCopyHandler.COPY_STATES.FETCH_ERROR: return 'Unable to copy data.'; - case IrisGridCopyHandler.COPY_STATES.FETCH_IN_PROGRESS: + case IrisGridCopyHandler.COPY_STATES.FETCH_RANGES_IN_PROGRESS: return `Fetching ${rowCount.toLocaleString()} rows for clipboard...`; + case IrisGridCopyHandler.COPY_STATES.FETCH_HEADER_IN_PROGRESS: + return 'Fetching header for clipboard...'; case IrisGridCopyHandler.COPY_STATES.DONE: return 'Copied to Clipboard!'; default: @@ -186,7 +213,7 @@ class IrisGridCopyHandler extends Component< return; } - const { ranges, error } = copyOperation; + const { error } = copyOperation; if (error != null) { log.debug('Showing copy error', error); this.setState({ @@ -198,18 +225,23 @@ class IrisGridCopyHandler extends Component< return; } - const rowCount = GridRange.rowCount(ranges); + this.setState({ isShown: true, error: undefined }); - this.setState({ rowCount, isShown: true, error: undefined }); + if (isCopyRangesOperation(copyOperation)) { + const { ranges } = copyOperation; + const rowCount = GridRange.rowCount(ranges); + this.setState({ rowCount }); - if (rowCount > IrisGridCopyHandler.NO_PROMPT_THRESHOLD) { - this.setState({ - buttonState: IrisGridCopyHandler.BUTTON_STATES.COPY, - copyState: IrisGridCopyHandler.COPY_STATES.CONFIRMATION_REQUIRED, - }); - } else { - this.startFetch(); + if (rowCount > IrisGridCopyHandler.NO_PROMPT_THRESHOLD) { + this.setState({ + buttonState: IrisGridCopyHandler.BUTTON_STATES.COPY, + copyState: IrisGridCopyHandler.COPY_STATES.CONFIRMATION_REQUIRED, + }); + return; + } } + + this.startFetch(); } stopCopy(): void { @@ -278,39 +310,58 @@ class IrisGridCopyHandler extends Component< this.setState({ buttonState: IrisGridCopyHandler.BUTTON_STATES.FETCH_IN_PROGRESS, - copyState: IrisGridCopyHandler.COPY_STATES.FETCH_IN_PROGRESS, + copyState: IrisGridCopyHandler.COPY_STATES.FETCH_RANGES_IN_PROGRESS, }); const { model, copyOperation } = this.props; - const { - ranges, - includeHeaders, - userColumnWidths, - movedColumns, - formatValues, - } = copyOperation; - log.debug('startFetch', ranges); - - const hiddenColumns = IrisGridUtils.getHiddenColumns(userColumnWidths); - let modelRanges = GridUtils.getModelRanges(ranges, movedColumns); - if (hiddenColumns.length > 0) { - const subtractRanges = hiddenColumns.map(GridRange.makeColumn); - modelRanges = GridRange.subtractRangesFromRanges( - modelRanges, - subtractRanges + + if (isCopyHeaderOperation(copyOperation)) { + const { columnIndex, columnDepth, movedColumns } = copyOperation; + log.debug('startFetch copyHeader', columnIndex, columnDepth); + + const modelIndex = GridUtils.getModelIndex(columnIndex, movedColumns); + const copyText = model.textForColumnHeader(modelIndex, columnDepth); + if (copyText === undefined) { + this.fetchPromise = undefined; + this.setState({ + error: 'Invalid column header selected.', + copyState: IrisGridCopyHandler.COPY_STATES.DONE, + }); + return; + } + this.fetchPromise = PromiseUtils.makeCancelable(copyText); + } else { + const { + ranges, + includeHeaders, + userColumnWidths, + movedColumns, + formatValues, + } = copyOperation; + log.debug('startFetch copyRanges', ranges); + + const hiddenColumns = IrisGridUtils.getHiddenColumns(userColumnWidths); + let modelRanges = GridUtils.getModelRanges(ranges, movedColumns); + if (hiddenColumns.length > 0) { + const subtractRanges = hiddenColumns.map(GridRange.makeColumn); + modelRanges = GridRange.subtractRangesFromRanges( + modelRanges, + subtractRanges + ); + } + + // Remove the hidden columns from the snapshot + const formatValue = + formatValues != null && formatValues + ? (value: unknown, column: Column) => + model.displayString(value, column.type, column.name) + : (value: unknown) => `${value}`; + + this.fetchPromise = PromiseUtils.makeCancelable( + model.textSnapshot(modelRanges, includeHeaders, formatValue) ); } - // Remove the hidden columns from the snapshot - const formatValue = - formatValues != null && formatValues - ? (value: unknown, column: Column) => - model.displayString(value, column.type, column.name) - : (value: unknown) => `${value}`; - - this.fetchPromise = PromiseUtils.makeCancelable( - model.textSnapshot(modelRanges, includeHeaders, formatValue) - ); try { const text = await this.fetchPromise; this.fetchPromise = undefined; diff --git a/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts b/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts index 96b022e58a..8359dd639c 100644 --- a/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts +++ b/packages/iris-grid/src/mousehandlers/IrisGridCopyCellMouseHandler.ts @@ -4,6 +4,7 @@ import { GridPoint, EventHandlerResult, GridMouseEvent, + GridRange, } from '@deephaven/grid'; import { ContextActionUtils } from '@deephaven/components'; import IrisGrid from '../IrisGrid'; @@ -12,7 +13,7 @@ class IrisGridCopyCellMouseHandler extends GridMouseHandler { private irisGrid: IrisGrid; constructor(irisGrid: IrisGrid) { - super(); + super(250); this.irisGrid = irisGrid; this.cursor = null; @@ -30,9 +31,14 @@ class IrisGridCopyCellMouseHandler extends GridMouseHandler { ) { this.cursor = null; if (gridPoint.columnHeaderDepth !== undefined) { - this.irisGrid.copyColumnHeader(gridPoint.column); + this.irisGrid.copyColumnHeader( + gridPoint.column, + gridPoint.columnHeaderDepth + ); } else { - this.irisGrid.copyCell(gridPoint.column, gridPoint.row); + this.irisGrid.copyRanges([ + GridRange.makeCell(gridPoint.column, gridPoint.row), + ]); } return true; } From 1eb45ec2cea64f2ef5d970e7aa54bacf84e1466b Mon Sep 17 00:00:00 2001 From: georgecwan Date: Fri, 22 Dec 2023 09:37:47 -0800 Subject: [PATCH 5/5] Add unit test to IrisGridCopyHander --- packages/grid/src/Grid.tsx | 14 +++++- .../src/IrisGridCopyHandler.test.tsx | 49 +++++++++++++++---- .../iris-grid/src/IrisGridCopyHandler.tsx | 15 ++++-- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/packages/grid/src/Grid.tsx b/packages/grid/src/Grid.tsx index b3529dc81c..04cdbf2ef5 100644 --- a/packages/grid/src/Grid.tsx +++ b/packages/grid/src/Grid.tsx @@ -346,6 +346,8 @@ class Grid extends PureComponent { this.handleEditCellCommit = this.handleEditCellCommit.bind(this); this.handleDoubleClick = this.handleDoubleClick.bind(this); this.notifyKeyboardHandlers = this.notifyKeyboardHandlers.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleKeyUp = this.handleKeyUp.bind(this); this.handleMouseDown = this.handleMouseDown.bind(this); this.handleMouseDrag = this.handleMouseDrag.bind(this); this.handleMouseMove = this.handleMouseMove.bind(this); @@ -1741,6 +1743,14 @@ class Grid extends PureComponent { } } + handleKeyDown(event: GridKeyboardEvent): void { + this.notifyKeyboardHandlers('onDown', event); + } + + handleKeyUp(event: GridKeyboardEvent): void { + this.notifyKeyboardHandlers('onUp', event); + } + /** * Notify all of the mouse handlers for this grid of a mouse event. * @param functionName The name of the function in the mouse handler to call @@ -2237,8 +2247,8 @@ class Grid extends PureComponent { onClick={this.handleClick} onContextMenu={this.handleContextMenu} onDoubleClick={this.handleDoubleClick} - onKeyDown={e => this.notifyKeyboardHandlers('onDown', e)} - onKeyUp={e => this.notifyKeyboardHandlers('onUp', e)} + onKeyDown={this.handleKeyDown} + onKeyUp={this.handleKeyUp} onMouseDown={this.handleMouseDown} onMouseMove={this.handleMouseMove} onMouseLeave={this.handleMouseLeave} diff --git a/packages/iris-grid/src/IrisGridCopyHandler.test.tsx b/packages/iris-grid/src/IrisGridCopyHandler.test.tsx index d8b9ebf546..cb4bab3d91 100644 --- a/packages/iris-grid/src/IrisGridCopyHandler.test.tsx +++ b/packages/iris-grid/src/IrisGridCopyHandler.test.tsx @@ -5,7 +5,12 @@ import { GridTestUtils } from '@deephaven/grid'; import { copyToClipboard } from '@deephaven/utils'; import dh from '@deephaven/jsapi-shim'; import IrisGridTestUtils from './IrisGridTestUtils'; -import IrisGridCopyHandler, { CopyOperation } from './IrisGridCopyHandler'; +import IrisGridCopyHandler, { + CopyOperation, + CopyHeaderOperation, + CopyRangesOperation, +} from './IrisGridCopyHandler'; +import IrisGridProxyModel from './IrisGridProxyModel'; jest.mock('@deephaven/utils', () => ({ ...jest.requireActual('@deephaven/utils'), @@ -29,12 +34,12 @@ function makeSnapshotFn() { return jest.fn(() => Promise.resolve(DEFAULT_EXPECTED_TEXT)); } -function makeCopyOperation( +function makeCopyRangesOperation( ranges = GridTestUtils.makeRanges(), includeHeaders = false, movedColumns = [], userColumnWidths = IrisGridTestUtils.makeUserColumnWidths() -): CopyOperation { +): CopyRangesOperation { return { ranges, includeHeaders, @@ -43,16 +48,29 @@ function makeCopyOperation( }; } +function makeCopyHeaderOperation( + columnIndex = 0, + columnDepth = 0, + movedColumns = [] +): CopyHeaderOperation { + return { + columnIndex, + columnDepth, + movedColumns, + }; +} + function makeModel() { const model = irisGridTestUtils.makeModel(); model.textSnapshot = makeSnapshotFn(); + model.textForColumnHeader = jest.fn((c: number) => c.toString()); return model; } function mountCopySelection({ model = makeModel(), - copyOperation = makeCopyOperation(), -} = {}) { + copyOperation = makeCopyRangesOperation(), +}: { model?: IrisGridProxyModel; copyOperation?: CopyOperation } = {}) { return render( ); @@ -66,9 +84,20 @@ it('renders without crashing', () => { mountCopySelection(); }); +it('copies column header', async () => { + const copyOperation = makeCopyHeaderOperation(); + const model = makeModel(); + mountCopySelection({ copyOperation, model }); + screen.getByRole('progressbar', { hidden: true }); + screen.getByText('Fetching header for clipboard...'); + expect(model.textForColumnHeader).toHaveBeenCalled(); + + await waitFor(() => expect(copyToClipboard).toHaveBeenCalledWith('0')); +}); + it('copies immediately if less than 10,000 rows of data', async () => { const ranges = GridTestUtils.makeRanges(1, 10000); - const copyOperation = makeCopyOperation(ranges); + const copyOperation = makeCopyRangesOperation(ranges); const model = makeModel(); mountCopySelection({ copyOperation, model }); screen.getByRole('progressbar', { hidden: true }); @@ -84,7 +113,7 @@ it('prompts to copy if more than 10,000 rows of data', async () => { const user = userEvent.setup({ delay: null }); const model = makeModel(); const ranges = GridTestUtils.makeRanges(1, 10001); - const copyOperation = makeCopyOperation(ranges); + const copyOperation = makeCopyRangesOperation(ranges); mountCopySelection({ copyOperation, model }); const copyBtn = screen.getByText('Copy'); expect(copyBtn).toBeTruthy(); @@ -112,7 +141,7 @@ it('shows click to copy if async copy fails', async () => { mockedCopyToClipboard.mockReturnValueOnce(Promise.reject(error)); const ranges = GridTestUtils.makeRanges(); - const copyOperation = makeCopyOperation(ranges); + const copyOperation = makeCopyRangesOperation(ranges); mountCopySelection({ copyOperation }); await waitFor(() => @@ -138,7 +167,7 @@ it('shows click to copy if async copy fails', async () => { it('retry option available if fetching fails', async () => { const user = userEvent.setup({ delay: null }); const ranges = GridTestUtils.makeRanges(); - const copyOperation = makeCopyOperation(ranges); + const copyOperation = makeCopyRangesOperation(ranges); const model = makeModel(); model.textSnapshot = jest.fn(() => Promise.reject()); @@ -166,7 +195,7 @@ it('shows an error if the copy fails permissions', async () => { mockedCopyToClipboard.mockReturnValueOnce(Promise.reject(error)); const ranges = GridTestUtils.makeRanges(); - const copyOperation = makeCopyOperation(ranges); + const copyOperation = makeCopyRangesOperation(ranges); mountCopySelection({ copyOperation }); await waitFor(() => diff --git a/packages/iris-grid/src/IrisGridCopyHandler.tsx b/packages/iris-grid/src/IrisGridCopyHandler.tsx index e00215889c..0310b82ec6 100644 --- a/packages/iris-grid/src/IrisGridCopyHandler.tsx +++ b/packages/iris-grid/src/IrisGridCopyHandler.tsx @@ -308,17 +308,17 @@ class IrisGridCopyHandler extends Component< async startFetch(): Promise { this.stopFetch(); - this.setState({ - buttonState: IrisGridCopyHandler.BUTTON_STATES.FETCH_IN_PROGRESS, - copyState: IrisGridCopyHandler.COPY_STATES.FETCH_RANGES_IN_PROGRESS, - }); - const { model, copyOperation } = this.props; if (isCopyHeaderOperation(copyOperation)) { const { columnIndex, columnDepth, movedColumns } = copyOperation; log.debug('startFetch copyHeader', columnIndex, columnDepth); + this.setState({ + buttonState: IrisGridCopyHandler.BUTTON_STATES.FETCH_IN_PROGRESS, + copyState: IrisGridCopyHandler.COPY_STATES.FETCH_HEADER_IN_PROGRESS, + }); + const modelIndex = GridUtils.getModelIndex(columnIndex, movedColumns); const copyText = model.textForColumnHeader(modelIndex, columnDepth); if (copyText === undefined) { @@ -340,6 +340,11 @@ class IrisGridCopyHandler extends Component< } = copyOperation; log.debug('startFetch copyRanges', ranges); + this.setState({ + buttonState: IrisGridCopyHandler.BUTTON_STATES.FETCH_IN_PROGRESS, + copyState: IrisGridCopyHandler.COPY_STATES.FETCH_RANGES_IN_PROGRESS, + }); + const hiddenColumns = IrisGridUtils.getHiddenColumns(userColumnWidths); let modelRanges = GridUtils.getModelRanges(ranges, movedColumns); if (hiddenColumns.length > 0) {