diff --git a/packages/tools/src/nameTools.ts b/packages/tools/src/nameTools.ts
index 9d22301bd..ef76f1d56 100644
--- a/packages/tools/src/nameTools.ts
+++ b/packages/tools/src/nameTools.ts
@@ -1,4 +1,4 @@
-import { DatabaseInfo, DatabaseInfoObjects } from 'dbgate-types';
+import { ColumnInfo, DatabaseInfo, DatabaseInfoObjects, TableInfo } from 'dbgate-types';
export function fullNameFromString(name) {
const m = name.match(/\[([^\]]+)\]\.\[([^\]]+)\]/);
@@ -45,3 +45,7 @@ export function findObjectLike(
// @ts-ignore
return dbinfo[objectTypeField].find((x) => equalStringLike(x.pureName, pureName));
}
+
+export function findForeignKeyForColumn(table: TableInfo, column: ColumnInfo) {
+ return (table.foreignKeys || []).find((fk) => fk.columns.find((col) => col.columnName == column.columnName));
+}
diff --git a/packages/web/src/designer/Designer.js b/packages/web/src/designer/Designer.js
index 2aea4017d..c91bb24b1 100644
--- a/packages/web/src/designer/Designer.js
+++ b/packages/web/src/designer/Designer.js
@@ -7,6 +7,7 @@ import useTheme from '../theme/useTheme';
import DesignerReference from './DesignerReference';
import cleanupDesignColumns from './cleanupDesignColumns';
import { isConnectedByReference } from './designerTools';
+import { getTableInfo } from '../utility/metadataLoaders';
const Wrapper = styled.div`
flex: 1;
@@ -20,7 +21,7 @@ const Canvas = styled.div`
position: relative;
`;
-export default function Designer({ value, onChange }) {
+export default function Designer({ value, onChange, conid, database }) {
const { tables, references } = value || {};
const theme = useTheme();
@@ -39,10 +40,11 @@ export default function Designer({ value, onChange }) {
json.designerId = uuidv1();
json.left = e.clientX - rect.left;
json.top = e.clientY - rect.top;
- onChange({
- ...value,
- tables: [...(tables || []), json],
- });
+
+ onChange((current) => ({
+ ...current,
+ tables: [...(current.tables || []), json],
+ }));
};
const changeTable = React.useCallback(
@@ -70,6 +72,9 @@ export default function Designer({ value, onChange }) {
onChange((current) => ({
...current,
tables: (current.tables || []).filter((x) => x.designerId != table.designerId),
+ references: (current.references || []).filter(
+ (x) => x.sourceId != table.designerId && x.targetId != table.designerId
+ ),
}));
},
[onChange]
@@ -144,6 +149,45 @@ export default function Designer({ value, onChange }) {
});
};
+ const handleAddReferenceByColumn = async (designerId, foreignKey) => {
+ const toTable = await getTableInfo({
+ conid,
+ database,
+ pureName: foreignKey.refTableName,
+ schemaName: foreignKey.refSchemaName,
+ });
+ const newTableDesignerId = uuidv1();
+ onChange((current) => {
+ const fromTable = (current.tables || []).find((x) => x.designerId == designerId);
+ if (!fromTable) return;
+ return {
+ ...current,
+ tables: [
+ ...(current.tables || []),
+ {
+ ...toTable,
+ left: fromTable.left + 300,
+ top: fromTable.top + 50,
+ designerId: newTableDesignerId,
+ },
+ ],
+ references: [
+ ...(current.references || []),
+ {
+ designerId: uuidv1(),
+ sourceId: fromTable.designerId,
+ targetId: newTableDesignerId,
+ joinType: 'INNER JOIN',
+ columns: foreignKey.columns.map((col) => ({
+ source: col.columnName,
+ target: col.refColumnName,
+ })),
+ },
+ ],
+ };
+ });
+ };
+
const handleSelectColumn = React.useCallback(
(column) => {
onChange((current) => ({
@@ -211,6 +255,7 @@ export default function Designer({ value, onChange }) {
onCreateReference={handleCreateReference}
onSelectColumn={handleSelectColumn}
onChangeColumn={handleChangeColumn}
+ onAddReferenceByColumn={handleAddReferenceByColumn}
table={table}
onChangeTable={changeTable}
onBringToFront={bringToFront}
diff --git a/packages/web/src/designer/DesignerTable.js b/packages/web/src/designer/DesignerTable.js
index b5c107661..5d04e8028 100644
--- a/packages/web/src/designer/DesignerTable.js
+++ b/packages/web/src/designer/DesignerTable.js
@@ -1,11 +1,16 @@
import React from 'react';
import styled from 'styled-components';
+import { findForeignKeyForColumn } from 'dbgate-tools';
import ColumnLabel from '../datagrid/ColumnLabel';
import { FontIcon } from '../icons';
import useTheme from '../theme/useTheme';
import DomTableRef from './DomTableRef';
import _ from 'lodash';
import { CheckboxField } from '../utility/inputs';
+import { useShowMenu } from '../modals/showMenu';
+import { DropDownMenuDivider, DropDownMenuItem } from '../modals/DropDownMenu';
+import useShowModal from '../modals/showModal';
+import InputTextModal from '../modals/InputTextModal';
const Wrapper = styled.div`
position: absolute;
@@ -80,12 +85,50 @@ const ColumnLine = styled.div`
`}
`;
+function TableContextMenu({ remove, setTableAlias, removeTableAlias }) {
+ return (
+ <>
+ Remove
+
+ Set table alias
+ {!!removeTableAlias && Remove table alias}
+ >
+ );
+}
+
+function ColumnContextMenu({ setSortOrder, addReference }) {
+ return (
+ <>
+ setSortOrder(1)}>Sort ascending
+ setSortOrder(-1)}>Sort descending
+ setSortOrder(0)}>Unsort
+ {!!addReference && Add reference}
+ >
+ );
+}
+
+function ColumnDesignerIcons({ column, designerId, designer }) {
+ const designerColumn = (designer.columns || []).find(
+ (x) => x.designerId == designerId && x.columnName == column.columnName
+ );
+ if (!designerColumn) return null;
+ return (
+ <>
+ {!!designerColumn.filter && }
+ {designerColumn.sortOrder > 0 && }
+ {designerColumn.sortOrder < 0 && }
+ {!!designerColumn.isGrouped && }
+ >
+ );
+}
+
export default function DesignerTable({
table,
onChangeTable,
onBringToFront,
onRemoveTable,
onCreateReference,
+ onAddReferenceByColumn,
onSelectColumn,
onChangeColumn,
sourceDragColumn,
@@ -97,11 +140,13 @@ export default function DesignerTable({
setChangeToken,
designer,
}) {
- const { pureName, columns, left, top, designerId } = table;
+ const { pureName, columns, left, top, designerId, alias } = table;
const [movingPosition, setMovingPosition] = React.useState(null);
const movingPositionRef = React.useRef(null);
const theme = useTheme();
const domObjectsRef = React.useRef({});
+ const showMenu = useShowMenu();
+ const showModal = useShowModal();
const moveStartXRef = React.useRef(null);
const moveStartYRef = React.useRef(null);
@@ -185,6 +230,71 @@ export default function DesignerTable({
changeTokenDebounced.current();
};
+ const handleSetTableAlias = () => {
+ showModal((modalState) => (
+ {
+ onChangeTable({
+ ...table,
+ alias: newAlias,
+ });
+ }}
+ />
+ ));
+ };
+
+ const handleHeaderContextMenu = (event) => {
+ event.preventDefault();
+ showMenu(
+ event.pageX,
+ event.pageY,
+ onRemoveTable({ designerId })}
+ setTableAlias={handleSetTableAlias}
+ removeTableAlias={
+ alias
+ ? () =>
+ onChangeTable({
+ ...table,
+ alias: null,
+ })
+ : null
+ }
+ />
+ );
+ };
+
+ const handleColumnContextMenu = (column) => (event) => {
+ event.preventDefault();
+ const foreignKey = findForeignKeyForColumn(table, column);
+ showMenu(
+ event.pageX,
+ event.pageY,
+ {
+ onChangeColumn(
+ {
+ ...column,
+ designerId,
+ },
+ (col) => ({ ...col, sortOrder })
+ );
+ }}
+ addReference={
+ foreignKey
+ ? () => {
+ onAddReferenceByColumn(designerId, foreignKey);
+ }
+ : null
+ }
+ />
+ );
+ };
+
return (
onBringToFront(table)}
ref={(dom) => dispatchDomColumn('', dom)}
>
-
- {pureName}
+
+ {alias || pureName}
onRemoveTable(table)} theme={theme}>
@@ -204,6 +314,7 @@ export default function DesignerTable({
{(columns || []).map((column) => (
-
+
+
))}
diff --git a/packages/web/src/designer/QueryDesigner.js b/packages/web/src/designer/QueryDesigner.js
index f5f1969ae..031fe40ed 100644
--- a/packages/web/src/designer/QueryDesigner.js
+++ b/packages/web/src/designer/QueryDesigner.js
@@ -3,5 +3,5 @@ import styled from 'styled-components';
import Designer from './Designer';
export default function QueryDesigner({ value, conid, database, engine, onChange }) {
- return ;
+ return ;
}
diff --git a/packages/web/src/designer/designerTools.ts b/packages/web/src/designer/designerTools.ts
index 356b1fca8..a1061a1ff 100644
--- a/packages/web/src/designer/designerTools.ts
+++ b/packages/web/src/designer/designerTools.ts
@@ -34,7 +34,7 @@ export function findConnectingReference(
tables2: DesignerTableInfo[],
additionalCondition: (ref: DesignerReferenceInfo) => boolean
) {
- for (const ref of designer.references) {
+ for (const ref of designer.references || []) {
if (additionalCondition(ref) && referenceIsConnecting(ref, tables1, tables2)) {
return ref;
}
@@ -119,6 +119,7 @@ export function isConnectedByReference(
table2: { designerId: string },
withoutRef: { designerId: string }
) {
+ if (!designer.references) return false;
const creator = new DesignerComponentCreator({
...designer,
references: withoutRef
diff --git a/packages/web/src/icons.js b/packages/web/src/icons.js
index e247a3447..14bf7424e 100644
--- a/packages/web/src/icons.js
+++ b/packages/web/src/icons.js
@@ -85,6 +85,8 @@ const iconNames = {
'img reference': 'mdi mdi-link-box',
'img link': 'mdi mdi-link',
+ 'img filter': 'mdi mdi-filter',
+ 'img group': 'mdi mdi-group',
};
export function FontIcon({ icon, className = '', ...other }) {
diff --git a/packages/web/src/tabs/QueryDesignTab.js b/packages/web/src/tabs/QueryDesignTab.js
index 50f56b0c2..170811d32 100644
--- a/packages/web/src/tabs/QueryDesignTab.js
+++ b/packages/web/src/tabs/QueryDesignTab.js
@@ -102,7 +102,7 @@ export default function QueryDesignTab({
sesid,
sql: sqlPreview,
});
- }, [busy]);
+ }, [busy, conid, sessionId, database, sqlPreview]);
const handleCancel = () => {
axios.post('sessions/cancel', {
@@ -118,12 +118,15 @@ export default function QueryDesignTab({
setBusy(false);
};
- const handleKeyDown = React.useCallback((e) => {
- if (e.keyCode == keycodes.f5) {
- e.preventDefault();
- handleExecute();
- }
- }, []);
+ const handleKeyDown = React.useCallback(
+ (e) => {
+ if (e.keyCode == keycodes.f5) {
+ e.preventDefault();
+ handleExecute();
+ }
+ },
+ [handleExecute]
+ );
React.useEffect(() => {
if (tabVisible) {