From 08fd9213e4ec34bec76550e503b70fee9cd4bca5 Mon Sep 17 00:00:00 2001 From: Adam Obuchowicz Date: Fri, 25 Oct 2024 14:26:21 +0200 Subject: [PATCH] Table Input Widget: Add column with plus (#11388) Fixes #10863 (The `Column #3` in a screenshot below is just created and actually existing). ![image](https://github.com/user-attachments/assets/995d1a85-eb04-4b8d-bf23-b47ea61185e4) --- CHANGELOG.md | 3 + app/gui/e2e/project-view/rightPanel.spec.ts | 11 +- .../project-view/tableVisualisation.spec.ts | 1 + app/gui/e2e/project-view/widgets.spec.ts | 29 ++-- .../GraphEditor/widgets/WidgetTableEditor.vue | 18 ++- .../widgets/WidgetTableEditor/TableHeader.vue | 75 +++++++--- .../__tests__/tableNewArgument.test.ts | 132 +++++++++--------- .../WidgetTableEditor/tableNewArgument.ts | 64 +++++---- .../components/TooltipTrigger.vue | 1 - app/gui/src/project-view/providers/index.ts | 5 +- 10 files changed, 203 insertions(+), 136 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b397ec7fa7..cbfc042aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,15 @@ - [Copying and pasting in Table Editor Widget now works properly][11332] - [Fix invisible selection in Table Input Widget][11358] - [Enable cloud file browser in local projects][11383] +- [Changed the way of adding new column in Table Input Widget][11388]. The + "virtual column" is replaced with an explicit (+) button. [11151]: https://github.com/enso-org/enso/pull/11151 [11271]: https://github.com/enso-org/enso/pull/11271 [11332]: https://github.com/enso-org/enso/pull/11332 [11358]: https://github.com/enso-org/enso/pull/11358 [11383]: https://github.com/enso-org/enso/pull/11383 +[11388]: https://github.com/enso-org/enso/pull/11388 #### Enso Standard Library diff --git a/app/gui/e2e/project-view/rightPanel.spec.ts b/app/gui/e2e/project-view/rightPanel.spec.ts index d8cc94aa46..474231e5ec 100644 --- a/app/gui/e2e/project-view/rightPanel.spec.ts +++ b/app/gui/e2e/project-view/rightPanel.spec.ts @@ -26,15 +26,16 @@ test('Doc panel focus (regression #10471)', async ({ page }) => { await page.keyboard.press(`${CONTROL_KEY}+D`) await page.keyboard.press(`${CONTROL_KEY}+\``) await expect(locate.rightDock(page)).toBeVisible() - await expect(locate.bottomDock(page)).toBeVisible() + const codeEditor = page.locator('.CodeEditor') + await expect(codeEditor).toBeVisible() // Focus code editor. - await locate.bottomDock(page).click() + await codeEditor.click() await page.evaluate(() => { - const codeEditor = (window as any).__codeEditorApi - const docStart = codeEditor.indexOf('The main method') - codeEditor.placeCursor(docStart + 8) + const codeEditorApi = (window as any).__codeEditorApi + const docStart = codeEditorApi.indexOf('The main method') + codeEditorApi.placeCursor(docStart + 8) }) await page.keyboard.press('Space') await page.keyboard.press('T') diff --git a/app/gui/e2e/project-view/tableVisualisation.spec.ts b/app/gui/e2e/project-view/tableVisualisation.spec.ts index 168ff37cc9..73fd06f10f 100644 --- a/app/gui/e2e/project-view/tableVisualisation.spec.ts +++ b/app/gui/e2e/project-view/tableVisualisation.spec.ts @@ -58,6 +58,7 @@ test('Copy from Table Visualization', async ({ page, context }) => { const node = await actions.createTableNode(page) const widget = node.locator('.WidgetTableEditor') await expect(widget).toBeVisible() + await widget.getByRole('button', { name: 'Add new column' }).click() await widget.locator('.ag-cell', { hasNotText: /0/ }).first().click() await page.keyboard.press('Control+V') await expect(widget.locator('.ag-cell')).toHaveText([ diff --git a/app/gui/e2e/project-view/widgets.spec.ts b/app/gui/e2e/project-view/widgets.spec.ts index 8c33e473f7..21a1b2697e 100644 --- a/app/gui/e2e/project-view/widgets.spec.ts +++ b/app/gui/e2e/project-view/widgets.spec.ts @@ -544,27 +544,32 @@ test('Table widget', async ({ page }) => { const node = await actions.createTableNode(page) const widget = node.locator('.WidgetTableEditor') await expect(widget).toBeVisible() - await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'New Column']) - await expect(widget.locator('.ag-header-cell-text', { hasText: 'New Column' })).toHaveClass( - /(?<=^| )virtualColumn(?=$| )/, - ) - // There are two cells, one with row number, second allowing creating first row and column - await expect(widget.locator('.ag-cell')).toHaveCount(2) + await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#']) + await expect(widget.getByRole('button', { name: 'Add new column' })).toExist() + await expect(widget.locator('.ag-cell')).toHaveText(['0', '']) + + // Create first column + await widget.getByRole('button', { name: 'Add new column' }).click() + await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'Column #1']) + await expect(widget.locator('.ag-cell')).toHaveText(['0', '', '']) // Putting first value - await widget.locator('.ag-cell', { hasNotText: '0' }).click() + await widget.locator('.ag-cell', { hasNotText: '0' }).first().click() await page.keyboard.type('Value') await page.keyboard.press('Enter') - // There will be new blank column and new blank row allowing adding new columns and rows - // (so 4 cells in total) - await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'Column #0', 'New Column']) + // There will be new blank row allowing adding new rows. await expect(widget.locator('.ag-cell')).toHaveText(['0', 'Value', '', '1', '', '']) // Renaming column - await widget.locator('.ag-header-cell-text', { hasText: 'Column #0' }).first().click() + await widget.locator('.ag-header-cell-text', { hasText: 'Column #1' }).first().click() await page.keyboard.type('Header') await page.keyboard.press('Enter') - await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'Header', 'New Column']) + await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'Header']) + + // Adding next column + await widget.getByRole('button', { name: 'Add new column' }).click() + await expect(widget.locator('.ag-header-cell-text')).toHaveText(['#', 'Header', 'Column #2']) + await expect(widget.locator('.ag-cell')).toHaveText(['0', 'Value', '', '', '1', '', '', '']) // Switching edit between cells and headers - check we will never edit two things at once. await expect(widget.locator('.ag-text-field-input')).toHaveCount(0) diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor.vue index 78dcedc52c..da5a17f3f2 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor.vue @@ -9,6 +9,7 @@ import { import ResizeHandles from '@/components/ResizeHandles.vue' import AgGridTableView from '@/components/shared/AgGridTableView.vue' import { injectGraphNavigator } from '@/providers/graphNavigator' +import { useTooltipRegistry } from '@/providers/tooltipState' import { Score, defineWidget, widgetProps } from '@/providers/widgetRegistry' import { WidgetEditHandler } from '@/providers/widgetRegistry/editHandler' import { useGraphStore } from '@/stores/graph' @@ -20,12 +21,13 @@ import '@ag-grid-community/styles/ag-theme-alpine.css' import type { CellEditingStartedEvent, CellEditingStoppedEvent, + ColDef, Column, ColumnMovedEvent, ProcessDataFromClipboardParams, RowDragEndEvent, } from 'ag-grid-enterprise' -import { computed, ref } from 'vue' +import { computed, markRaw, ref } from 'vue' import type { ComponentExposed } from 'vue-component-type-helpers' const props = defineProps(widgetProps(widgetDefinition)) @@ -178,14 +180,22 @@ function processDataFromClipboard({ data, api }: ProcessDataFromClipboardParams< // === Column Default Definition === -const defaultColDef = { +const tooltipRegistry = useTooltipRegistry() +const defaultColDef: ColDef = { editable: true, resizable: true, sortable: false, lockPinned: true, + menuTabs: ['generalMenuTab'], headerComponentParams: { - onHeaderEditingStarted: headerEditHandler.headerEditedInGrid.bind(headerEditHandler), - onHeaderEditingStopped: headerEditHandler.headerEditingStoppedInGrid.bind(headerEditHandler), + // TODO[ao]: we mark raw, because otherwise any change _inside_ tooltipRegistry causes the grid + // to be refreshed. Technically, shallowReactive should work here, but it does not, + // I don't know why + tooltipRegistry: markRaw(tooltipRegistry), + editHandlers: { + onHeaderEditingStarted: headerEditHandler.headerEditedInGrid.bind(headerEditHandler), + onHeaderEditingStopped: headerEditHandler.headerEditingStoppedInGrid.bind(headerEditHandler), + }, }, } diff --git a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor/TableHeader.vue b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor/TableHeader.vue index 8ffe338fef..710a48b8d9 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor/TableHeader.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor/TableHeader.vue @@ -1,22 +1,39 @@ @@ -25,17 +42,23 @@ const props = defineProps<{ params: IHeaderParams & HeaderParams }>() +/** Re-provide tooltipRegistry. See `tooltipRegistry` docs in {@link HeaderParams} */ +provideTooltipRegistry.provideConstructed(props.params.tooltipRegistry) + const editing = ref(false) const inputElement = ref() +const editHandlers = computed(() => + props.params.type === 'astColumn' ? props.params.editHandlers : undefined, +) watch(editing, (newVal) => { if (newVal) { - props.params.onHeaderEditingStarted?.((cancel: boolean) => { + editHandlers.value?.onHeaderEditingStarted?.((cancel: boolean) => { if (cancel) editing.value = false else acceptNewName() }) } else { - props.params.onHeaderEditingStopped?.() + editHandlers.value?.onHeaderEditingStopped?.() } }) @@ -48,33 +71,47 @@ watch(inputElement, (newVal, oldVal) => { }) function acceptNewName() { + if (editHandlers.value == null) { + console.error("Tried to accept header new name where it's not editable!") + return + } if (inputElement.value == null) { console.error('Tried to accept header new name without input element!') return } - props.params.nameSetter?.(inputElement.value.value) + editHandlers.value.nameSetter(inputElement.value.value) editing.value = false } -function onMouseClick() { - if (!editing.value && props.params.nameSetter != null) { +function onMouseClick(event: MouseEvent) { + if (!editing.value && props.params.type === 'astColumn') { editing.value = true + event.stopPropagation() } } function onMouseRightClick(event: MouseEvent) { if (!editing.value) { props.params.showColumnMenuAfterMouseClick(event) + event.preventDefault() + event.stopPropagation() } } -