diff --git a/packages/datalib/src/GridConfig.ts b/packages/datalib/src/GridConfig.ts index 427a7e7ae..36636022a 100644 --- a/packages/datalib/src/GridConfig.ts +++ b/packages/datalib/src/GridConfig.ts @@ -7,6 +7,15 @@ export interface GridConfigColumns { addedColumns: string[]; } +export interface GridReferenceDefinition { + schemaName: string; + pureName: string; + columns: { + baseName: string; + refName: string; + }[]; +} + export interface GridConfig extends GridConfigColumns { filters: { [uniqueName: string]: string }; focusedColumn?: string; diff --git a/packages/datalib/src/TableGridDisplay.ts b/packages/datalib/src/TableGridDisplay.ts index 4b4b04113..52ef3f226 100644 --- a/packages/datalib/src/TableGridDisplay.ts +++ b/packages/datalib/src/TableGridDisplay.ts @@ -131,7 +131,7 @@ export class TableGridDisplay extends GridDisplay { for (const column of this.getGridColumns()) { if (column.foreignKey) { const table = this.cache.tables[column.uniqueName]; - if (table) { + if (table && table.columns && table.columns.length > 0) { const hintColumn = table.columns.find((x) => x?.dataType?.toLowerCase()?.includes('char')); if (hintColumn) { const parentUniqueName = column.uniquePath.slice(0, -1).join('.'); @@ -147,6 +147,7 @@ export class TableGridDisplay extends GridDisplay { } } else { this.requireFkTarget(column); + this.isLoadedCorrectly = false; res = 'loadRequired'; } } diff --git a/packages/filterparser/src/filterTool.ts b/packages/filterparser/src/filterTool.ts new file mode 100644 index 000000000..7a1aa3e51 --- /dev/null +++ b/packages/filterparser/src/filterTool.ts @@ -0,0 +1,4 @@ +export function getFilterValueExpression(value) { + if (value == null) return 'NULL'; + return `="${value}"`; +} diff --git a/packages/filterparser/src/index.ts b/packages/filterparser/src/index.ts index 474f5ed3e..77f9df246 100644 --- a/packages/filterparser/src/index.ts +++ b/packages/filterparser/src/index.ts @@ -1,2 +1,3 @@ export * from './parseFilter'; export * from './getFilterType'; +export * from './filterTool'; diff --git a/packages/web/src/datagrid/DataGrid.js b/packages/web/src/datagrid/DataGrid.js index 05ecfed58..ab01cd194 100644 --- a/packages/web/src/datagrid/DataGrid.js +++ b/packages/web/src/datagrid/DataGrid.js @@ -10,6 +10,7 @@ import { ManagerMainContainer, ManagerOuterContainer1, ManagerOuterContainer2, + ManagerOuterContainerFull, WidgetTitle, } from './ManagerStyles'; import ReferenceManager from './ReferenceManager'; @@ -40,18 +41,21 @@ const DataGridContainer = styled.div` /** @param props {import('./types').DataGridProps} */ export default function DataGrid(props) { + const Container1 = props.showReferences ? ManagerOuterContainer1 : ManagerOuterContainerFull; return ( - + Columns - - - References - - + + {props.showReferences && ( + + References + + + )} diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js index c762249d1..4854710da 100644 --- a/packages/web/src/datagrid/DataGridCore.js +++ b/packages/web/src/datagrid/DataGridCore.js @@ -425,6 +425,10 @@ export default function DataGridCore(props) { } }, [jslid]); + React.useEffect(() => { + if (props.onSelectedRowsChanged) props.onSelectedRowsChanged(getSelectedRowData()) + }, [selectedCells]); + // const handleCloseInplaceEditor = React.useCallback( // mode => { // const [row, col] = currentCell || []; @@ -680,8 +684,16 @@ export default function DataGridCore(props) { return res; } + function getSelectedRowIndexes() { + return _.uniq((selectedCells || []).map((x) => x[0])); + } + function getSelectedRowDefinitions() { - return getRowDefinitions(_.uniq((selectedCells || []).map((x) => x[0]))); + return getRowDefinitions(getSelectedRowIndexes()); + } + + function getSelectedRowData() { + return _.compact(getSelectedRowIndexes().map((index) => loadedRows && loadedRows[index])); } function revertRowChanges() { diff --git a/packages/web/src/datagrid/ManagerStyles.js b/packages/web/src/datagrid/ManagerStyles.js index 63907546e..8516b9744 100644 --- a/packages/web/src/datagrid/ManagerStyles.js +++ b/packages/web/src/datagrid/ManagerStyles.js @@ -31,6 +31,10 @@ export const ManagerOuterContainer2 = styled(ManagerOuterContainer)` flex: 0 0 40%; `; +export const ManagerOuterContainerFull = styled(ManagerOuterContainer)` + flex: 1; +`; + export const ManagerInnerContainer = styled.div` flex: 1 1; overflow-y: scroll; diff --git a/packages/web/src/datagrid/ReferenceManager.js b/packages/web/src/datagrid/ReferenceManager.js index c4579b626..06e3ebd48 100644 --- a/packages/web/src/datagrid/ReferenceManager.js +++ b/packages/web/src/datagrid/ReferenceManager.js @@ -31,9 +31,9 @@ const NameContainer = styled.div` margin-left: 5px; `; -function ManagerRow({ tableName, columns, Icon }) { +function ManagerRow({ tableName, columns, Icon, onClick }) { return ( - + {tableName} ({columns.map((x) => x.columnName).join(', ')}) @@ -60,7 +60,22 @@ export default function ReferenceManager(props) { <>
References tables ({foreignKeys.length})
{foreignKeys.map((fk) => ( - + + props.onReferenceClick({ + schemaName: fk.refSchemaName, + pureName: fk.refTableName, + columns: fk.columns.map((col) => ({ + baseName: col.columnName, + refName: col.refColumnName, + })), + }) + } + /> ))} )} @@ -68,7 +83,22 @@ export default function ReferenceManager(props) { <>
Dependend tables ({dependencies.length})
{dependencies.map((fk) => ( - + + props.onReferenceClick({ + schemaName: fk.schemaName, + pureName: fk.pureName, + columns: fk.columns.map((col) => ({ + baseName: col.refColumnName, + refName: col.columnName, + })), + }) + } + /> ))} )} diff --git a/packages/web/src/datagrid/TableDataGrid.js b/packages/web/src/datagrid/TableDataGrid.js index 3d734c77c..8c8934850 100644 --- a/packages/web/src/datagrid/TableDataGrid.js +++ b/packages/web/src/datagrid/TableDataGrid.js @@ -1,9 +1,13 @@ import React from 'react'; +import _ from 'lodash'; import DataGrid from './DataGrid'; import { TableGridDisplay, createGridConfig, createGridCache } from '@dbgate/datalib'; +import { getFilterValueExpression } from '@dbgate/filterparser'; import { useConnectionInfo, getTableInfo } from '../utility/metadataLoaders'; import engines from '@dbgate/engines'; import useSocket from '../utility/SocketProvider'; +import { VerticalSplitter } from '../widgets/Splitter'; +import stableStringify from 'json-stable-stringify'; export default function TableDataGrid({ conid, @@ -12,14 +16,20 @@ export default function TableDataGrid({ pureName, tabVisible, toolbarPortalRef, - cache, - setCache, changeSetState, dispatchChangeSet, + config = undefined, + setConfig = undefined, + cache = undefined, + setCache = undefined, }) { - const [config, setConfig] = React.useState(createGridConfig()); + const [myConfig, setMyConfig] = React.useState(createGridConfig()); + const [childConfig, setChildConfig] = React.useState(createGridConfig()); + const [myCache, setMyCache] = React.useState(createGridCache()); + const [childCache, setChildCache] = React.useState(createGridCache()); const connection = useConnectionInfo({ conid }); + const [reference, setReference] = React.useState(null); const display = React.useMemo( () => @@ -27,18 +37,18 @@ export default function TableDataGrid({ ? new TableGridDisplay( { schemaName, pureName }, engines(connection), - config, - setConfig, - cache, - setCache, + config || myConfig, + setConfig || setMyConfig, + cache || myCache, + setCache || setMyCache, (name) => getTableInfo({ conid, database, ...name }) ) : null, - [connection, config, cache] + [connection, config || myConfig, cache || myCache, conid, database, schemaName, pureName] ); const handleDatabaseStructureChanged = React.useCallback(() => { - setCache(createGridCache()); + (setCache || setMyCache)(createGridCache()); }, []); const socket = useSocket(); @@ -54,18 +64,62 @@ export default function TableDataGrid({ } }, [conid, database, display]); + const handleSelectedRowsChanged = (selectedRows) => { + const filters = { + ...(config || myConfig).filters, + ..._.fromPairs( + reference.columns.map((col) => [ + col.refName, + selectedRows.map((x) => getFilterValueExpression(x[col.baseName])).join(','), + ]) + ), + }; + if (stableStringify(filters) != stableStringify((config || myConfig).filters)) { + setChildConfig((cfg) => ({ + ...cfg, + filters, + })); + setChildCache((ca) => ({ + ...ca, + refreshTime: new Date().getTime(), + })); + } + }; + if (!display) return null; return ( - + + + {reference && ( + + )} + ); } diff --git a/packages/web/src/datagrid/types.ts b/packages/web/src/datagrid/types.ts index fb9aaeec7..5aba8c1cf 100644 --- a/packages/web/src/datagrid/types.ts +++ b/packages/web/src/datagrid/types.ts @@ -1,4 +1,4 @@ -import { GridDisplay, ChangeSet } from '@dbgate/datalib'; +import { GridDisplay, ChangeSet, GridReferenceDefinition } from '@dbgate/datalib'; export interface DataGridProps { conid?: string; @@ -9,4 +9,7 @@ export interface DataGridProps { dispatchChangeSet?: Function; toolbarPortalRef?: any; jslid?: string; + showReferences?: boolean; + onReferenceClick?: (def: GridReferenceDefinition) => void; + onSelectedRowsChanged?: Function; } diff --git a/packages/web/src/tabs/TableDataTab.js b/packages/web/src/tabs/TableDataTab.js index 8d09b4d08..03e962e9b 100644 --- a/packages/web/src/tabs/TableDataTab.js +++ b/packages/web/src/tabs/TableDataTab.js @@ -6,7 +6,6 @@ import { useUpdateDatabaseForTab } from '../utility/globalState'; import TableDataGrid from '../datagrid/TableDataGrid'; export default function TableDataTab({ conid, database, schemaName, pureName, tabVisible, toolbarPortalRef }) { - const [cache, setCache] = React.useState(createGridCache()); const [changeSetState, dispatchChangeSet] = useUndoReducer(createChangeSet()); useUpdateDatabaseForTab(tabVisible, conid, database); @@ -18,8 +17,6 @@ export default function TableDataTab({ conid, database, schemaName, pureName, ta pureName={pureName} tabVisible={tabVisible} toolbarPortalRef={toolbarPortalRef} - cache={cache} - setCache={setCache} changeSetState={changeSetState} dispatchChangeSet={dispatchChangeSet} /> diff --git a/packages/web/src/widgets/Splitter.js b/packages/web/src/widgets/Splitter.js index ab43bf7c4..ad889bed3 100644 --- a/packages/web/src/widgets/Splitter.js +++ b/packages/web/src/widgets/Splitter.js @@ -11,20 +11,21 @@ const MainContainer = styled.div` const ChildContainer = styled.div` flex: 1; // flex: 0 0 50%; -// flex-basis: 100px; -// flex-grow: 1; + // flex-basis: 100px; + // flex-grow: 1; display: flex; position: relative; `; export function VerticalSplitter({ children }) { - if (!_.isArray(children) || children.length !== 2) { - throw new Error('Splitter must have exactly 2 children'); + const childrenArray = _.isArray(children) ? children : [children]; + if (childrenArray.length !== 1 && childrenArray.length != 2) { + throw new Error('Splitter must have 1 or 2 children'); } return ( - {children[0]} - {children[1]} + {childrenArray[0]} + {childrenArray[1] && {childrenArray[1]}} ); }