From 7b21c0e15d3155b1f200a05301e9741e05e79ada Mon Sep 17 00:00:00 2001 From: Adam Obuchowicz Date: Mon, 27 Jan 2025 11:19:13 +0100 Subject: [PATCH] WIP --- .../GraphEditor/widgets/WidgetTableEditor.vue | 16 +- .../widgets/WidgetTableEditor/TableHeader.vue | 10 +- .../widgets/WidgetTableEditor/editHandlers.ts | 23 +-- .../WidgetTableEditor/tableInputArgument.ts | 174 +++++++++++------- .../components/shared/AgGridTableView.vue | 7 +- 5 files changed, 143 insertions(+), 87 deletions(-) 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 98c2e3d0bbfa..da118fefa7f1 100644 --- a/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor.vue +++ b/app/gui/src/project-view/components/GraphEditor/widgets/WidgetTableEditor.vue @@ -25,7 +25,7 @@ import type { ProcessDataFromClipboardParams, RowDragEndEvent, } from 'ag-grid-enterprise' -import { ComponentInstance, computed, proxyRefs, ref } from 'vue' +import { ComponentInstance, computed, proxyRefs, ref, watch, watchEffect } from 'vue' import type { ComponentExposed } from 'vue-component-type-helpers' import { z } from 'zod' import TableHeader, { HeaderParams } from './WidgetTableEditor/TableHeader.vue' @@ -67,7 +67,7 @@ const { rowData, columnDefs, moveColumn, moveRow, pasteFromClipboard } = useTabl // === Edit Handlers === -const { editedCell, gridEventHandlers, headerEventHandlers } = useTableEditHandler( +const { handler, editedCell, gridEventHandlers, headerEventHandlers } = useTableEditHandler( () => grid.value?.gridApi, columnDefs, (hooks) => { @@ -88,6 +88,18 @@ const { editedCell, gridEventHandlers, headerEventHandlers } = useTableEditHandl }, ) +watch( + () => props.input.value, + () => grid.value?.gridApi?.refreshCells(), +) + +// const rowDataSuppressed = ref([]) +// watchEffect(() => { +// if (!handler.isActive() || true) { +// rowDataSuppressed.value = rowData.value +// } +// }) + // === Resizing === const graphNav = injectGraphNavigator() 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 3e6171485924..20860e0d095f 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 @@ -56,7 +56,9 @@ function emitEditEnd() { watch(inputElement, (newVal, oldVal) => { if (newVal != null) { - // Whenever input field appears, focus and select text + // Whenever input field appears, put text, focus and select + // We don't do that through props, because we don't want updates. + newVal.value = props.displayName newVal.focus() newVal.select() } @@ -115,12 +117,14 @@ function onMouseRightClick(event: MouseEvent) { v-if="editing" ref="inputElement" class="ag-input-field-input ag-text-field-input" - :value="displayName" - @change="acceptNewName" @keydown.arrow-left.stop @keydown.arrow-right.stop @keydown.arrow-up.stop @keydown.arrow-down.stop + @keydown.tab.prevent="{ + // We prevent default, because switching edit on tab is handled by the widget edit + // handlers + }" /> Array.from(columns.value, (col) => ({ id: col.id, name: col.name.rawTextContent })), + (a, b) => arrayEquals(a, b, (a, b) => a.id === b.id && a.name === b.name), + ) + const rowCount = computed(() => columns.value.reduce((soFar, col) => Math.max(soFar, col.data.length), 0), ) @@ -183,7 +190,7 @@ export function useTableInputArgument( function mayAddNewColumn( rowCount_: number = rowCount.value, - colCount: number = columns.value.length, + colCount: number = columnHeaders.value.length, ): boolean { return rowCount_ * (colCount + 1) <= CELLS_LIMIT } @@ -278,59 +285,70 @@ export function useTableInputArgument( }, }) - const newColumnDef = computed(() => ({ - colId: NEW_COLUMN_ID, - headerName: '', - valueGetter: () => null, - editable: false, - resizable: false, - suppressNavigable: true, - width: 40, - maxWidth: 40, - headerComponentParams: { - columnParams: { - type: 'newColumn', - enabled: mayAddNewColumn(), - newColumnRequested: () => { - const edit = graph.startEdit() - fixColumns(edit) - addColumn(edit, `${DEFAULT_COLUMN_PREFIX}${columns.value.length + 1}`) - onUpdate({ edit, directInteraction: true }) + const newColumnDef = computed( + () => ( + console.error('Updating newColumnDef'), + { + colId: NEW_COLUMN_ID, + headerName: '', + valueGetter: () => null, + editable: false, + resizable: false, + suppressNavigable: true, + width: 40, + maxWidth: 40, + headerComponentParams: { + columnParams: { + type: 'newColumn', + enabled: mayAddNewColumn(), + newColumnRequested: () => { + const edit = graph.startEdit() + fixColumns(edit) + addColumn(edit, `${DEFAULT_COLUMN_PREFIX}${columns.value.length + 1}`) + onUpdate({ edit, directInteraction: true }) + }, + }, }, - }, - }, - mainMenuItems: ['autoSizeThis', 'autoSizeAll'], - contextMenuItems: [removeRowMenuItem], - lockPosition: 'right', - cellClass: 'newColumnCell', - })) - - const rowIndexColumnDef = computed(() => ({ - colId: ROW_INDEX_COLUMN_ID, - headerName: ROW_INDEX_HEADER, - valueGetter: ({ data }: { data: RowData | undefined }) => data?.index, - editable: false, - resizable: false, - suppressNavigable: true, - headerComponentParams: { columnParams: { type: 'rowIndexColumn' } }, - mainMenuItems: ['autoSizeThis', 'autoSizeAll'], - contextMenuItems: [removeRowMenuItem], - cellClass: 'rowIndexCell', - lockPosition: 'left', - rowDrag: ({ data }: { data: RowData | undefined }) => - data?.index != null && data.index < rowCount.value, - })) + mainMenuItems: ['autoSizeThis', 'autoSizeAll'], + contextMenuItems: [removeRowMenuItem], + lockPosition: 'right', + cellClass: 'newColumnCell', + } + ), + ) + + const rowIndexColumnDef = computed( + () => ( + console.error('Updating rowIndexColumnDef'), + { + colId: ROW_INDEX_COLUMN_ID, + headerName: ROW_INDEX_HEADER, + valueGetter: ({ data }: { data: RowData | undefined }) => data?.index, + editable: false, + resizable: false, + suppressNavigable: true, + headerComponentParams: { columnParams: { type: 'rowIndexColumn' } }, + mainMenuItems: ['autoSizeThis', 'autoSizeAll'], + contextMenuItems: [removeRowMenuItem], + cellClass: 'rowIndexCell', + lockPosition: 'left', + rowDrag: ({ data }: { data: RowData | undefined }) => + data?.index != null && data.index < rowCount.value, + } + ), + ) const columnDefs = computed(() => { + console.error('Updating columnDefs') const cols: ColumnDef[] = Array.from( - columns.value, - (col) => + columnHeaders.value, + (col, i) => ({ colId: col.id, - headerName: col.name.rawTextContent, + headerName: col.name, valueGetter: ({ data }: { data: RowData | undefined }) => { if (data == null) return undefined - const ast = toValue(input).value.module.tryGet(data.cells[col.data.id]) + const ast = toValue(input).value.module.tryGet(data.cells[columns.value[i]!.data.id]) if (ast == null) return null const value = cellValueConversion.astToAgGrid(ast as Ast.Expression) if (!value.ok) { @@ -342,15 +360,16 @@ export function useTableInputArgument( return value.value }, valueSetter: ({ data, newValue }: { data: RowData; newValue: any }): boolean => { - const astId = data?.cells[col.data.id] + const astId = data?.cells[col.id] const edit = graph.startEdit() fixColumns(edit) if (data.index === rowCount.value) { - addRow(edit, (colId) => (colId === col.data.id ? newValue : null)) + addRow(edit, (colId) => (colId === col.id ? newValue : null)) } else { const newValueAst = convertWithImport(newValue, edit) if (astId != null) edit.replaceValue(astId, newValueAst) - else edit.getVersion(col.data).set(data.index, newValueAst) + // TODO + else edit.getVersion(columns.value[i]!.data).set(data.index, newValueAst) } onUpdate({ edit, directInteraction: true }) return true @@ -361,7 +380,7 @@ export function useTableInputArgument( nameSetter: (newName: string) => { const edit = graph.startEdit() fixColumns(edit) - edit.getVersion(col.name).setRawTextContent(newName) + edit.getVersion(columns.value[i]!.name).setRawTextContent(newName) onUpdate({ edit, directInteraction: true }) }, }, @@ -386,25 +405,42 @@ export function useTableInputArgument( return cols }) - const rowData = computed(() => { - const rows: RowData[] = [] - for (const col of columns.value) { - for (const [rowIndex, value] of col.data.enumerate()) { - const row: RowData = rows.at(rowIndex) ?? { index: rowIndex, cells: {} } - assert(rowIndex <= rows.length) - if (rowIndex === rows.length) { - rows.push(row) - } - if (value?.id) { - row.cells[col.data.id] = value?.id + const rowData = ref([]) + + watch( + columns, + (columns) => { + console.log('Update rowData if needed') + for (const col of columns) { + for (const [rowIndex, value] of col.data.enumerate()) { + const row: RowData = rowData.value.at(rowIndex) ?? { index: rowIndex, cells: {} } + assert(rowIndex <= rowData.value.length) + if (rowIndex === rowData.value.length) { + rowData.value.push(row) + } + if (value?.id && row.cells[col.data.id] != value.id) { + row.cells[col.data.id] = value.id + } else if (!value?.id && row.cells[col.data.id]) { + delete row.cells[col.data.id] + } } } - } - if (mayAddNewRow()) { - rows.push({ index: rows.length, cells: {} }) - } - return rows - }) + const expectedRowNumber = rowCount.value + (mayAddNewRow() ? 1 : 0) + if (expectedRowNumber < rowData.value.length) { + rowData.value.splice(expectedRowNumber) + } else if (expectedRowNumber > rowData.value.length) { + const toAdd = expectedRowNumber - rowData.value.length + rowData.value.splice( + rowData.value.length, + 0, + ...Array(toAdd).fill({ index: rowCount.value, cells: {} }), + ) + } + }, + { immediate: true }, + ) + + watch(rowData, () => console.error('Updating rowData'), { flush: 'sync' }) const nothingImport = computed(() => requiredImportsByFQN(suggestions, NOTHING_PATH, true)) diff --git a/app/gui/src/project-view/components/shared/AgGridTableView.vue b/app/gui/src/project-view/components/shared/AgGridTableView.vue index 70af524a0a4b..fb30d20562e0 100644 --- a/app/gui/src/project-view/components/shared/AgGridTableView.vue +++ b/app/gui/src/project-view/components/shared/AgGridTableView.vue @@ -75,6 +75,7 @@ import type { GetRowIdFunc, GridApi, GridReadyEvent, + ICellEditorComp, IHeaderComp, IHeaderParams, MenuItemDef, @@ -317,14 +318,15 @@ const vueHost = new VueHost() const mappedComponents = computed(() => { if (!props.components) return - const retval: Record IHeaderComp> = {} + const retval: Record IHeaderComp | ICellEditorComp> = {} for (const [key, comp] of Object.entries(props.components)) { class ComponentWrapper implements IHeaderComp { private readonly container: HTMLElement = document.createElement('div') private handle: VueComponentHandle | undefined init(params: IHeaderParams) { - this.handle = vueHost.register(h(comp, params), this.container) + console.log('init') + this.handle = vueHost.register(h(comp, params), this.container, params.column.getColId()) } getGui() { @@ -337,6 +339,7 @@ const mappedComponents = computed(() => { } destroy() { + console.log('destroy') this.handle?.unregister() } }