form view infrastructure, loading row from DB

This commit is contained in:
Jan Prochazka
2021-01-09 20:37:49 +01:00
parent bb35a496f8
commit b71b58c93f
10 changed files with 250 additions and 36 deletions

View File

@@ -0,0 +1,23 @@
import _ from 'lodash';
import { GridConfig, GridCache, GridConfigColumns, createGridCache, GroupFunc } from './GridConfig';
import { ForeignKeyInfo, TableInfo, ColumnInfo, EngineDriver, NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
import { parseFilter, getFilterType } from 'dbgate-filterparser';
import { filterName } from './filterName';
import { ChangeSetFieldDefinition, ChangeSetRowDefinition } from './ChangeSet';
import { Expression, Select, treeToSql, dumpSqlSelect, Condition } from 'dbgate-sqltree';
import { isTypeLogical } from 'dbgate-tools';
import { ChangeCacheFunc, ChangeConfigFunc, DisplayColumn } from './GridDisplay';
export class FormViewDisplay {
isLoadedCorrectly = true;
columns: DisplayColumn[];
constructor(
public config: GridConfig,
protected setConfig: ChangeConfigFunc,
public cache: GridCache,
protected setCache: ChangeCacheFunc,
public driver?: EngineDriver,
public dbinfo: DatabaseInfo = null
) {}
}

View File

@@ -29,6 +29,8 @@ export interface GridConfig extends GridConfigColumns {
grouping: { [uniqueName: string]: GroupFunc }; grouping: { [uniqueName: string]: GroupFunc };
childConfig?: GridConfig; childConfig?: GridConfig;
reference?: GridReferenceDefinition; reference?: GridReferenceDefinition;
isFormView?: boolean;
formViewKey?: { [uniqueName: string]: string };
} }
export interface GridCache { export interface GridCache {

View File

@@ -518,4 +518,20 @@ export abstract class GridDisplay {
conditions, conditions,
}; };
} }
switchToFormView(rowData) {
if (!this.baseTable) return;
const { primaryKey } = this.baseTable;
if (!primaryKey) return;
const { columns } = primaryKey;
this.setConfig((cfg) => ({
...cfg,
isFormView: true,
formViewKey: _.pick(
rowData,
columns.map((x) => x.columnName)
),
}));
}
} }

View File

@@ -0,0 +1,62 @@
import { FormViewDisplay } from './FormViewDisplay';
import _ from 'lodash';
import { GridDisplay, ChangeCacheFunc, DisplayColumn, DisplayedColumnInfo, ChangeConfigFunc } from './GridDisplay';
import { TableInfo, EngineDriver, ViewInfo, ColumnInfo, NamedObjectInfo, DatabaseInfo } from 'dbgate-types';
import { GridConfig, GridCache, createGridCache } from './GridConfig';
import { Expression, Select, treeToSql, dumpSqlSelect, mergeConditions, Condition } from 'dbgate-sqltree';
import { filterName } from './filterName';
import { TableGridDisplay } from './TableGridDisplay';
export class TableFormViewDisplay extends FormViewDisplay {
public table: TableInfo;
// use utility functions from GridDisplay and publish result in FromViewDisplat interface
private gridDisplay: TableGridDisplay;
constructor(
public tableName: NamedObjectInfo,
driver: EngineDriver,
config: GridConfig,
setConfig: ChangeConfigFunc,
cache: GridCache,
setCache: ChangeCacheFunc,
dbinfo: DatabaseInfo
) {
super(config, setConfig, cache, setCache, driver, dbinfo);
this.gridDisplay = new TableGridDisplay(tableName, driver, config, setConfig, cache, setCache, dbinfo);
this.isLoadedCorrectly = this.gridDisplay.isLoadedCorrectly;
this.columns = this.gridDisplay.columns;
}
getPrimaryKeyCondition(): Condition {
if (!this.config.formViewKey) return null;
return {
conditionType: 'and',
conditions: _.keys(this.config.formViewKey).map((columnName) => ({
conditionType: 'binary',
operator: '=',
left: {
exprType: 'column',
columnName,
source: {
alias: 'basetbl',
},
},
right: {
exprType: 'value',
value: this.config.formViewKey[columnName],
},
})),
};
}
getCurrentRowQuery() {
if (!this.driver) return null;
const select = this.gridDisplay.createSelect();
if (!select) return null;
select.topRecords = 1;
select.where = mergeConditions(select.where, this.getPrimaryKeyCondition());
const sql = treeToSql(this.driver, select, dumpSqlSelect);
return sql;
}
}

