diff --git a/packages/datalib/src/FormViewDisplay.ts b/packages/datalib/src/FormViewDisplay.ts index 766340576..cfbe5cd1d 100644 --- a/packages/datalib/src/FormViewDisplay.ts +++ b/packages/datalib/src/FormViewDisplay.ts @@ -11,6 +11,7 @@ import { ChangeCacheFunc, ChangeConfigFunc, DisplayColumn } from './GridDisplay' export class FormViewDisplay { isLoadedCorrectly = true; columns: DisplayColumn[]; + public baseTable: TableInfo; constructor( public config: GridConfig, @@ -20,4 +21,5 @@ export class FormViewDisplay { public driver?: EngineDriver, public dbinfo: DatabaseInfo = null ) {} + } diff --git a/packages/datalib/src/TableFormViewDisplay.ts b/packages/datalib/src/TableFormViewDisplay.ts index 9b7f90ec7..4a26cd646 100644 --- a/packages/datalib/src/TableFormViewDisplay.ts +++ b/packages/datalib/src/TableFormViewDisplay.ts @@ -15,9 +15,9 @@ import { import { filterName } from './filterName'; import { TableGridDisplay } from './TableGridDisplay'; import stableStringify from 'json-stable-stringify'; +import { ChangeSetFieldDefinition, ChangeSetRowDefinition } from './ChangeSet'; export class TableFormViewDisplay extends FormViewDisplay { - public table: TableInfo; // use utility functions from GridDisplay and publish result in FromViewDisplat interface private gridDisplay: TableGridDisplay; @@ -35,13 +35,17 @@ export class TableFormViewDisplay extends FormViewDisplay { this.isLoadedCorrectly = this.gridDisplay.isLoadedCorrectly; this.columns = this.gridDisplay.columns; + this.baseTable = this.gridDisplay.baseTable; } - getPrimaryKeyEqualCondition(): Condition { - if (!this.config.formViewKey) return null; + getPrimaryKeyEqualCondition(row = null): Condition { + if (!row) row = this.config.formViewKey; + if (!row) return null; + const { primaryKey } = this.gridDisplay.baseTable; + if (primaryKey) return null; return { conditionType: 'and', - conditions: _.keys(this.config.formViewKey).map((columnName) => ({ + conditions: primaryKey.columns.map(({ columnName }) => ({ conditionType: 'binary', operator: '=', left: { @@ -186,4 +190,25 @@ export class TableFormViewDisplay extends FormViewDisplay { const sql = treeToSql(this.driver, select, dumpSqlSelect); return sql; } + + getChangeSetRow(row): ChangeSetRowDefinition { + if (!this.baseTable) return null; + return { + pureName: this.baseTable.pureName, + schemaName: this.baseTable.schemaName, + condition: this.extractKey(row), + }; + } + + 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 { + ...this.getChangeSetRow(row), + uniqueName: uniqueName, + columnName: col.columnName, + }; + } } diff --git a/packages/web/src/datagrid/InplaceEditor.js b/packages/web/src/datagrid/InplaceEditor.js index a2aca264b..e32ab10ed 100644 --- a/packages/web/src/datagrid/InplaceEditor.js +++ b/packages/web/src/datagrid/InplaceEditor.js @@ -23,6 +23,7 @@ export default function InplaceEditor({ onSetValue, }) { const editorRef = React.useRef(); + const widthRef = React.useRef(widthPx); const isChangedRef = React.useRef(!!inplaceEditorState.text); React.useEffect(() => { const editor = editorRef.current; @@ -88,9 +89,9 @@ export default function InplaceEditor({ onChange={() => (isChangedRef.current = true)} onKeyDown={handleKeyDown} style={{ - width: widthPx, - minWidth: widthPx, - maxWidth: widthPx, + width: widthRef.current, + minWidth: widthRef.current, + maxWidth: widthRef.current, }} /> ); diff --git a/packages/web/src/formview/ChangeSetFormer.ts b/packages/web/src/formview/ChangeSetFormer.ts new file mode 100644 index 000000000..739ce111c --- /dev/null +++ b/packages/web/src/formview/ChangeSetFormer.ts @@ -0,0 +1,93 @@ +import { + ChangeSet, + changeSetContainsChanges, + changeSetInsertNewRow, + createChangeSet, + deleteChangeSetRows, + findExistingChangeSetItem, + getChangeSetInsertedRows, + TableFormViewDisplay, + revertChangeSetRowChanges, + setChangeSetValue, + ChangeSetRowDefinition, +} from 'dbgate-datalib'; +import Former from './Former'; + +export default class ChangeSetFormer extends Former { + public changeSet: ChangeSet; + public setChangeSet: Function; + private batchChangeSet: ChangeSet; + public rowDefinition: ChangeSetRowDefinition; + public rowStatus; + + constructor( + public sourceRow: any, + public changeSetState, + public dispatchChangeSet, + public display: TableFormViewDisplay + ) { + super(); + this.changeSet = changeSetState && changeSetState.value; + this.setChangeSet = (value) => dispatchChangeSet({ type: 'set', value }); + this.batchChangeSet = null; + this.rowDefinition = display.getChangeSetRow(sourceRow); + const [matchedField, matchedChangeSetItem] = findExistingChangeSetItem(this.changeSet, this.rowDefinition); + this.rowData = matchedChangeSetItem ? { ...sourceRow, ...matchedChangeSetItem.fields } : sourceRow; + let status = 'regular'; + if (matchedChangeSetItem && matchedField == 'updates') status = 'updated'; + if (matchedField == 'deletes') status = 'deleted'; + this.rowStatus = { + status, + modifiedFields: + matchedChangeSetItem && matchedChangeSetItem.fields ? new Set(Object.keys(matchedChangeSetItem.fields)) : null, + }; + } + + applyModification(changeSetReducer) { + if (this.batchChangeSet) { + this.batchChangeSet = changeSetReducer(this.batchChangeSet); + } else { + this.setChangeSet(changeSetReducer(this.changeSet)); + } + } + + setCellValue( uniqueName: string, value: any) { + const row = this.sourceRow; + const definition = this.display.getChangeSetField(row, uniqueName); + this.applyModification((chs) => setChangeSetValue(chs, definition, value)); + } + + deleteRow(index: number) { + this.applyModification((chs) => deleteChangeSetRows(chs, this.rowDefinition)); + } + + beginUpdate() { + this.batchChangeSet = this.changeSet; + } + endUpdate() { + this.setChangeSet(this.batchChangeSet); + this.batchChangeSet = null; + } + + revertRowChanges() { + this.applyModification((chs) => revertChangeSetRowChanges(chs, this.rowDefinition)); + } + revertAllChanges() { + this.applyModification((chs) => createChangeSet()); + } + undo() { + this.dispatchChangeSet({ type: 'undo' }); + } + redo() { + this.dispatchChangeSet({ type: 'redo' }); + } + get canUndo() { + return this.changeSetState.canUndo; + } + get canRedo() { + return this.changeSetState.canRedo; + } + get containsChanges() { + return changeSetContainsChanges(this.changeSet); + } +} diff --git a/packages/web/src/formview/FormView.js b/packages/web/src/formview/FormView.js index ae206e70c..10322d0b4 100644 --- a/packages/web/src/formview/FormView.js +++ b/packages/web/src/formview/FormView.js @@ -1,3 +1,5 @@ +// @ts-nocheck + import _ from 'lodash'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -48,7 +50,6 @@ const TableHeaderCell = styled.td` position: relative; ${(props) => - // @ts-ignore props.isSelected && ` background: initial; @@ -66,12 +67,17 @@ const TableBodyCell = styled.td` overflow: hidden; ${(props) => - // @ts-ignore props.isSelected && ` background: initial; background-color: ${props.theme.gridbody_selection[4]}; color: ${props.theme.gridbody_invfont1};`} + + ${(props) => + !props.isSelected && + props.isModifiedCell && + ` + background-color: ${props.theme.gridbody_background_orange[1]};`} `; const FocusField = styled.input` @@ -86,7 +92,7 @@ function isDataCell(cell) { } export default function FormView(props) { - const { rowData, toolbarPortalRef, tabVisible, config, setConfig, onNavigate } = props; + const { toolbarPortalRef, tabVisible, config, setConfig, onNavigate, former } = props; /** @type {import('dbgate-datalib').FormViewDisplay} */ const formDisplay = props.formDisplay; const theme = useTheme(); @@ -100,6 +106,8 @@ export default function FormView(props) { const rowCount = Math.floor((wrapperHeight - 20) / rowHeight); const columnChunks = _.chunk(formDisplay.columns, rowCount); + const { rowData, rowStatus } = former; + const handleSwitchToTable = () => { setConfig((cfg) => ({ ...cfg, @@ -318,6 +326,7 @@ export default function FormView(props) { data-col={chunkIndex * 2 + 1} // @ts-ignore isSelected={currentCell[0] == rowIndex && currentCell[1] == chunkIndex * 2 + 1} + isModifiedCell={rowStatus.modifiedFields && rowStatus.modifiedFields.has(col.uniqueName)} ref={(element) => setCellRef(rowIndex, chunkIndex * 2 + 1, element)} > {inplaceEditorState.cell && @@ -328,7 +337,9 @@ export default function FormView(props) { inplaceEditorState={inplaceEditorState} dispatchInsplaceEditor={dispatchInsplaceEditor} cellValue={rowData[col.uniqueName]} - onSetValue={(value) => {}} + onSetValue={(value) => { + former.setCellValue(col.uniqueName, value); + }} // grider={grider} // rowIndex={rowIndex} // uniqueName={col.uniqueName} diff --git a/packages/web/src/formview/Former.ts b/packages/web/src/formview/Former.ts new file mode 100644 index 000000000..cbd1508d2 --- /dev/null +++ b/packages/web/src/formview/Former.ts @@ -0,0 +1,53 @@ +// export interface GriderRowStatus { +// status: 'regular' | 'updated' | 'deleted' | 'inserted'; +// modifiedFields?: Set; +// insertedFields?: Set; +// deletedFields?: Set; +// } + +export default abstract class Former { + public rowData: any; + + // getRowStatus(index): GriderRowStatus { + // const res: GriderRowStatus = { + // status: 'regular', + // }; + // return res; + // } + beginUpdate() {} + endUpdate() {} + setCellValue(uniqueName: string, value: any) {} + revertRowChanges() {} + revertAllChanges() {} + undo() {} + redo() {} + get editable() { + return false; + } + get canInsert() { + return false; + } + get allowSave() { + return this.containsChanges; + } + get canUndo() { + return false; + } + get canRedo() { + return false; + } + get containsChanges() { + return false; + } + get disableLoadNextPage() { + return false; + } + get errors() { + return null; + } + updateRow(changeObject) { + for (const key of Object.keys(changeObject)) { + this.setCellValue(key, changeObject[key]); + } + } +} diff --git a/packages/web/src/formview/SqlFormView.js b/packages/web/src/formview/SqlFormView.js index 9f252e2ed..c5f5b218c 100644 --- a/packages/web/src/formview/SqlFormView.js +++ b/packages/web/src/formview/SqlFormView.js @@ -5,6 +5,7 @@ import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders'; import useExtensions from '../utility/useExtensions'; import FormView from './FormView'; import axios from '../utility/axios'; +import ChangeSetFormer from './ChangeSetFormer'; async function loadRow(props, sql) { const { conid, database } = props; @@ -26,7 +27,7 @@ async function loadRow(props, sql) { } export default function SqlFormView(props) { - const { formDisplay } = props; + const { formDisplay, changeSetState, dispatchChangeSet } = props; const [rowData, setRowData] = React.useState(null); const handleLoadCurrentRow = async () => { @@ -46,7 +47,14 @@ export default function SqlFormView(props) { if (formDisplay && !formDisplay.isLoadedCurrentRow(rowData)) { handleLoadCurrentRow(); } - }, [formDisplay]); + }, [formDisplay, rowData]); + + const former = React.useMemo(() => new ChangeSetFormer(rowData, changeSetState, dispatchChangeSet, formDisplay), [ + rowData, + changeSetState, + dispatchChangeSet, + formDisplay, + ]); // const { config, setConfig, cache, setCache, schemaName, pureName, conid, database } = props; // const { formViewKey } = config; @@ -76,5 +84,5 @@ export default function SqlFormView(props) { // setDisplay(newDisplay); // }, [config, cache, conid, database, schemaName, pureName, dbinfo, extensions]); - return ; + return ; }