diff --git a/packages/datalib/src/ChangeSet.ts b/packages/datalib/src/ChangeSet.ts index 652c7c2c7..033d97e0d 100644 --- a/packages/datalib/src/ChangeSet.ts +++ b/packages/datalib/src/ChangeSet.ts @@ -115,6 +115,54 @@ export function setChangeSetValue( }; } +// export function batchUpdateChangeSet( +// changeSet: ChangeSet, +// rowDefinitions: ChangeSetRowDefinition[], +// dataRows: [] +// ): ChangeSet { +// const res = { +// updates: [...changeSet.updates], +// deletes: [...changeSet.deletes], +// inserts: [...changeSet.inserts], +// }; +// const rowItems: ChangeSetItem[] = rowDefinitions.map(definition => { +// let [field, item] = findExistingChangeSetItem(res, definition); +// let createUpdate = false; +// if (field == 'deletes') { +// res.deletes = res.deletes.filter(x => x != item); +// createUpdate = true; +// } +// if (field == 'updates' && item == null) { +// item = { +// ...definition, +// fields: {}, +// }; +// res.updates.push(item); +// } +// return item; +// }); +// for (const tuple in _.zip(rowItems, dataRows)) { +// const [definition, dataRow] = tuple; +// for +// } +// return res; +// } + +export function batchUpdateChangeSet( + changeSet: ChangeSet, + rowDefinitions: ChangeSetRowDefinition[], + dataRows: [] +): ChangeSet { + // console.log('batchUpdateChangeSet', changeSet, rowDefinitions, dataRows); + for (const tuple of _.zip(rowDefinitions, dataRows)) { + const [definition, dataRow] = tuple; + for (const key of _.keys(dataRow)) { + changeSet = setChangeSetValue(changeSet, { ...definition, columnName: key, uniqueName: key }, dataRow[key]); + } + } + return changeSet; +} + function extractFields(item: ChangeSetItem): UpdateField[] { return _.keys(item.fields).map(targetColumn => ({ targetColumn, @@ -194,9 +242,9 @@ export function changeSetToSql(changeSet: ChangeSet): Command[] { } export function revertChangeSetRowChanges(changeSet: ChangeSet, definition: ChangeSetRowDefinition): ChangeSet { - console.log('definition', definition) + console.log('definition', definition); const [field, item] = findExistingChangeSetItem(changeSet, definition); - console.log('field, item', field, item) + console.log('field, item', field, item); if (item) return { ...changeSet, diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js index b542f273d..c37a97d30 100644 --- a/packages/web/src/datagrid/DataGridCore.js +++ b/packages/web/src/datagrid/DataGridCore.js @@ -33,6 +33,7 @@ import { getChangeSetInsertedRows, changeSetInsertNewRow, deleteChangeSetRows, + batchUpdateChangeSet, } from '@dbgate/datalib'; import { scriptToSql } from '@dbgate/sqltree'; import { sleep } from '../utility/common'; @@ -111,6 +112,8 @@ export default function DataGridCore(props) { const [selectedCells, setSelectedCells] = React.useState(emptyCellArray); const [dragStartCell, setDragStartCell] = React.useState(nullCell); const [shiftDragStartCell, setShiftDragStartCell] = React.useState(nullCell); + const [autofillDragStartCell, setAutofillDragStartCell] = React.useState(nullCell); + const [autofillSelectedCells, setAutofillSelectedCells] = React.useState(emptyCellArray); // const [inplaceEditorCell, setInplaceEditorCell] = React.useState(nullCell); // const [inplaceEditorInitText, setInplaceEditorInitText] = React.useState(''); @@ -121,6 +124,14 @@ export default function DataGridCore(props) { changeSetRef.current = changeSet; + const autofillMarkerCell = React.useMemo( + () => + selectedCells && selectedCells.length > 0 && _.uniq(selectedCells.map(x => x[0])).length == 1 + ? [_.max(selectedCells.map(x => x[0])), _.max(selectedCells.map(x => x[1]))] + : null, + [selectedCells] + ); + const loadNextData = async () => { if (isLoading) return; setLoadProps({ @@ -280,21 +291,6 @@ export default function DataGridCore(props) { [columnSizes, firstVisibleColumnScrollIndex, gridScrollAreaWidth, columns] ); - const cellIsSelected = React.useCallback( - (row, col) => { - const [currentRow, currentCol] = currentCell; - if (row == currentRow && col == currentCol) return true; - for (const [selectedRow, selectedCol] of selectedCells) { - if (row == selectedRow && col == selectedCol) return true; - if (selectedRow == 'header' && col == selectedCol) return true; - if (row == selectedRow && selectedCol == 'header') return true; - if (selectedRow == 'header' && selectedCol == 'header') return true; - } - return false; - }, - [currentCell, selectedCells] - ); - if (!loadedRows || !columns) return null; const insertedRows = getChangeSetInsertedRows(changeSet, display.baseTable); const rowCountNewIncluded = loadedRows.length + insertedRows.length; @@ -310,21 +306,33 @@ export default function DataGridCore(props) { function handleGridMouseDown(event) { event.target.closest('table').focus(); const cell = cellFromEvent(event); - setCurrentCell(cell); - setSelectedCells(getCellRange(cell, cell)); - setDragStartCell(cell); + const autofill = event.target.closest('div.autofillHandleMarker'); + if (autofill) { + setAutofillDragStartCell(cell); + } else { + setCurrentCell(cell); + setSelectedCells(getCellRange(cell, cell)); + setDragStartCell(cell); - if (isRegularCell(cell) && !_.isEqual(cell, inplaceEditorState.cell) && _.isEqual(cell, currentCell)) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'show', cell, selectAll: true }); - } else if (!_.isEqual(cell, inplaceEditorState.cell)) { - // @ts-ignore - dispatchInsplaceEditor({ type: 'close' }); + if (isRegularCell(cell) && !_.isEqual(cell, inplaceEditorState.cell) && _.isEqual(cell, currentCell)) { + // @ts-ignore + dispatchInsplaceEditor({ type: 'show', cell, selectAll: true }); + } else if (!_.isEqual(cell, inplaceEditorState.cell)) { + // @ts-ignore + dispatchInsplaceEditor({ type: 'close' }); + } } } function handleGridMouseMove(event) { - if (dragStartCell) { + if (autofillDragStartCell) { + const cell = cellFromEvent(event); + if (isRegularCell(cell) && (cell[0] == autofillDragStartCell[0] || cell[1] == autofillDragStartCell[1])) { + const autoFillStart = [selectedCells[0][0], _.min(selectedCells.map(x => x[1]))]; + // @ts-ignore + setAutofillSelectedCells(getCellRange(autoFillStart, cell)); + } + } else if (dragStartCell) { const cell = cellFromEvent(event); setCurrentCell(cell); setSelectedCells(getCellRange(dragStartCell, cell)); @@ -338,24 +346,44 @@ export default function DataGridCore(props) { setSelectedCells(getCellRange(dragStartCell, cell)); setDragStartCell(null); } + if (autofillDragStartCell) { + const currentRowNumber = currentCell[0]; + if (_.isNumber(currentRowNumber)) { + const rowIndexes = _.uniq((autofillSelectedCells || []).map(x => x[0])).filter(x => x != currentRowNumber); + // @ts-ignore + const colNames = selectedCells.map(cell => columns[columnSizes.realToModel(cell[1])].uniqueName); + const changeObject = _.pick(loadedAndInsertedRows[currentRowNumber], colNames); + setChangeSet( + batchUpdateChangeSet( + changeSet, + getRowDefinitions(rowIndexes), + // @ts-ignore + rowIndexes.map(() => changeObject) + ) + ); + } + + setAutofillDragStartCell(null); + setAutofillSelectedCells([]); + } } - function getSelectedRowDefinitions() { + function getRowDefinitions(rowIndexes) { const res = []; if (!loadedAndInsertedRows) return res; - const rowIndexes = _.uniq((selectedCells || []).map(x => x[0])); for (const index of rowIndexes) { if (loadedAndInsertedRows[index] && _.isNumber(index)) { - const insertedRowIndex = - firstVisibleRowScrollIndex + index >= loadedRows.length - ? firstVisibleRowScrollIndex + index - loadedRows.length - : null; + const insertedRowIndex = index >= loadedRows.length ? index - loadedRows.length : null; res.push(display.getChangeSetRow(loadedAndInsertedRows[index], insertedRowIndex)); } } return res; } + function getSelectedRowDefinitions() { + return getRowDefinitions(_.uniq((selectedCells || []).map(x => x[0]))); + } + function revertRowChanges() { const updatedChangeSet = getSelectedRowDefinitions().reduce( (chs, row) => revertChangeSetRowChanges(chs, row), @@ -365,10 +393,7 @@ export default function DataGridCore(props) { } function deleteCurrentRow() { - const updatedChangeSet = getSelectedRowDefinitions().reduce( - (chs, row) => deleteChangeSetRows(chs, row), - changeSet - ); + const updatedChangeSet = getSelectedRowDefinitions().reduce((chs, row) => deleteChangeSetRows(chs, row), changeSet); setChangeSet(updatedChangeSet); } @@ -699,12 +724,14 @@ export default function DataGridCore(props) { visibleRealColumns={visibleRealColumns} inplaceEditorState={inplaceEditorState} dispatchInsplaceEditor={dispatchInsplaceEditor} - cellIsSelected={cellIsSelected} + autofillSelectedCells={autofillSelectedCells} + selectedCells={selectedCells} insertedRowIndex={ firstVisibleRowScrollIndex + index >= loadedRows.length ? firstVisibleRowScrollIndex + index - loadedRows.length : null } + autofillMarkerCell={autofillMarkerCell} changeSet={changeSet} setChangeSet={setChangeSet} display={display} diff --git a/packages/web/src/datagrid/DataGridRow.js b/packages/web/src/datagrid/DataGridRow.js index 51177dfcd..4a6247fae 100644 --- a/packages/web/src/datagrid/DataGridRow.js +++ b/packages/web/src/datagrid/DataGridRow.js @@ -22,22 +22,34 @@ const TableBodyCell = styled.td` // border-collapse: collapse; padding: 2px; white-space: nowrap; + position: relative; overflow: hidden; ${props => props.isSelected && + !props.isAutofillSelected && ` background: initial; background-color: deepskyblue; color: white;`} + ${props => + props.isAutofillSelected && + ` + background: initial; + background-color: magenta; + color: white;`} + + ${props => props.isModifiedRow && !props.isInsertedRow && !props.isSelected && + !props.isAutofillSelected && !props.isModifiedCell && ` background-color: #FFFFDB;`} ${props => !props.isSelected && + !props.isAutofillSelected && !props.isInsertedRow && props.isModifiedCell && ` @@ -45,20 +57,27 @@ const TableBodyCell = styled.td` ${props => !props.isSelected && + !props.isAutofillSelected && props.isInsertedRow && ` background-color: #DBFFDB;`} ${props => !props.isSelected && + !props.isAutofillSelected && props.isDeletedRow && ` background-color: #FFDBFF; + `} + + ${props => + props.isDeletedRow && + ` background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAEElEQVQImWNgIAX8x4KJBAD+agT8INXz9wAAAABJRU5ErkJggg=='); // from http://www.patternify.com/ background-repeat: repeat-x; background-position: 50% 50%;`} - + `; const HintSpan = styled.span` @@ -89,24 +108,47 @@ const TableHeaderCell = styled.td` overflow: hidden; `; +const AutoFillPoint = styled.div` + width: 8px; + height: 8px; + background-color: #1a73e8; + position: absolute; + right: 0px; + bottom: 0px; + overflow: visible; + cursor: crosshair; +`; + function CellFormattedValue({ value }) { if (value == null) return (NULL); if (_.isDate(value)) return moment(value).format('YYYY-MM-DD HH:mm:ss'); return value; } +function cellIsSelected(row, col, selectedCells) { + for (const [selectedRow, selectedCol] of selectedCells) { + if (row == selectedRow && col == selectedCol) return true; + if (selectedRow == 'header' && col == selectedCol) return true; + if (row == selectedRow && selectedCol == 'header') return true; + if (selectedRow == 'header' && selectedCol == 'header') return true; + } + return false; +} + export default function DataGridRow({ rowHeight, rowIndex, visibleRealColumns, inplaceEditorState, dispatchInsplaceEditor, - cellIsSelected, row, display, changeSet, setChangeSet, insertedRowIndex, + autofillMarkerCell, + selectedCells, + autofillSelectedCells, }) { // console.log('RENDER ROW', rowIndex); const rowDefinition = display.getChangeSetRow(row, insertedRowIndex); @@ -120,6 +162,7 @@ export default function DataGridRow({ return true; }) .map(col => col.uniqueName); + return ( @@ -135,8 +178,8 @@ export default function DataGridRow({ }} data-row={rowIndex} data-col={col.colIndex} - // @ts-ignore - isSelected={cellIsSelected(rowIndex, col.colIndex)} + isSelected={cellIsSelected(rowIndex, col.colIndex, selectedCells)} + isAutofillSelected={cellIsSelected(rowIndex, col.colIndex, autofillSelectedCells)} isModifiedRow={!!matchedChangeSetItem} isModifiedCell={ matchedChangeSetItem && matchedField == 'updates' && col.uniqueName in matchedChangeSetItem.fields @@ -163,6 +206,9 @@ export default function DataGridRow({ {hintFieldsAllowed.includes(col.uniqueName) && {row[col.hintColumnName]}} )} + {autofillMarkerCell && autofillMarkerCell[1] == col.colIndex && autofillMarkerCell[0] == rowIndex && ( + + )} ))}