View File

@@ -1,11 +1,13 @@
export * from "./GridDisplay"; export * from './GridDisplay';
export * from "./GridConfig"; export * from './GridConfig';
export * from "./TableGridDisplay"; export * from './TableGridDisplay';
export * from "./ViewGridDisplay"; export * from './ViewGridDisplay';
export * from "./JslGridDisplay"; export * from './JslGridDisplay';
export * from "./ChangeSet"; export * from './ChangeSet';
export * from "./filterName"; export * from './filterName';
export * from "./FreeTableGridDisplay"; export * from './FreeTableGridDisplay';
export * from "./FreeTableModel"; export * from './FreeTableModel';
export * from "./MacroDefinition"; export * from './MacroDefinition';
export * from "./runMacro"; export * from './runMacro';
export * from './FormViewDisplay';
export * from './TableFormViewDisplay';

View File

@@ -21,17 +21,13 @@ const DataGridContainer = styled.div`
`; `;
export default function DataGrid(props) { export default function DataGrid(props) {
const { GridCore, FormView } = props; const { GridCore, FormView, config, formDisplay } = props;
const theme = useTheme(); const theme = useTheme();
const [managerSize, setManagerSize] = React.useState(0); const [managerSize, setManagerSize] = React.useState(0);
const [selection, setSelection] = React.useState([]); const [selection, setSelection] = React.useState([]);
const [grider, setGrider] = React.useState(null); const [grider, setGrider] = React.useState(null);
const [formViewData, setFormViewData] = React.useState(null); // const [formViewData, setFormViewData] = React.useState(null);
const isFormView = !!formViewData; const isFormView = !!(config && config.isFormView);
const handleSetFormView = (rowData) => {
setFormViewData(rowData);
};
return ( return (
<HorizontalSplitter initialValue="300px" size={managerSize} setSize={setManagerSize}> <HorizontalSplitter initialValue="300px" size={managerSize} setSize={setManagerSize}>
@@ -57,13 +53,13 @@ export default function DataGrid(props) {
<DataGridContainer> <DataGridContainer>
{isFormView ? ( {isFormView ? (
<FormView {...props} rowData={formViewData} onSetTableView={() => setFormViewData(null)} /> <FormView {...props} />
) : ( ) : (
<GridCore <GridCore
{...props} {...props}
onSelectionChanged={setSelection} onSelectionChanged={setSelection}
onChangeGrider={setGrider} onChangeGrider={setGrider}
onSetFormView={FormView ? handleSetFormView : null} formViewAvailable={!!FormView && !!formDisplay}
/> />
)} )}
</DataGridContainer> </DataGridContainer>

View File

@@ -116,7 +116,7 @@ export default function DataGridCore(props) {
onSelectionChanged, onSelectionChanged,
frameSelection, frameSelection,
onKeyDown, onKeyDown,
onSetFormView, formViewAvailable,
} = props; } = props;
// console.log('RENDER GRID', display.baseTable.pureName); // console.log('RENDER GRID', display.baseTable.pureName);
const columns = React.useMemo(() => display.allColumns, [display]); const columns = React.useMemo(() => display.allColumns, [display]);
@@ -943,6 +943,13 @@ export default function DataGridCore(props) {
display.clearFilters(); display.clearFilters();
}; };
const handleSetFormView =
formViewAvailable && display.baseTable && display.baseTable.primaryKey
? (rowData) => {
display.switchToFormView(rowData);
}
: null;
// console.log('visibleRealColumnIndexes', visibleRealColumnIndexes); // console.log('visibleRealColumnIndexes', visibleRealColumnIndexes);
// console.log( // console.log(
// 'gridScrollAreaWidth / columnSizes.getVisibleScrollSizeSum()', // 'gridScrollAreaWidth / columnSizes.getVisibleScrollSizeSum()',
@@ -1048,7 +1055,7 @@ export default function DataGridCore(props) {
display={display} display={display}
focusedColumn={display.focusedColumn} focusedColumn={display.focusedColumn}
frameSelection={frameSelection} frameSelection={frameSelection}
onSetFormView={onSetFormView} onSetFormView={handleSetFormView}
/> />
) )
)} )}

View File

@@ -2,7 +2,7 @@ import React from 'react';
import _ from 'lodash'; import _ from 'lodash';
import DataGrid from './DataGrid'; import DataGrid from './DataGrid';
import styled from 'styled-components'; import styled from 'styled-components';
import { TableGridDisplay, createGridConfig, createGridCache } from 'dbgate-datalib'; import { TableGridDisplay, TableFormViewDisplay, createGridConfig, createGridCache } from 'dbgate-datalib';
import { getFilterValueExpression } from 'dbgate-filterparser'; import { getFilterValueExpression } from 'dbgate-filterparser';
import { findEngineDriver } from 'dbgate-tools'; import { findEngineDriver } from 'dbgate-tools';
import { useConnectionInfo, getTableInfo, useDatabaseInfo } from '../utility/metadataLoaders'; import { useConnectionInfo, getTableInfo, useDatabaseInfo } from '../utility/metadataLoaders';
@@ -88,7 +88,22 @@ export default function TableDataGrid({
: null; : null;
} }
function createFormDisplay() {
return connection
? new TableFormViewDisplay(
{ schemaName, pureName },
findEngineDriver(connection, extensions),
config,
setConfig,
cache || myCache,
setCache || setMyCache,
dbinfo
)
: null;
}
const [display, setDisplay] = React.useState(createDisplay()); const [display, setDisplay] = React.useState(createDisplay());
const [formDisplay, setFormDisplay] = React.useState(createFormDisplay());
React.useEffect(() => { React.useEffect(() => {
setRefReloadToken((v) => v + 1); setRefReloadToken((v) => v + 1);
@@ -102,6 +117,13 @@ export default function TableDataGrid({
setDisplay(newDisplay); setDisplay(newDisplay);
}, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo, extensions]); }, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo, extensions]);
React.useEffect(() => {
const newDisplay = createFormDisplay();
if (!newDisplay) return;
if (formDisplay && formDisplay.isLoadedCorrectly && !newDisplay.isLoadedCorrectly) return;
setFormDisplay(newDisplay);
}, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo, extensions]);
const handleDatabaseStructureChanged = React.useCallback(() => { const handleDatabaseStructureChanged = React.useCallback(() => {
(setCache || setMyCache)(createGridCache()); (setCache || setMyCache)(createGridCache());
}, []); }, []);
@@ -159,9 +181,12 @@ export default function TableDataGrid({
<VerticalSplitter> <VerticalSplitter>
<DataGrid <DataGrid
// key={`${conid}, ${database}, ${schemaName}, ${pureName}`} // key={`${conid}, ${database}, ${schemaName}, ${pureName}`}
config={config}
setConfig={setConfig}
conid={conid} conid={conid}
database={database} database={database}
display={display} display={display}
formDisplay={formDisplay}
tabVisible={tabVisible} tabVisible={tabVisible}
changeSetState={changeSetState} changeSetState={changeSetState}
dispatchChangeSet={dispatchChangeSet} dispatchChangeSet={dispatchChangeSet}
@@ -174,9 +199,9 @@ export default function TableDataGrid({
GridCore={SqlDataGridCore} GridCore={SqlDataGridCore}
FormView={SqlFormView} FormView={SqlFormView}
isDetailView={isDetailView} isDetailView={isDetailView}
tableInfo={ // tableInfo={
dbinfo && dbinfo.tables && dbinfo.tables.find((x) => x.pureName == pureName && x.schemaName == schemaName) // dbinfo && dbinfo.tables && dbinfo.tables.find((x) => x.pureName == pureName && x.schemaName == schemaName)
} // }
/> />
{reference && ( {reference && (
<ReferenceContainer> <ReferenceContainer>

View File

@@ -61,15 +61,34 @@ const NullSpan = styled.span`
font-style: italic; font-style: italic;
`; `;
export default function FormView({ tableInfo, rowData, toolbarPortalRef, tabVisible, onSetTableView }) { export default function FormView(props) {
const { rowData, toolbarPortalRef, tabVisible, config, setConfig } = props;
/** @type {import('dbgate-datalib').FormViewDisplay} */
const formDisplay = props.formDisplay;
const theme = useTheme(); const theme = useTheme();
const [headerRowRef, { height: rowHeight }] = useDimensions(); const [headerRowRef, { height: rowHeight }] = useDimensions();
const [wrapperRef, { height: wrapperHeight }] = useDimensions(); const [wrapperRef, { height: wrapperHeight }] = useDimensions();
if (!tableInfo || !rowData) return null; const handleSwitchToTable = () => {
setConfig((cfg) => ({
...cfg,
isFormView: false,
formViewKey: null,
}));
};
const toolbar =
toolbarPortalRef &&
toolbarPortalRef.current &&
tabVisible &&
ReactDOM.createPortal(<FormViewToolbar switchToTable={handleSwitchToTable} />, toolbarPortalRef.current);
// console.log('display', display);
if (!formDisplay || !formDisplay.isLoadedCorrectly) return toolbar;
const rowCount = Math.floor((wrapperHeight - 20) / rowHeight); const rowCount = Math.floor((wrapperHeight - 20) / rowHeight);
const columnChunks = _.chunk(tableInfo.columns, rowCount); const columnChunks = _.chunk(formDisplay.columns, rowCount);
return ( return (
<Wrapper ref={wrapperRef}> <Wrapper ref={wrapperRef}>
@@ -78,18 +97,15 @@ export default function FormView({ tableInfo, rowData, toolbarPortalRef, tabVisi
{chunk.map((col) => ( {chunk.map((col) => (
<TableRow key={col.columnName} theme={theme} ref={headerRowRef} style={{ height: `${rowHeight}px` }}> <TableRow key={col.columnName} theme={theme} ref={headerRowRef} style={{ height: `${rowHeight}px` }}>
<TableHeaderCell theme={theme}> <TableHeaderCell theme={theme}>
<ColumnLabel {...col} foreignKey={findForeignKeyForColumn(tableInfo, col)} /> <ColumnLabel {...col} />
</TableHeaderCell> </TableHeaderCell>
<TableBodyCell theme={theme}>{rowData[col.columnName]}</TableBodyCell> <TableBodyCell theme={theme}>{rowData && rowData[col.columnName]}</TableBodyCell>
</TableRow> </TableRow>
))} ))}
</Table> </Table>
))} ))}
{toolbarPortalRef && {toolbar}
toolbarPortalRef.current &&
tabVisible &&
ReactDOM.createPortal(<FormViewToolbar switchToTable={onSetTableView} />, toolbarPortalRef.current)}
</Wrapper> </Wrapper>
); );
} }

