diff --git a/packages/datalib/src/ChangeSet.ts b/packages/datalib/src/ChangeSet.ts new file mode 100644 index 000000000..7eacb9eab --- /dev/null +++ b/packages/datalib/src/ChangeSet.ts @@ -0,0 +1,99 @@ +import _ from 'lodash'; + +export interface ChangeSetItem { + pureName: string; + schemaName: string; + insertedRowIndex?: number; + condition?: { [column: string]: string }; + fields?: { [column: string]: string }; +} + +export interface ChangeSet { + inserts: ChangeSetItem[]; + updates: ChangeSetItem[]; + deletes: ChangeSetItem[]; +} + +export function createChangeSet(): ChangeSet { + return { + inserts: [], + updates: [], + deletes: [], + }; +} + +export interface ChangeSetFieldDefinition { + pureName: string; + schemaName: string; + uniqueName: string; + columnName: string; + insertedRowIndex?: number; + condition?: { [column: string]: string }; +} + +function findExistingChangeSetItem( + changeSet: ChangeSet, + definition: ChangeSetFieldDefinition +): [keyof ChangeSet, ChangeSetItem] { + if (definition.insertedRowIndex != null) { + return [ + 'inserts', + changeSet.inserts.find( + x => + x.pureName == definition.pureName && + x.schemaName == definition.schemaName && + x.insertedRowIndex == definition.insertedRowIndex + ), + ]; + } else { + return [ + 'updates', + changeSet.updates.find( + x => + x.pureName == definition.pureName && + x.schemaName == definition.schemaName && + _.isEqual(x.condition, definition.condition) + ), + ]; + } +} + +export function setChangeSetValue( + changeSet: ChangeSet, + definition: ChangeSetFieldDefinition, + value: string +): ChangeSet { + const [fieldName, existingItem] = findExistingChangeSetItem(changeSet, definition); + if (existingItem) { + return { + ...changeSet, + [fieldName]: changeSet[fieldName].map(item => + item == existingItem + ? { + ...item, + fields: { + ...item.fields, + [definition.uniqueName]: value, + }, + } + : item + ), + }; + } + + return { + ...changeSet, + [fieldName]: [ + ...changeSet[fieldName], + { + pureName: definition.pureName, + schemaName: definition.schemaName, + condition: definition.condition, + insertedRowIndex: definition.insertedRowIndex, + fields: { + [definition.uniqueName]: value, + }, + }, + ], + }; +} diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index 2d4c67c3c..ae30a973e 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -4,6 +4,7 @@ import { ForeignKeyInfo, TableInfo, ColumnInfo, DbType } from '@dbgate/types'; import { parseFilter, getFilterType } from '@dbgate/filterparser'; import { filterName } from './filterName'; import { Select, Expression } from '@dbgate/sqltree'; +import { ChangeSetFieldDefinition } from './ChangeSet'; export interface DisplayColumn { schemaName: string; @@ -47,6 +48,8 @@ export abstract class GridDisplay { ) {} abstract getPageQuery(offset: number, count: number): string; columns: DisplayColumn[]; + baseTable?: TableInfo; + changeSetKeyFields: string[] = null; setColumnVisibility(uniquePath: string[], isVisible: boolean) { const uniqueName = uniquePath.join('.'); if (uniquePath.length == 1) { @@ -350,4 +353,23 @@ export abstract class GridDisplay { }); this.reload(); } + + getChangeSetCondition(row) { + if (!this.changeSetKeyFields) return null; + return _.pick(row, this.changeSetKeyFields); + } + + getChangeSetField(row, uniqueName): ChangeSetFieldDefinition { + const col = this.columns.find(x => x.uniqueName == uniqueName); + if (!col) return null; + if (!this.baseTable) return null; + if (this.baseTable.pureName != col.pureName || this.baseTable.schemaName != col.schemaName) return null; + return { + pureName: col.pureName, + schemaName: col.schemaName, + uniqueName: uniqueName, + columnName: col.columnName, + condition: this.getChangeSetCondition(row), + }; + } } diff --git a/packages/datalib/src/TableGridDisplay.ts b/packages/datalib/src/TableGridDisplay.ts index 3c21f0aa9..acc85335c 100644 --- a/packages/datalib/src/TableGridDisplay.ts +++ b/packages/datalib/src/TableGridDisplay.ts @@ -1,4 +1,4 @@ -import _ from 'lodash' +import _ from 'lodash'; import { GridDisplay, combineReferenceActions } from './GridDisplay'; import { Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree'; import { TableInfo, EngineDriver } from '@dbgate/types'; @@ -16,6 +16,10 @@ export class TableGridDisplay extends GridDisplay { ) { super(config, setConfig, cache, setCache, getTableInfo); this.columns = this.getDisplayColumns(table, []); + this.baseTable = table; + this.changeSetKeyFields = table.primaryKey + ? table.primaryKey.columns.map(x => x.columnName) + : table.columns.map(x => x.columnName); } createSelect() { diff --git a/packages/datalib/src/index.ts b/packages/datalib/src/index.ts index a37b6f16d..2f4c51446 100644 --- a/packages/datalib/src/index.ts +++ b/packages/datalib/src/index.ts @@ -1,4 +1,5 @@ export * from "./GridDisplay"; export * from "./GridConfig"; export * from "./TableGridDisplay"; +export * from "./ChangeSet"; export * from "./filterName"; diff --git a/packages/web/src/datagrid/ColumnManager.js b/packages/web/src/datagrid/ColumnManager.js index 08585b804..6533e686e 100644 --- a/packages/web/src/datagrid/ColumnManager.js +++ b/packages/web/src/datagrid/ColumnManager.js @@ -21,12 +21,15 @@ const SearchBoxWrapper = styled.div` `; const Button = styled.button` - width: 50px; +// -webkit-appearance: none; +// -moz-appearance: none; +// appearance: none; + // width: 50px; `; const Input = styled.input` flex: 1; - width: 80px; + min-width: 90px; `; function ExpandIcon({ display, column, isHover, ...other }) { diff --git a/packages/web/src/datagrid/DataFilterControl.js b/packages/web/src/datagrid/DataFilterControl.js index 1ad2fb717..2b2f86e02 100644 --- a/packages/web/src/datagrid/DataFilterControl.js +++ b/packages/web/src/datagrid/DataFilterControl.js @@ -34,7 +34,7 @@ const FilterDiv = styled.div` `; const FilterInput = styled.input` flex: 1; - width: 10px; + min-width: 10px; background-color: ${props => (props.state == 'ok' ? '#CCFFCC' : props.state == 'error' ? '#FFCCCC' : 'white')}; `; const FilterButton = styled.button` diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js index 9958f43ac..1146b4828 100644 --- a/packages/web/src/datagrid/DataGridCore.js +++ b/packages/web/src/datagrid/DataGridCore.js @@ -21,6 +21,7 @@ import { emptyCellArray, } from './selection'; import keycodes from '../utility/keycodes'; +import InplaceEditor from './InplaceEditor'; const GridContainer = styled.div` position: absolute; @@ -101,7 +102,6 @@ const NullSpan = styled.span` color: gray; font-style: italic; `; - const wheelRowCount = 5; function CellFormattedValue({ value }) { @@ -112,7 +112,7 @@ function CellFormattedValue({ value }) { /** @param props {import('./types').DataGridProps} */ export default function DataGridCore(props) { - const { conid, database, display, tabVisible } = props; + const { conid, database, display, changeSet, tabVisible } = props; const columns = display.getGridColumns(); // console.log(`GRID, conid=${conid}, database=${database}, sql=${sql}`); @@ -137,6 +137,8 @@ export default function DataGridCore(props) { const [dragStartCell, setDragStartCell] = React.useState(nullCell); const [shiftDragStartCell, setShiftDragStartCell] = React.useState(nullCell); + const [inplaceEditorCell, setInplaceEditorCell] = React.useState(nullCell); + const loadNextData = async () => { if (isLoading) return; setLoadProps({ @@ -323,7 +325,12 @@ export default function DataGridCore(props) { setCurrentCell(cell); setSelectedCells(getCellRange(cell, cell)); setDragStartCell(cell); - // console.log('START', cell); + + if (isRegularCell(cell) && !_.isEqual(cell, inplaceEditorCell) && _.isEqual(cell, currentCell)) { + setInplaceEditorCell(cell); + } else if (!_.isEqual(cell, inplaceEditorCell)) { + setInplaceEditorCell(null); + } } function handleGridMouseMove(event) { @@ -641,8 +648,22 @@ export default function DataGridCore(props) { // @ts-ignore isSelected={cellIsSelected(firstVisibleRowScrollIndex + index, col.colIndex)} > - - {col.hintColumnName && {row[col.hintColumnName]}} + {inplaceEditorCell && + firstVisibleRowScrollIndex + index == inplaceEditorCell[0] && + col.colIndex == inplaceEditorCell[1] ? ( + + ) : ( + <> + + {col.hintColumnName && {row[col.hintColumnName]}} + + )} ))} diff --git a/packages/web/src/datagrid/InplaceEditor.js b/packages/web/src/datagrid/InplaceEditor.js new file mode 100644 index 000000000..12da1cb0b --- /dev/null +++ b/packages/web/src/datagrid/InplaceEditor.js @@ -0,0 +1,39 @@ +import _ from 'lodash'; +import React from 'react'; +import styled from 'styled-components'; +import theme from '../theme'; +import keycodes from '../utility/keycodes'; +import { setChangeSetValue } from '@dbgate/datalib'; + +const StyledInput = styled.input` + border: 0px solid; + outline: none; + margin: 0px; + padding: 0px; +`; + +export default function InplaceEditor({ widthPx, value, definition, changeSet, setChangeSet }) { + const editorRef = React.useRef(); + React.useEffect(() => { + const editor = editorRef.current; + editor.value = value; + editor.focus(); + editor.select(); + }, []); + function handleBlur() { + const editor = editorRef.current; + setChangeSet(setChangeSetValue(changeSet, definition, editor.value)); + } + return ( + + ); +} diff --git a/packages/web/src/datagrid/selection.ts b/packages/web/src/datagrid/selection.ts index 28ff9384a..2c4e3d693 100644 --- a/packages/web/src/datagrid/selection.ts +++ b/packages/web/src/datagrid/selection.ts @@ -67,3 +67,4 @@ export function cellFromEvent(event): CellAddress { const row = cell.getAttribute('data-row'); return convertCellAddress(row, col); } + diff --git a/packages/web/src/datagrid/types.ts b/packages/web/src/datagrid/types.ts index ef43b79b8..5ca7774e6 100644 --- a/packages/web/src/datagrid/types.ts +++ b/packages/web/src/datagrid/types.ts @@ -1,8 +1,10 @@ -import { GridDisplay } from '@dbgate/datalib'; +import { GridDisplay, ChangeSet } from '@dbgate/datalib'; export interface DataGridProps { conid: number; database: string; display: GridDisplay; tabVisible?: boolean; + changeSet?: ChangeSet; + setChangeSet?: Function; } diff --git a/packages/web/src/tabs/TableDataTab.js b/packages/web/src/tabs/TableDataTab.js index 32eac4f1b..2f13e858b 100644 --- a/packages/web/src/tabs/TableDataTab.js +++ b/packages/web/src/tabs/TableDataTab.js @@ -3,7 +3,7 @@ import useFetch from '../utility/useFetch'; import styled from 'styled-components'; import theme from '../theme'; import DataGrid from '../datagrid/DataGrid'; -import { TableGridDisplay, createGridConfig, createGridCache } from '@dbgate/datalib'; +import { TableGridDisplay, createGridConfig, createGridCache, createChangeSet } from '@dbgate/datalib'; import useTableInfo from '../utility/useTableInfo'; import useConnectionInfo from '../utility/useConnectionInfo'; import engines from '@dbgate/engines'; @@ -13,6 +13,10 @@ export default function TableDataTab({ conid, database, schemaName, pureName, ta const tableInfo = useTableInfo({ conid, database, schemaName, pureName }); const [config, setConfig] = React.useState(createGridConfig()); const [cache, setCache] = React.useState(createGridCache()); + const [changeSet, setChangeSet] = React.useState(createChangeSet()); + + console.log('changeSet', changeSet); + const connection = useConnectionInfo(conid); if (!tableInfo || !connection) return null; const display = new TableGridDisplay(tableInfo, engines(connection), config, setConfig, cache, setCache, name => @@ -25,6 +29,8 @@ export default function TableDataTab({ conid, database, schemaName, pureName, ta database={database} display={display} tabVisible={tabVisible} + changeSet={changeSet} + setChangeSet={setChangeSet} /> ); }