|
@@ -5,10 +5,13 @@ import TreeView from '@mui/lab/TreeView';
|
|
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
|
import TreeItem from '@mui/lab/TreeItem';
|
|
|
+import Menu from '@mui/material/Menu';
|
|
|
+import MenuItem from '@mui/material/MenuItem';
|
|
|
import databaseSvgStr from '../style/icons/database-solid-ghost.svg';
|
|
|
import {
|
|
|
fetchTables,
|
|
|
- IDatasource,
|
|
|
+ // IDatasource,
|
|
|
+ IDatasourceItem,
|
|
|
setDatasource,
|
|
|
useDatasourceList
|
|
|
} from './api/datasource';
|
|
@@ -19,7 +22,11 @@ dragImg.src = 'data:image/svg+xml;base64,' + btoa(databaseSvgStr);
|
|
|
|
|
|
interface IProps {
|
|
|
manager: string;
|
|
|
- onOpenTable: (datasource: IDatasource, tableName: string) => void;
|
|
|
+ onOpenTable: (
|
|
|
+ datasource: IDatasourceItem,
|
|
|
+ tableName: string,
|
|
|
+ action: 'schema' | 'content'
|
|
|
+ ) => void;
|
|
|
}
|
|
|
|
|
|
const DatasourceView: React.FunctionComponent<IProps> = ({
|
|
@@ -33,11 +40,71 @@ const DatasourceView: React.FunctionComponent<IProps> = ({
|
|
|
}>
|
|
|
>({});
|
|
|
|
|
|
- const handleNodeToggle = async (ds: IDatasource) => {
|
|
|
- if (!tables[ds.sourcename]) {
|
|
|
- const tables = await fetchTables(ds);
|
|
|
+ const [contextMenu, setContextMenu] = React.useState<{
|
|
|
+ mouseX: number;
|
|
|
+ mouseY: number;
|
|
|
+ } | null>(null);
|
|
|
+
|
|
|
+ const [contextData, setContextData] = React.useState<{
|
|
|
+ datasource: IDatasourceItem;
|
|
|
+ tableName: string;
|
|
|
+ }>({} as any);
|
|
|
+
|
|
|
+ const handleContextMenu = (
|
|
|
+ event: React.MouseEvent,
|
|
|
+ datasource: IDatasourceItem,
|
|
|
+ tableName: string
|
|
|
+ ) => {
|
|
|
+ event.preventDefault();
|
|
|
+ setContextMenu(
|
|
|
+ contextMenu === null
|
|
|
+ ? {
|
|
|
+ mouseX: event.clientX + 2,
|
|
|
+ mouseY: event.clientY - 6
|
|
|
+ }
|
|
|
+ : // repeated contextmenu when it is already open closes it with Chrome 84 on Ubuntu
|
|
|
+ // Other native context menus might behave different.
|
|
|
+ // With this behavior we prevent contextmenu from the backdrop to re-locale existing context menus.
|
|
|
+ null
|
|
|
+ );
|
|
|
+ setContextData({ datasource, tableName });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleClose = () => {
|
|
|
+ setContextMenu(null);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleContent = () => {
|
|
|
+ setContextMenu(null);
|
|
|
+ onOpenTable(contextData.datasource, contextData.tableName, 'content');
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSchema = () => {
|
|
|
+ setContextMenu(null);
|
|
|
+ onOpenTable(contextData.datasource, contextData.tableName, 'schema');
|
|
|
+ };
|
|
|
+
|
|
|
+ const contextMenuView = (
|
|
|
+ <Menu
|
|
|
+ open={contextMenu !== null}
|
|
|
+ onClose={handleClose}
|
|
|
+ anchorReference="anchorPosition"
|
|
|
+ anchorPosition={
|
|
|
+ contextMenu !== null
|
|
|
+ ? { top: contextMenu.mouseY, left: contextMenu.mouseX }
|
|
|
+ : undefined
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <MenuItem onClick={handleContent}>预览表内容</MenuItem>
|
|
|
+ <MenuItem onClick={handleSchema}>预览表结构</MenuItem>
|
|
|
+ </Menu>
|
|
|
+ );
|
|
|
+
|
|
|
+ const handleNodeToggle = async (id: number) => {
|
|
|
+ if (!tables[id]) {
|
|
|
+ const tables = await fetchTables(id);
|
|
|
setTables(prev => {
|
|
|
- return { [ds.sourcename]: tables, ...prev };
|
|
|
+ return { [id]: tables, ...prev };
|
|
|
});
|
|
|
}
|
|
|
};
|
|
@@ -49,29 +116,26 @@ const DatasourceView: React.FunctionComponent<IProps> = ({
|
|
|
tree = <div style={{ padding: '0px 12px' }}>Loading ...</div>;
|
|
|
} else {
|
|
|
tree = datasources!.map(ds => {
|
|
|
- const table = tables[ds.sourcename];
|
|
|
+ const table = tables[ds.id];
|
|
|
let tableItems;
|
|
|
if (!table) {
|
|
|
tableItems = (
|
|
|
- <TreeItem nodeId={`${ds.sourcename}-loading`} label="Loading ..." />
|
|
|
+ <TreeItem nodeId={`${ds.id}-loading`} label="Loading ..." />
|
|
|
);
|
|
|
} else if (table === 'error') {
|
|
|
- tableItems = (
|
|
|
- <TreeItem nodeId={`${ds.sourcename}-error`} label="Error" />
|
|
|
- );
|
|
|
+ tableItems = <TreeItem nodeId={`${ds.id}-error`} label="Error" />;
|
|
|
} else if (table.length === 0) {
|
|
|
- tableItems = (
|
|
|
- <TreeItem nodeId={`${ds.sourcename}-error`} label="Empty" />
|
|
|
- );
|
|
|
+ tableItems = <TreeItem nodeId={`${ds.id}-error`} label="Empty" />;
|
|
|
} else {
|
|
|
tableItems = table.map(t => {
|
|
|
return (
|
|
|
<TreeItem
|
|
|
title={t}
|
|
|
- key={`${ds.sourcename}-${t}`}
|
|
|
- nodeId={`${ds.sourcename}-${t}`}
|
|
|
+ key={`${ds.id}-${t}`}
|
|
|
+ nodeId={`${ds.id}-${t}`}
|
|
|
label={t}
|
|
|
- onDoubleClick={() => onOpenTable(ds, t)}
|
|
|
+ onDoubleClick={() => onOpenTable(ds, t, 'content')}
|
|
|
+ onContextMenu={evt => handleContextMenu(evt, ds, t)}
|
|
|
draggable="true"
|
|
|
onDragStart={evt => {
|
|
|
const data = {
|
|
@@ -80,8 +144,8 @@ const DatasourceView: React.FunctionComponent<IProps> = ({
|
|
|
editType: 'createNode',
|
|
|
finalized: true,
|
|
|
nodeTemplate: {
|
|
|
- id: `${ds.manager}-${ds.sourcename}-${t}`,
|
|
|
- op: `database-${ds.source}-input`,
|
|
|
+ id: `${ds.manager}-${ds.id}-${t}`,
|
|
|
+ op: `database-${ds.datasource}-input`,
|
|
|
description: t,
|
|
|
type: 'execution_node',
|
|
|
label: t,
|
|
@@ -121,12 +185,13 @@ const DatasourceView: React.FunctionComponent<IProps> = ({
|
|
|
}
|
|
|
return (
|
|
|
<TreeItem
|
|
|
- key={ds.sourcename}
|
|
|
- nodeId={ds.sourcename}
|
|
|
- label={ds.databasename}
|
|
|
- onClick={() => handleNodeToggle(ds)}
|
|
|
+ key={ds.id}
|
|
|
+ nodeId={ds.id.toString()}
|
|
|
+ label={ds.database_name}
|
|
|
+ onClick={() => handleNodeToggle(ds.id)}
|
|
|
>
|
|
|
{tableItems}
|
|
|
+ {contextMenuView}
|
|
|
</TreeItem>
|
|
|
);
|
|
|
});
|