View File

@@ -1,6 +1,71 @@
import { TableFormViewDisplay } from 'dbgate-datalib';
import { findEngineDriver } from 'dbgate-tools';
import React from 'react'; import React from 'react';
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
import useExtensions from '../utility/useExtensions';
import FormView from './FormView'; import FormView from './FormView';
import axios from '../utility/axios';
async function loadCurrentRow(props) {
const { formDisplay, conid, database } = props;
/** @type {import('dbgate-datalib').TableFormViewDisplay} */
const sql = formDisplay.getCurrentRowQuery();
const response = await axios.request({
url: 'database-connections/query-data',
method: 'post',
params: {
conid,
database,
},
data: { sql },
});
if (response.data.errorMessage) return response.data;
return response.data.rows[0];
}
export default function SqlFormView(props) { export default function SqlFormView(props) {
return <FormView {...props} />; const { formDisplay } = props;
const [rowData, setRowData] = React.useState(null);
const handleLoadCurrentRow = async () => {
const row = await loadCurrentRow(props);
if (row) setRowData(row);
};
React.useEffect(() => {
handleLoadCurrentRow();
}, [formDisplay]);
// const { config, setConfig, cache, setCache, schemaName, pureName, conid, database } = props;
// const { formViewKey } = config;
// const [display, setDisplay] = React.useState(null);
// const connection = useConnectionInfo({ conid });
// const dbinfo = useDatabaseInfo({ conid, database });
// const extensions = useExtensions();
// console.log('SqlFormView.props', props);
// React.useEffect(() => {
// const newDisplay = connection
// ? new TableFormViewDisplay(
// { schemaName, pureName },
// findEngineDriver(connection, extensions),
// config,
// setConfig,
// cache,
// setCache,
// dbinfo
// )
// : null;
// if (!newDisplay) return;
// if (display && display.isLoadedCorrectly && !newDisplay.isLoadedCorrectly) return;
// setDisplay(newDisplay);
// }, [config, cache, conid, database, schemaName, pureName, dbinfo, extensions]);
return <FormView {...props} rowData={rowData} />;
} }