mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-26 19:06:02 +00:00
inline editing
This commit is contained in:
99
packages/datalib/src/ChangeSet.ts
Normal file
99
packages/datalib/src/ChangeSet.ts
Normal file
@@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { ForeignKeyInfo, TableInfo, ColumnInfo, DbType } from '@dbgate/types';
|
|||||||
import { parseFilter, getFilterType } from '@dbgate/filterparser';
|
import { parseFilter, getFilterType } from '@dbgate/filterparser';
|
||||||
import { filterName } from './filterName';
|
import { filterName } from './filterName';
|
||||||
import { Select, Expression } from '@dbgate/sqltree';
|
import { Select, Expression } from '@dbgate/sqltree';
|
||||||
|
import { ChangeSetFieldDefinition } from './ChangeSet';
|
||||||
|
|
||||||
export interface DisplayColumn {
|
export interface DisplayColumn {
|
||||||
schemaName: string;
|
schemaName: string;
|
||||||
@@ -47,6 +48,8 @@ export abstract class GridDisplay {
|
|||||||
) {}
|
) {}
|
||||||
abstract getPageQuery(offset: number, count: number): string;
|
abstract getPageQuery(offset: number, count: number): string;
|
||||||
columns: DisplayColumn[];
|
columns: DisplayColumn[];
|
||||||
|
baseTable?: TableInfo;
|
||||||
|
changeSetKeyFields: string[] = null;
|
||||||
setColumnVisibility(uniquePath: string[], isVisible: boolean) {
|
setColumnVisibility(uniquePath: string[], isVisible: boolean) {
|
||||||
const uniqueName = uniquePath.join('.');
|
const uniqueName = uniquePath.join('.');
|
||||||
if (uniquePath.length == 1) {
|
if (uniquePath.length == 1) {
|
||||||
@@ -350,4 +353,23 @@ export abstract class GridDisplay {
|
|||||||
});
|
});
|
||||||
this.reload();
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash';
|
||||||
import { GridDisplay, combineReferenceActions } from './GridDisplay';
|
import { GridDisplay, combineReferenceActions } from './GridDisplay';
|
||||||
import { Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree';
|
import { Select, treeToSql, dumpSqlSelect } from '@dbgate/sqltree';
|
||||||
import { TableInfo, EngineDriver } from '@dbgate/types';
|
import { TableInfo, EngineDriver } from '@dbgate/types';
|
||||||
@@ -16,6 +16,10 @@ export class TableGridDisplay extends GridDisplay {
|
|||||||
) {
|
) {
|
||||||
super(config, setConfig, cache, setCache, getTableInfo);
|
super(config, setConfig, cache, setCache, getTableInfo);
|
||||||
this.columns = this.getDisplayColumns(table, []);
|
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() {
|
createSelect() {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./GridDisplay";
|
export * from "./GridDisplay";
|
||||||
export * from "./GridConfig";
|
export * from "./GridConfig";
|
||||||
export * from "./TableGridDisplay";
|
export * from "./TableGridDisplay";
|
||||||
|
export * from "./ChangeSet";
|
||||||
export * from "./filterName";
|
export * from "./filterName";
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ const SearchBoxWrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Button = styled.button`
|
const Button = styled.button`
|
||||||
width: 50px;
|
// -webkit-appearance: none;
|
||||||
|
// -moz-appearance: none;
|
||||||
|
// appearance: none;
|
||||||
|
// width: 50px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Input = styled.input`
|
const Input = styled.input`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 80px;
|
min-width: 90px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function ExpandIcon({ display, column, isHover, ...other }) {
|
function ExpandIcon({ display, column, isHover, ...other }) {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const FilterDiv = styled.div`
|
|||||||
`;
|
`;
|
||||||
const FilterInput = styled.input`
|
const FilterInput = styled.input`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 10px;
|
min-width: 10px;
|
||||||
background-color: ${props => (props.state == 'ok' ? '#CCFFCC' : props.state == 'error' ? '#FFCCCC' : 'white')};
|
background-color: ${props => (props.state == 'ok' ? '#CCFFCC' : props.state == 'error' ? '#FFCCCC' : 'white')};
|
||||||
`;
|
`;
|
||||||
const FilterButton = styled.button`
|
const FilterButton = styled.button`
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
emptyCellArray,
|
emptyCellArray,
|
||||||
} from './selection';
|
} from './selection';
|
||||||
import keycodes from '../utility/keycodes';
|
import keycodes from '../utility/keycodes';
|
||||||
|
import InplaceEditor from './InplaceEditor';
|
||||||
|
|
||||||
const GridContainer = styled.div`
|
const GridContainer = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -101,7 +102,6 @@ const NullSpan = styled.span`
|
|||||||
color: gray;
|
color: gray;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const wheelRowCount = 5;
|
const wheelRowCount = 5;
|
||||||
|
|
||||||
function CellFormattedValue({ value }) {
|
function CellFormattedValue({ value }) {
|
||||||
@@ -112,7 +112,7 @@ function CellFormattedValue({ value }) {
|
|||||||
|
|
||||||
/** @param props {import('./types').DataGridProps} */
|
/** @param props {import('./types').DataGridProps} */
|
||||||
export default function DataGridCore(props) {
|
export default function DataGridCore(props) {
|
||||||
const { conid, database, display, tabVisible } = props;
|
const { conid, database, display, changeSet, tabVisible } = props;
|
||||||
const columns = display.getGridColumns();
|
const columns = display.getGridColumns();
|
||||||
|
|
||||||
// console.log(`GRID, conid=${conid}, database=${database}, sql=${sql}`);
|
// 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 [dragStartCell, setDragStartCell] = React.useState(nullCell);
|
||||||
const [shiftDragStartCell, setShiftDragStartCell] = React.useState(nullCell);
|
const [shiftDragStartCell, setShiftDragStartCell] = React.useState(nullCell);
|
||||||
|
|
||||||
|
const [inplaceEditorCell, setInplaceEditorCell] = React.useState(nullCell);
|
||||||
|
|
||||||
const loadNextData = async () => {
|
const loadNextData = async () => {
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
setLoadProps({
|
setLoadProps({
|
||||||
@@ -323,7 +325,12 @@ export default function DataGridCore(props) {
|
|||||||
setCurrentCell(cell);
|
setCurrentCell(cell);
|
||||||
setSelectedCells(getCellRange(cell, cell));
|
setSelectedCells(getCellRange(cell, cell));
|
||||||
setDragStartCell(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) {
|
function handleGridMouseMove(event) {
|
||||||
@@ -641,8 +648,22 @@ export default function DataGridCore(props) {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
isSelected={cellIsSelected(firstVisibleRowScrollIndex + index, col.colIndex)}
|
isSelected={cellIsSelected(firstVisibleRowScrollIndex + index, col.colIndex)}
|
||||||
>
|
>
|
||||||
<CellFormattedValue value={row[col.uniqueName]} />
|
{inplaceEditorCell &&
|
||||||
{col.hintColumnName && <HintSpan>{row[col.hintColumnName]}</HintSpan>}
|
firstVisibleRowScrollIndex + index == inplaceEditorCell[0] &&
|
||||||
|
col.colIndex == inplaceEditorCell[1] ? (
|
||||||
|
<InplaceEditor
|
||||||
|
widthPx={col.widthPx}
|
||||||
|
value={row[col.uniqueName]}
|
||||||
|
changeSet={changeSet}
|
||||||
|
setChangeSet={props.setChangeSet}
|
||||||
|
definition={display.getChangeSetField(row, col.uniqueName)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CellFormattedValue value={row[col.uniqueName]} />
|
||||||
|
{col.hintColumnName && <HintSpan>{row[col.hintColumnName]}</HintSpan>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</TableBodyCell>
|
</TableBodyCell>
|
||||||
))}
|
))}
|
||||||
</TableBodyRow>
|
</TableBodyRow>
|
||||||
|
|||||||
39
packages/web/src/datagrid/InplaceEditor.js
Normal file
39
packages/web/src/datagrid/InplaceEditor.js
Normal file
@@ -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 (
|
||||||
|
<StyledInput
|
||||||
|
onBlur={handleBlur}
|
||||||
|
ref={editorRef}
|
||||||
|
type="text"
|
||||||
|
style={{
|
||||||
|
width: widthPx,
|
||||||
|
minWidth: widthPx,
|
||||||
|
maxWidth: widthPx,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -67,3 +67,4 @@ export function cellFromEvent(event): CellAddress {
|
|||||||
const row = cell.getAttribute('data-row');
|
const row = cell.getAttribute('data-row');
|
||||||
return convertCellAddress(row, col);
|
return convertCellAddress(row, col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { GridDisplay } from '@dbgate/datalib';
|
import { GridDisplay, ChangeSet } from '@dbgate/datalib';
|
||||||
|
|
||||||
export interface DataGridProps {
|
export interface DataGridProps {
|
||||||
conid: number;
|
conid: number;
|
||||||
database: string;
|
database: string;
|
||||||
display: GridDisplay;
|
display: GridDisplay;
|
||||||
tabVisible?: boolean;
|
tabVisible?: boolean;
|
||||||
|
changeSet?: ChangeSet;
|
||||||
|
setChangeSet?: Function;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import useFetch from '../utility/useFetch';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import theme from '../theme';
|
import theme from '../theme';
|
||||||
import DataGrid from '../datagrid/DataGrid';
|
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 useTableInfo from '../utility/useTableInfo';
|
||||||
import useConnectionInfo from '../utility/useConnectionInfo';
|
import useConnectionInfo from '../utility/useConnectionInfo';
|
||||||
import engines from '@dbgate/engines';
|
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 tableInfo = useTableInfo({ conid, database, schemaName, pureName });
|
||||||
const [config, setConfig] = React.useState(createGridConfig());
|
const [config, setConfig] = React.useState(createGridConfig());
|
||||||
const [cache, setCache] = React.useState(createGridCache());
|
const [cache, setCache] = React.useState(createGridCache());
|
||||||
|
const [changeSet, setChangeSet] = React.useState(createChangeSet());
|
||||||
|
|
||||||
|
console.log('changeSet', changeSet);
|
||||||
|
|
||||||
const connection = useConnectionInfo(conid);
|
const connection = useConnectionInfo(conid);
|
||||||
if (!tableInfo || !connection) return null;
|
if (!tableInfo || !connection) return null;
|
||||||
const display = new TableGridDisplay(tableInfo, engines(connection), config, setConfig, cache, setCache, name =>
|
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}
|
database={database}
|
||||||
display={display}
|
display={display}
|
||||||
tabVisible={tabVisible}
|
tabVisible={tabVisible}
|
||||||
|
changeSet={changeSet}
|
||||||
|
setChangeSet={setChangeSet}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user