mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
feat(core): refresh metadata
CB-43
This commit is contained in:
@@ -39,7 +39,7 @@ export class UsersManagerService {
|
||||
async grantRole(userId: string, roleId: string, update?: boolean) {
|
||||
await this.graphQLService.gql.grantUserRole({ userId, roleId });
|
||||
if (update) {
|
||||
await this.users.refresh(userId);
|
||||
await this.users.refresh(true, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export class UsersManagerService {
|
||||
await this.graphQLService.gql.deleteUser({ userId });
|
||||
|
||||
if (update) {
|
||||
await this.users.refresh(userId);
|
||||
await this.users.refresh(true, userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import styled, { css } from 'reshadow';
|
||||
|
||||
import { usePermission, EPermission } from '@dbeaver/core/root';
|
||||
|
||||
import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeManagerService';
|
||||
import { useChildren } from '../shared/useChildren';
|
||||
import { NavigationTreeNode } from './NavigationTreeNode/NavigationTreeNode';
|
||||
|
||||
@@ -46,7 +47,7 @@ export const NavigationTree = observer(function NavigationTree() {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!nodeChildren || nodeChildren.children.length === 0) {
|
||||
if (!nodeChildren.children || nodeChildren.children.length === 0) {
|
||||
return styled(navigationTreeStyles)(
|
||||
<center as="div">
|
||||
<tree as="div">
|
||||
@@ -62,7 +63,7 @@ export const NavigationTree = observer(function NavigationTree() {
|
||||
return styled(navigationTreeStyles)(
|
||||
<tree as="div">
|
||||
{nodeChildren.children.map(id => (
|
||||
<NavigationTreeNode id={id} key={id} />
|
||||
<NavigationTreeNode key={id} id={id} parentId={ROOT_NODE_PATH}/>
|
||||
))}
|
||||
</tree>
|
||||
);
|
||||
|
||||
@@ -7,63 +7,65 @@
|
||||
*/
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import {
|
||||
ContextMenuService, IContextMenuItem, IMenuContext, IMenuPanel,
|
||||
} from '@dbeaver/core/dialogs';
|
||||
import { ContextMenuService, IMenuPanel } from '@dbeaver/core/dialogs';
|
||||
|
||||
import { NodesManagerService } from '../shared/NodesManager/NodesManagerService';
|
||||
import { NodeWithParent } from '../shared/NodesManager/NodeWithParent';
|
||||
import { NavNode } from '../shared/NodesManager/EntityTypes';
|
||||
import { NavNodeManagerService } from '../shared/NodesManager/NavNodeManagerService';
|
||||
|
||||
@injectable()
|
||||
export class NavigationTreeContextMenuService {
|
||||
static nodeContextType = 'NodeWithParent';
|
||||
private static menuToken = 'navTreeMenu';
|
||||
|
||||
constructor(private contextMenuService: ContextMenuService,
|
||||
private nodesManagerService: NodesManagerService) {
|
||||
}
|
||||
constructor(
|
||||
private contextMenuService: ContextMenuService,
|
||||
private navNodeManagerService: NavNodeManagerService
|
||||
) { }
|
||||
|
||||
getMenuToken() {
|
||||
return NavigationTreeContextMenuService.menuToken;
|
||||
}
|
||||
|
||||
constructMenuWithContext(node: NodeWithParent): IMenuPanel {
|
||||
const context: IMenuContext<NodeWithParent> = {
|
||||
constructMenuWithContext(node: NavNode): IMenuPanel {
|
||||
return this.contextMenuService.createContextMenu<NavNode>({
|
||||
menuId: this.getMenuToken(),
|
||||
contextId: node.id,
|
||||
contextType: NavigationTreeContextMenuService.nodeContextType,
|
||||
data: node,
|
||||
};
|
||||
return this.contextMenuService.createContextMenu(context);
|
||||
});
|
||||
}
|
||||
|
||||
registerMenuItems() {
|
||||
const openNodeTab: IContextMenuItem<NodeWithParent> = {
|
||||
id: 'openNodeTab',
|
||||
isPresent(context) {
|
||||
return context.contextType === NavigationTreeContextMenuService.nodeContextType;
|
||||
},
|
||||
order: 1,
|
||||
title: 'app_navigationTree_openNodeTab',
|
||||
onClick: (context: IMenuContext<NodeWithParent>) => {
|
||||
const node = context.data;
|
||||
this.nodesManagerService.navToNode(node.id);
|
||||
},
|
||||
};
|
||||
this.contextMenuService.addMenuItem<NodeWithParent>(this.contextMenuService.getRootMenuToken(), openNodeTab);
|
||||
this.contextMenuService.addMenuItem<NavNode>(
|
||||
this.contextMenuService.getRootMenuToken(),
|
||||
{
|
||||
id: 'openNodeTab',
|
||||
isPresent(context) {
|
||||
return context.contextType === NavigationTreeContextMenuService.nodeContextType;
|
||||
},
|
||||
order: 1,
|
||||
title: 'app_navigationTree_openNodeTab',
|
||||
onClick: (context) => {
|
||||
const node = context.data;
|
||||
this.navNodeManagerService.navToNode(node.id, node.parentId);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const refreshNode: IContextMenuItem<NodeWithParent> = {
|
||||
id: 'refreshNode',
|
||||
isPresent(context) {
|
||||
return context.contextType === NavigationTreeContextMenuService.nodeContextType;
|
||||
},
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
title: 'app_navigationTree_refreshNode',
|
||||
onClick: (context: IMenuContext<NodeWithParent>) => {
|
||||
const node = context.data;
|
||||
this.nodesManagerService.refreshNode(node.id);
|
||||
},
|
||||
};
|
||||
this.contextMenuService.addMenuItem<NodeWithParent>(this.contextMenuService.getRootMenuToken(), refreshNode);
|
||||
this.contextMenuService.addMenuItem<NavNode>(
|
||||
this.contextMenuService.getRootMenuToken(),
|
||||
{
|
||||
id: 'refreshNode',
|
||||
isPresent(context) {
|
||||
return context.contextType === NavigationTreeContextMenuService.nodeContextType;
|
||||
},
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
title: 'app_navigationTree_refreshNode',
|
||||
onClick: (context) => {
|
||||
const node = context.data;
|
||||
this.navNodeManagerService.refresh(node.id);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+27
-40
@@ -21,13 +21,13 @@ type NodeProps = PropsWithChildren<{
|
||||
title?: string;
|
||||
icon?: string;
|
||||
isExpanded: boolean;
|
||||
isLoaded?: boolean;
|
||||
isLoading: boolean;
|
||||
isSelected: boolean;
|
||||
portal?: JSX.Element;
|
||||
isExpandable?: boolean;
|
||||
onExpand?: (id: string) => void;
|
||||
onDoubleClick?: (id: string) => void;
|
||||
onClick?: (id: string, isMultiple?: boolean) => void;
|
||||
onExpand?: () => void;
|
||||
onDoubleClick?: () => void;
|
||||
onClick?: (isMultiple?: boolean) => void;
|
||||
}>
|
||||
|
||||
const KEYCODE = {
|
||||
@@ -36,23 +36,19 @@ const KEYCODE = {
|
||||
DOWN: 40,
|
||||
};
|
||||
|
||||
export function NavigationNode(
|
||||
props: NodeProps
|
||||
) {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
icon,
|
||||
children,
|
||||
isLoaded,
|
||||
isExpanded,
|
||||
isSelected,
|
||||
portal,
|
||||
isExpandable,
|
||||
onExpand,
|
||||
onDoubleClick,
|
||||
onClick,
|
||||
} = props;
|
||||
export function NavigationNode({
|
||||
title,
|
||||
icon,
|
||||
children,
|
||||
isLoading,
|
||||
isExpanded,
|
||||
isSelected,
|
||||
portal,
|
||||
isExpandable,
|
||||
onExpand,
|
||||
onDoubleClick,
|
||||
onClick,
|
||||
}: NodeProps) {
|
||||
|
||||
const styles = useStyles(navigationNodeStyles);
|
||||
|
||||
@@ -60,28 +56,19 @@ export function NavigationNode(
|
||||
(e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation();
|
||||
if (onExpand) {
|
||||
onExpand(id);
|
||||
onExpand();
|
||||
}
|
||||
},
|
||||
[id, onExpand]
|
||||
);
|
||||
|
||||
const handleDoubleClick = useCallback(
|
||||
(e: MouseEvent<HTMLDivElement>) => {
|
||||
if (onDoubleClick) {
|
||||
onDoubleClick(id);
|
||||
}
|
||||
},
|
||||
[id, onDoubleClick]
|
||||
[onExpand]
|
||||
);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: MouseEvent<HTMLDivElement>) => {
|
||||
if (onClick) {
|
||||
onClick(id, e.ctrlKey);
|
||||
onClick(e.ctrlKey);
|
||||
}
|
||||
},
|
||||
[id, onClick]
|
||||
[onClick]
|
||||
);
|
||||
|
||||
const handleEnter = useCallback(
|
||||
@@ -89,29 +76,29 @@ export function NavigationNode(
|
||||
switch (event.keyCode) {
|
||||
case KEYCODE.ENTER:
|
||||
if (onClick) {
|
||||
onClick(id, event.ctrlKey);
|
||||
onClick(event.ctrlKey);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[id, onClick]
|
||||
[onClick]
|
||||
);
|
||||
|
||||
return styled(styles)(
|
||||
<>
|
||||
<node as="div" {...use({ isExpanded: isExpanded && isLoaded })}>
|
||||
<node as="div" {...use({ isExpanded: isExpanded && isExpandable })}>
|
||||
<control
|
||||
tabIndex={0}
|
||||
aria-selected={isSelected}
|
||||
onClick={handleClick}
|
||||
onKeyDown={handleEnter}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
as="div"
|
||||
>
|
||||
<arrow as="div" hidden={!isExpandable} onClick={handleExpand}>
|
||||
{!isLoaded && isExpanded && <Loader small />}
|
||||
{(isLoaded || !isExpanded) && <Icon name="arrow" viewBox="0 0 16 16" />}
|
||||
{isLoading && <Loader small />}
|
||||
{!isLoading && <Icon name="arrow" viewBox="0 0 16 16" />}
|
||||
</arrow>
|
||||
<icon as="div"><StaticImage icon={icon} /></icon>
|
||||
<name as="div">{title}</name>
|
||||
|
||||
+8
-5
@@ -13,24 +13,27 @@ import { useChildren } from '../../shared/useChildren';
|
||||
|
||||
type NavigationNodeChildrenProps = PropsWithChildren<{
|
||||
parentId: string;
|
||||
component: React.ElementType<{id: string}>;
|
||||
component: React.ElementType<{
|
||||
id: string;
|
||||
parentId: string;
|
||||
}>;
|
||||
}>
|
||||
|
||||
export const NavigationNodeChildren = observer(function NavigationNodeChildren({
|
||||
parentId,
|
||||
component,
|
||||
}: NavigationNodeChildrenProps) {
|
||||
const nodeChildren = useChildren(parentId);
|
||||
const { children } = useChildren(parentId);
|
||||
|
||||
if (!nodeChildren?.isLoaded) {
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const NavigationNodeChild = component;
|
||||
return (
|
||||
<>
|
||||
{nodeChildren.children!.map(id => (
|
||||
<NavigationNodeChild id={id} key={id} />
|
||||
{children.map(id => (
|
||||
<NavigationNodeChild id={id} parentId={parentId} key={id} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
+6
-4
@@ -15,12 +15,14 @@ import { useNavigationTree } from './useNavigationTree';
|
||||
|
||||
type NavigationTreeNodeProps = {
|
||||
id: string;
|
||||
parentId: string;
|
||||
}
|
||||
|
||||
export const NavigationTreeNode = observer(function NavigationTreeNodeFn({
|
||||
id,
|
||||
parentId,
|
||||
}: NavigationTreeNodeProps) {
|
||||
const node = useNavigationTree(id);
|
||||
const node = useNavigationTree(id, parentId);
|
||||
|
||||
if (!node) {
|
||||
return null;
|
||||
@@ -33,12 +35,12 @@ export const NavigationTreeNode = observer(function NavigationTreeNodeFn({
|
||||
icon={node.icon}
|
||||
isSelected={node.isSelected}
|
||||
isExpanded={node.isExpanded}
|
||||
isLoaded={node.isLoaded}
|
||||
isExpandable={node.hasChildren && node.isExpandable}
|
||||
isLoading={node.isLoading}
|
||||
isExpandable={node.isExpandable}
|
||||
onExpand={node.handleExpand}
|
||||
onClick={node.handleSelect}
|
||||
onDoubleClick={node.handleDoubleClick}
|
||||
portal={<TreeNodeMenu nodeId={id} isSelected={node?.isSelected} />}
|
||||
portal={<TreeNodeMenu node={node.node} isSelected={node.isSelected} />}
|
||||
>
|
||||
<NavigationNodeChildren
|
||||
parentId={id}
|
||||
|
||||
+5
-6
@@ -15,32 +15,31 @@ import { Icon } from '@dbeaver/core/blocks';
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
import { MenuTrigger } from '@dbeaver/core/dialogs';
|
||||
|
||||
import { useNode } from '../../../shared/NodesManager/useNode';
|
||||
import { NavNode } from '../../../shared/NodesManager/EntityTypes';
|
||||
import { NavigationTreeContextMenuService } from '../../NavigationTreeContextMenuService';
|
||||
import { treeNodeMenuStyles } from './treeNodeMenuStyles';
|
||||
|
||||
|
||||
type TreeNodeMenuProps = {
|
||||
nodeId: string;
|
||||
node: NavNode;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
export const TreeNodeMenu = observer(function TreeNodeMenu({
|
||||
nodeId,
|
||||
node,
|
||||
isSelected,
|
||||
}: TreeNodeMenuProps) {
|
||||
|
||||
const navigationTreeContextMenuService = useService(NavigationTreeContextMenuService);
|
||||
const node = useNode(nodeId)!;
|
||||
|
||||
const menuPanel = useMemo(
|
||||
() => navigationTreeContextMenuService.constructMenuWithContext(node),
|
||||
[nodeId]
|
||||
[node]
|
||||
);
|
||||
const isHidden = useMemo(
|
||||
() => computed(() => !menuPanel.menuItems.length
|
||||
|| menuPanel.menuItems.every(item => item.isHidden)),
|
||||
[nodeId]
|
||||
[menuPanel]
|
||||
);
|
||||
|
||||
if (isHidden.get()) {
|
||||
|
||||
+29
-18
@@ -10,49 +10,52 @@ import { useState, useCallback, useEffect } from 'react';
|
||||
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
|
||||
import { NavNode } from '../../shared/NodesManager/EntityTypes';
|
||||
import { EObjectFeature } from '../../shared/NodesManager/EObjectFeature';
|
||||
import { NodesManagerService } from '../../shared/NodesManager/NodesManagerService';
|
||||
import { NodeWithParent } from '../../shared/NodesManager/NodeWithParent';
|
||||
import { NavNodeManagerService } from '../../shared/NodesManager/NavNodeManagerService';
|
||||
import { useNode } from '../../shared/NodesManager/useNode';
|
||||
import { useChildren } from '../../shared/useChildren';
|
||||
import { NavigationTreeService } from '../NavigationTreeService';
|
||||
|
||||
|
||||
export function useNavigationTree(nodeId: string) {
|
||||
export function useNavigationTree(nodeId: string, parentId: string) {
|
||||
const navigationTreeService = useService(NavigationTreeService);
|
||||
const nodesManagerService = useService(NodesManagerService);
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const [isExpanded, switchExpand] = useState(false);
|
||||
const [isSelected, switchSelect] = useState(false);
|
||||
const node = useNode(nodeId);
|
||||
const { node, isLoaded: nodeLoaded } = useNode(nodeId);
|
||||
const children = useChildren(nodeId);
|
||||
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isLoaded = children?.isLoaded;
|
||||
const isExpandable = isExpandableFilter(node) && (!children?.isLoaded || children.children.length > 0);
|
||||
const isLoaded = children.isLoaded;
|
||||
const isExpandable = isExpandableFilter(node) && (!isLoaded || children.children!.length > 0);
|
||||
|
||||
const handleDoubleClick = useCallback(
|
||||
(nodeId: string) => nodesManagerService.navToNode(nodeId),
|
||||
[nodesManagerService]
|
||||
() => navNodeManagerService.navToNode(nodeId, parentId),
|
||||
[navNodeManagerService, nodeId, parentId]
|
||||
);
|
||||
|
||||
const handleExpand = useCallback(
|
||||
(id: string) => {
|
||||
async () => {
|
||||
if (!isExpanded) {
|
||||
navigationTreeService.loadNestedNodes(id).then(state => !state && switchExpand(false));
|
||||
const state = await navigationTreeService.loadNestedNodes(nodeId);
|
||||
if (!state) {
|
||||
switchExpand(false);
|
||||
}
|
||||
}
|
||||
switchExpand(!isExpanded);
|
||||
},
|
||||
[isExpanded]
|
||||
[isExpanded, nodeId]
|
||||
);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(id: string, isMultiple?: boolean) => {
|
||||
switchSelect(navigationTreeService.selectNode(id, isMultiple));
|
||||
(isMultiple?: boolean) => {
|
||||
switchSelect(navigationTreeService.selectNode(nodeId, isMultiple));
|
||||
},
|
||||
[isSelected]
|
||||
[isSelected, nodeId]
|
||||
);
|
||||
|
||||
const {
|
||||
@@ -65,17 +68,25 @@ export function useNavigationTree(nodeId: string) {
|
||||
}
|
||||
}, [isExpandable && hasChildren]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isExpanded && !children.isLoaded && !children.isLoading && !!children.children && nodeLoaded) {
|
||||
navigationTreeService.loadNestedNodes(nodeId);
|
||||
}
|
||||
}, [isExpanded, children.isLoaded, children.isLoading, children.children, nodeLoaded, nodeId]);
|
||||
|
||||
// Here we subscribe to selected nodes if current node selected (mobx)
|
||||
if (isSelected && !navigationTreeService.selectedNodes.includes(node.id)) {
|
||||
if (isSelected && !navigationTreeService.selectedNodes.includes(nodeId)) {
|
||||
switchSelect(false);
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
node,
|
||||
nodeType,
|
||||
icon,
|
||||
isExpanded,
|
||||
isLoaded,
|
||||
isLoading: children.isLoading,
|
||||
isExpandable,
|
||||
isSelected,
|
||||
hasChildren,
|
||||
@@ -85,6 +96,6 @@ export function useNavigationTree(nodeId: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function isExpandableFilter(node: NodeWithParent) {
|
||||
return !node.object?.features?.find(feature => feature === EObjectFeature.entity);
|
||||
export function isExpandableFilter(node: NavNode) {
|
||||
return node.hasChildren && !node.objectFeatures.includes(EObjectFeature.entity);
|
||||
}
|
||||
|
||||
@@ -11,23 +11,22 @@ import { observable } from 'mobx';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { NotificationService } from '@dbeaver/core/eventsLog';
|
||||
|
||||
import { NodesManagerService } from '../shared/NodesManager/NodesManagerService';
|
||||
import { NavNodeManagerService, ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeManagerService';
|
||||
|
||||
@injectable()
|
||||
export class NavigationTreeService {
|
||||
|
||||
selectedNodes = observable.array<string>([]);
|
||||
|
||||
constructor(private nodesManagerService: NodesManagerService,
|
||||
private notificationService: NotificationService) {
|
||||
}
|
||||
constructor(
|
||||
private NavNodeManagerService: NavNodeManagerService,
|
||||
private notificationService: NotificationService
|
||||
) { }
|
||||
|
||||
async loadNestedNodes(id = '/') {
|
||||
async loadNestedNodes(id = ROOT_NODE_PATH) {
|
||||
try {
|
||||
const children = await this.nodesManagerService.loadChildren(id);
|
||||
if (children) {
|
||||
return true;
|
||||
}
|
||||
await this.NavNodeManagerService.loadTree(id);
|
||||
return true;
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, `Can't load tree node: ${id}`);
|
||||
}
|
||||
|
||||
@@ -15,15 +15,16 @@ export * from './shared/NodesManager/extensions/IObjectCatalogProvider';
|
||||
export * from './shared/NodesManager/extensions/IObjectCatalogSetter';
|
||||
export * from './shared/NodesManager/extensions/IObjectSchemaProvider';
|
||||
export * from './shared/NodesManager/extensions/IObjectSchemaSetter';
|
||||
export * from './shared/NodesManager/DBObjectService';
|
||||
export * from './shared/NodesManager/NavNodeManagerService';
|
||||
export * from './shared/NodesManager/NodeManagerUtils';
|
||||
export * from './shared/Navigation/NavigationService';
|
||||
export * from './shared/NavigationTabs/NavigationTabsService';
|
||||
export * from './shared/NavigationTabs/TabNavigationContext';
|
||||
export * from './shared/NodesManager/NodesManagerService';
|
||||
export * from './NavigationTree/NavigationTreeService';
|
||||
export * from './NavigationTree/NavigationTreeContextMenuService';
|
||||
export * from './shared/ToolsPanel/LogViewTab/LogViewerMenuService';
|
||||
export * from './shared/ToolsPanel/LogViewTab/LogViewerService';
|
||||
export * from './NavigationTree/NavigationTreeService';
|
||||
export * from './NavigationTree/NavigationTreeContextMenuService';
|
||||
export * from './Notifications/NotificationsItem/Snackbar/styles';
|
||||
export * from './Notifications/NotificationsItem/Snackbar/NotificationMark';
|
||||
|
||||
@@ -33,7 +34,8 @@ export * from './shared/NavigationTabs/TabHandler';
|
||||
export * from './shared/Navigation/NavigationContext';
|
||||
export * from './shared/TabEntity/TabEntity';
|
||||
// Enums
|
||||
// export * from '';
|
||||
export * from './shared/NodesManager/ENodeFeature';
|
||||
export * from './shared/NodesManager/EObjectFeature';
|
||||
|
||||
// hooks
|
||||
export * from './shared/useChildren';
|
||||
@@ -50,10 +52,8 @@ export * from './TopNavBar/Logo';
|
||||
|
||||
// Interfaces
|
||||
export * from './shared/Navigation/INavigator';
|
||||
export * from './shared/NodesManager/ENodeFeature';
|
||||
export * from './shared/NodesManager/EObjectFeature';
|
||||
export * from './shared/NodesManager/NodeManagerUtils';
|
||||
export * from './shared/NodesManager/NodeWithParent';
|
||||
export * from './shared/NodesManager/EntityTypes';
|
||||
|
||||
// tab entity
|
||||
export * from './shared/TabEntity/TabContainerEntity';
|
||||
|
||||
@@ -15,9 +15,9 @@ import {
|
||||
|
||||
import { NavigationTreeContextMenuService } from '../../NavigationTree/NavigationTreeContextMenuService';
|
||||
import { EMainMenu, MainMenuService } from '../../TopNavBar/MainMenu/MainMenuService';
|
||||
import { NavNode } from '../NodesManager/EntityTypes';
|
||||
import { EObjectFeature } from '../NodesManager/EObjectFeature';
|
||||
import { NodeManagerUtils } from '../NodesManager/NodeManagerUtils';
|
||||
import { NodeWithParent } from '../NodesManager/NodeWithParent';
|
||||
import { ConnectionsManagerService } from './ConnectionsManagerService';
|
||||
|
||||
|
||||
@@ -52,20 +52,20 @@ export class ConnectionDialogsService {
|
||||
}
|
||||
);
|
||||
|
||||
const closeConnection: IContextMenuItem<NodeWithParent> = {
|
||||
const closeConnection: IContextMenuItem<NavNode> = {
|
||||
id: 'closeConnection',
|
||||
isPresent(context) {
|
||||
return context.contextType === NavigationTreeContextMenuService.nodeContextType
|
||||
&& !!context.data.object?.features?.includes(EObjectFeature.dataSource);
|
||||
&& !!context.data.objectFeatures.includes(EObjectFeature.dataSource);
|
||||
},
|
||||
title: 'Disconnect',
|
||||
onClick: (context: IMenuContext<NodeWithParent>) => {
|
||||
onClick: (context: IMenuContext<NavNode>) => {
|
||||
const node = context.data;
|
||||
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(node.id);
|
||||
this.connectionsManagerService.closeConnectionAsync(connectionId);
|
||||
},
|
||||
};
|
||||
|
||||
this.contextMenuService.addMenuItem<NodeWithParent>(this.contextMenuService.getRootMenuToken(), closeConnection);
|
||||
this.contextMenuService.addMenuItem<NavNode>(this.contextMenuService.getRootMenuToken(), closeConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
DatabaseObjectInfo,
|
||||
} from '@dbeaver/core/sdk';
|
||||
|
||||
import { NodesManagerService } from '../NodesManager/NodesManagerService';
|
||||
import { NavNodeManagerService } from '../NodesManager/NavNodeManagerService';
|
||||
|
||||
export type DBDriver = Pick<
|
||||
DriverInfo,
|
||||
@@ -67,7 +67,7 @@ export class ConnectionsManagerService {
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private nodesManagerService: NodesManagerService,
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private sessionService: SessionService
|
||||
) {
|
||||
this.sessionService.onUpdate.subscribe(this.restoreConnections.bind(this));
|
||||
@@ -81,10 +81,10 @@ export class ConnectionsManagerService {
|
||||
return this.dbDrivers.load();
|
||||
}
|
||||
|
||||
addOpenedConnection(connection: Connection) {
|
||||
async addOpenedConnection(connection: Connection) {
|
||||
this.connectionsMap.set(connection.id, connection);
|
||||
this.onOpenConnection.next(connection);
|
||||
this.nodesManagerService.updateRootChildren(); // Update connections list, probably here we must also request node info and add it to nodes manager
|
||||
await this.navNodeManagerService.updateRootChildren(); // Update connections list, probably here we must also request node info and add it to nodes manager
|
||||
}
|
||||
|
||||
getConnectionById(connectionId: string): Connection | undefined {
|
||||
@@ -113,7 +113,7 @@ export class ConnectionsManagerService {
|
||||
for (const connection of this.connections) {
|
||||
await this.closeConnectionAsync(connection.id, true);
|
||||
}
|
||||
await this.nodesManagerService.updateRootChildren();
|
||||
await this.navNodeManagerService.updateRootChildren();
|
||||
}
|
||||
|
||||
async closeConnectionAsync(id: string, skipNodesRefresh?: boolean): Promise<void> {
|
||||
@@ -122,7 +122,7 @@ export class ConnectionsManagerService {
|
||||
this.connectionsMap.delete(id);
|
||||
|
||||
if (!skipNodesRefresh) {
|
||||
await this.nodesManagerService.updateRootChildren(); // Update connections list, probably here we must just remove nodes from nodes manager
|
||||
await this.navNodeManagerService.updateRootChildren(); // Update connections list, probably here we must just remove nodes from nodes manager
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ export class ConnectionsManagerService {
|
||||
}
|
||||
|
||||
private async afterConnectionClose(id: string) {
|
||||
await this.nodesManagerService.closeConnection(id);
|
||||
await this.navNodeManagerService.remove(id);
|
||||
this.onCloseConnection.next(id);
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ export class ConnectionsManagerService {
|
||||
this.connectionsMap.delete(connection.id);
|
||||
}
|
||||
|
||||
await this.nodesManagerService.updateRootChildren();
|
||||
await this.navNodeManagerService.updateRootChildren();
|
||||
}
|
||||
|
||||
private isObjectContainersLoaded(
|
||||
|
||||
@@ -14,4 +14,5 @@ export interface ITabOptions<T = any> {
|
||||
|
||||
export interface ITab<T = any> extends ITabOptions<T> {
|
||||
id: string;
|
||||
restored: boolean;
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ export class NavigationTabsService {
|
||||
typeof value.id === 'string'
|
||||
&& typeof value.handlerId === 'string'
|
||||
) {
|
||||
value.restored = false;
|
||||
map[key] = value;
|
||||
}
|
||||
}
|
||||
@@ -86,7 +87,7 @@ export class NavigationTabsService {
|
||||
}
|
||||
}
|
||||
|
||||
@action selectTab(tabId: string, skipHandlers?: boolean) {
|
||||
@action async selectTab(tabId: string, skipHandlers?: boolean) {
|
||||
const tab = this.tabsMap.get(tabId);
|
||||
if (!tab) {
|
||||
return;
|
||||
@@ -99,7 +100,7 @@ export class NavigationTabsService {
|
||||
}
|
||||
|
||||
if (!skipHandlers) {
|
||||
this.callHandlerCallback(tab, handler => handler.onSelect);
|
||||
await this.callHandlerCallback(tab, handler => handler.onSelect);
|
||||
}
|
||||
|
||||
this.tabSelectSubject.next(tab);
|
||||
@@ -177,7 +178,6 @@ export class NavigationTabsService {
|
||||
// must be executed with low priority, because this call runs many requests to backend and blocks others
|
||||
async restoreTabs() {
|
||||
const removedTabs: string[] = [];
|
||||
const restoreTasks: Promise<void>[] = [];
|
||||
const session = await this.sessionService.session.load();
|
||||
|
||||
for (const tabId of this.state.tabs) {
|
||||
@@ -192,10 +192,9 @@ export class NavigationTabsService {
|
||||
continue;
|
||||
}
|
||||
|
||||
restoreTasks.push(this.restoreTab(tab, removedTabs));
|
||||
await this.restoreTab(tab, removedTabs);
|
||||
}
|
||||
|
||||
await Promise.all(restoreTasks);
|
||||
|
||||
if (removedTabs.length > 0) {
|
||||
this.notificationService.logError({ title: 'Some tabs cannot be restored properly', isSilent: true });
|
||||
@@ -237,6 +236,8 @@ export class NavigationTabsService {
|
||||
|
||||
if (restoreFail) {
|
||||
removedTabs.push(tab.id);
|
||||
} else {
|
||||
tab.restored = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export class TabNavigationContext implements ITabNavigationContext {
|
||||
openNewTab<T = any>(options: ITabOptions<T>): ITab<T> {
|
||||
this._tab = observable({
|
||||
id: uuid(),
|
||||
restored: true,
|
||||
...options,
|
||||
});
|
||||
this._isNewlyCreated = true;
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { GraphQLService, CachedResource } from '@dbeaver/core/sdk';
|
||||
import { MetadataMap } from '@dbeaver/core/utils';
|
||||
|
||||
import { DBObject } from './EntityTypes';
|
||||
|
||||
export interface IDBObjectParams {
|
||||
navNodeId: string[];
|
||||
remove?: boolean;
|
||||
}
|
||||
|
||||
interface IDBObjectMetadata {
|
||||
loaded: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class DBObjectService {
|
||||
readonly dbObject = new CachedResource(
|
||||
new Map(),
|
||||
this.loadDBObject.bind(this),
|
||||
(_, metadata, { navNodeId }) => navNodeId.every(navNodeId => metadata.get(navNodeId).loaded),
|
||||
new MetadataMap<string, IDBObjectMetadata>(() => ({ loaded: false, loading: false })),
|
||||
(_, metadata, { navNodeId }) => navNodeId.some(navNodeId => metadata.get(navNodeId).loading)
|
||||
)
|
||||
|
||||
constructor(private graphQLService: GraphQLService) { }
|
||||
|
||||
getDBObject(navNodeId: string) {
|
||||
return this.dbObject.data.get(navNodeId);
|
||||
}
|
||||
|
||||
async load(navNodeId: string): Promise< DBObject>
|
||||
async load(navNodeId: string[]): Promise<DBObject[]>
|
||||
async load(navNodeId: string | string[]): Promise<DBObject | DBObject[]> {
|
||||
const dbObject = await this.dbObject.load({ navNodeId: Array.isArray(navNodeId) ? navNodeId : [navNodeId] });
|
||||
|
||||
if (!Array.isArray(navNodeId)) {
|
||||
return dbObject.get(navNodeId)!;
|
||||
}
|
||||
return navNodeId.map(navNodeId => dbObject.get(navNodeId)!);
|
||||
}
|
||||
|
||||
async remove(navNodeId: string[]) {
|
||||
await this.dbObject.refresh(true, { navNodeId, remove: true });
|
||||
}
|
||||
|
||||
private async loadDBObject(
|
||||
dbObject: Map<string, DBObject>,
|
||||
metadata: MetadataMap<string, IDBObjectMetadata>,
|
||||
load: boolean,
|
||||
data: IDBObjectParams
|
||||
) {
|
||||
for (const navNodeId of data.navNodeId) {
|
||||
if (data.remove) {
|
||||
dbObject.delete(navNodeId);
|
||||
metadata.delete(navNodeId);
|
||||
} else {
|
||||
const itemMetadata = metadata.get(navNodeId);
|
||||
|
||||
itemMetadata.loaded = false;
|
||||
if (load) {
|
||||
itemMetadata.loading = true;
|
||||
|
||||
try {
|
||||
const { objectInfo: { object } } = await this.graphQLService.gql.getDBObjectInfo({
|
||||
navNodeId,
|
||||
});
|
||||
|
||||
dbObject.set(navNodeId, { navNodeId, ...object } as DBObject);
|
||||
itemMetadata.loaded = true;
|
||||
} finally {
|
||||
itemMetadata.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dbObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { DatabaseObjectInfo, NavigatorNodeInfo } from '@dbeaver/core/sdk';
|
||||
|
||||
export type NavNodeInfo = NavigatorNodeInfo;
|
||||
|
||||
export type NavNode = Omit<NavNodeInfo, 'object'> & {
|
||||
parentId: string;
|
||||
objectFeatures: string[];
|
||||
}
|
||||
|
||||
export type DBObject = DatabaseObjectInfo & {
|
||||
navNodeId: string;
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { PermissionsService, EPermission } from '@dbeaver/core/root';
|
||||
import { GraphQLService, CachedResource } from '@dbeaver/core/sdk';
|
||||
import { MetadataMap } from '@dbeaver/core/utils';
|
||||
|
||||
import { INavigator } from '../Navigation/INavigator';
|
||||
import { IContextProvider } from '../Navigation/NavigationContext';
|
||||
import { NavigationService } from '../Navigation/NavigationService';
|
||||
import { DBObjectService } from './DBObjectService';
|
||||
import { ENodeFeature } from './ENodeFeature';
|
||||
import { NavNodeInfo, NavNode } from './EntityTypes';
|
||||
import { EObjectFeature } from './EObjectFeature';
|
||||
import { NodeManagerUtils } from './NodeManagerUtils';
|
||||
|
||||
export enum NavigationType {
|
||||
open,
|
||||
closeConnection
|
||||
}
|
||||
|
||||
export interface NavNodeKey {
|
||||
nodeId: string;
|
||||
parentId: string;
|
||||
}
|
||||
|
||||
export interface NavNodeValue {
|
||||
node: NavNodeInfo;
|
||||
parentId: string;
|
||||
}
|
||||
|
||||
export interface INodeContainerInfo {
|
||||
connectionId?: string;
|
||||
catalogId?: string;
|
||||
schemaId?: string;
|
||||
}
|
||||
|
||||
export interface INavNodePath {
|
||||
nodes: NavNodeKey[];
|
||||
remove?: boolean;
|
||||
navNodeId?: never;
|
||||
nodesValue?: never;
|
||||
}
|
||||
|
||||
export interface INavNodeData {
|
||||
nodesValue: NavNodeValue[];
|
||||
navNodeId?: never;
|
||||
nodes?: never;
|
||||
remove?: never;
|
||||
}
|
||||
|
||||
export interface INavNodeId {
|
||||
navNodeId: string[];
|
||||
remove?: boolean;
|
||||
nodes?: never;
|
||||
nodesValue?: never;
|
||||
}
|
||||
|
||||
export interface INodeNavigationContext {
|
||||
type: NavigationType;
|
||||
nodeId: string;
|
||||
parentId: string;
|
||||
folderId: string;
|
||||
name?: string;
|
||||
icon?: string;
|
||||
getParents: () => string[];
|
||||
loadParents: (parents: string[]) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface INodeNavigationData {
|
||||
type: NavigationType;
|
||||
nodeId: string;
|
||||
parentId: string;
|
||||
folderId?: string;
|
||||
}
|
||||
|
||||
interface INavNodeMetadata {
|
||||
loaded: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
interface INavTreeMetadata {
|
||||
loaded: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const ROOT_NODE_PATH = '/';
|
||||
|
||||
@injectable()
|
||||
export class NavNodeManagerService {
|
||||
readonly navNode = new CachedResource(
|
||||
new Map(),
|
||||
this.loadNavNodeInfo.bind(this),
|
||||
(_, metadata, data) => {
|
||||
if (data.nodesValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodesId = data.navNodeId
|
||||
? data.navNodeId
|
||||
: data.nodes.map(node => node.nodeId);
|
||||
|
||||
return nodesId.every(navNodeId => metadata.get(navNodeId).loaded);
|
||||
},
|
||||
new MetadataMap<string, INavNodeMetadata>(() => ({ loaded: false, loading: false })),
|
||||
(_, metadata, data) => {
|
||||
if (data.nodesValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodesId = data.navNodeId
|
||||
? data.navNodeId
|
||||
: data.nodes.map(node => node.nodeId);
|
||||
|
||||
return nodesId.some(navNodeId => metadata.get(navNodeId).loading);
|
||||
}
|
||||
);
|
||||
readonly navTree = new CachedResource(
|
||||
new Map(),
|
||||
this.loadNavTree.bind(this),
|
||||
(_, metadata, parentId) => metadata.get(parentId).loaded,
|
||||
new MetadataMap<string, INavTreeMetadata>(() => ({ loaded: false, loading: false })),
|
||||
(_, metadata, parentId) => metadata.get(parentId).loading
|
||||
)
|
||||
readonly navigator!: INavigator<INodeNavigationData>;
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private navigationService: NavigationService,
|
||||
private permissionsService: PermissionsService,
|
||||
private dbObjectService: DBObjectService,
|
||||
) {
|
||||
|
||||
this.navigator = this.navigationService.createNavigator<INodeNavigationData>(
|
||||
data => data.nodeId,
|
||||
this.navigateHandler.bind(this),
|
||||
{
|
||||
type: NavigationType.open,
|
||||
nodeId: ROOT_NODE_PATH,
|
||||
parentId: ROOT_NODE_PATH,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async navToNode(nodeId: string, parentId: string, folderId?: string) {
|
||||
await this.navigator.navigateTo({
|
||||
type: NavigationType.open,
|
||||
nodeId,
|
||||
parentId,
|
||||
folderId,
|
||||
});
|
||||
}
|
||||
|
||||
async refresh(navNodeId: string) {
|
||||
await this.graphQLService.gql.navRefreshNode({
|
||||
nodePath: navNodeId,
|
||||
});
|
||||
await this.navTree.refresh(true, navNodeId, false);
|
||||
}
|
||||
|
||||
async updateRootChildren() {
|
||||
if (!await this.permissionsService.hasAsync(EPermission.public)) {
|
||||
return;
|
||||
}
|
||||
await this.navTree.refresh(true, ROOT_NODE_PATH, false);
|
||||
}
|
||||
|
||||
getTree(navNodeId: string): string[] | undefined
|
||||
getTree(navNodeKey: NavNodeKey): string[] | undefined
|
||||
getTree(navNodeKey: NavNodeKey[]): (string[] | undefined)[]
|
||||
getTree(navNodeId: string | NavNodeKey | NavNodeKey[]) {
|
||||
if (typeof navNodeId === 'string') {
|
||||
return this.navTree.data.get(navNodeId);
|
||||
}
|
||||
|
||||
if (Array.isArray(navNodeId)) {
|
||||
return navNodeId.map(node => this.navTree.data.get(node.nodeId));
|
||||
}
|
||||
|
||||
return this.navTree.data.get(navNodeId.nodeId);
|
||||
}
|
||||
|
||||
async loadTree(navNodeId: string) {
|
||||
await this.navTree.load(navNodeId, false);
|
||||
return this.getTree(navNodeId)!;
|
||||
}
|
||||
|
||||
getNode(navNodeId: string): NavNode | undefined
|
||||
getNode(navNodeKey: NavNodeKey): NavNode | undefined
|
||||
getNode(navNodeKey: NavNodeKey[]): (NavNode | undefined)[]
|
||||
getNode(navNodeId: string | NavNodeKey | NavNodeKey[]) {
|
||||
if (typeof navNodeId === 'string') {
|
||||
return this.navNode.data.get(navNodeId);
|
||||
}
|
||||
|
||||
if (Array.isArray(navNodeId)) {
|
||||
return navNodeId.map(node => this.navNode.data.get(node.nodeId));
|
||||
}
|
||||
|
||||
return this.navNode.data.get(navNodeId.nodeId);
|
||||
}
|
||||
|
||||
getNestedChildren(navNode: string | string[]) {
|
||||
const nestedChildren: string[] = [];
|
||||
let prevChildren: string[];
|
||||
if (Array.isArray(navNode)) {
|
||||
prevChildren = navNode.concat();
|
||||
nestedChildren.push(...navNode);
|
||||
} else {
|
||||
prevChildren = (this.getTree(navNode)?.concat() || []);
|
||||
nestedChildren.push(...prevChildren);
|
||||
}
|
||||
|
||||
while (prevChildren.length) {
|
||||
const nodeKey = prevChildren.shift()!;
|
||||
const children = this.getTree(nodeKey) || [];
|
||||
prevChildren.push(...children);
|
||||
nestedChildren.push(...children);
|
||||
}
|
||||
|
||||
return nestedChildren;
|
||||
}
|
||||
|
||||
async loadNode(node: NavNodeKey): Promise<NavNode>
|
||||
async loadNode(...nodes: NavNodeKey[]): Promise<NavNode>
|
||||
async loadNode(...nodes: NavNodeKey[]) {
|
||||
await this.navNode.load({ nodes });
|
||||
|
||||
if (nodes.length === 1) {
|
||||
return this.getNode(nodes[0])!;
|
||||
}
|
||||
|
||||
return this.getNode(nodes);
|
||||
}
|
||||
|
||||
getParent(node: NavNode) {
|
||||
return this.navNode.data.get(node.parentId);
|
||||
}
|
||||
|
||||
isNodeHasData(node?: string | NavNode) {
|
||||
if (typeof node === 'string') {
|
||||
node = this.getNode(node);
|
||||
}
|
||||
|
||||
if (!node || !node.objectFeatures) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return node.objectFeatures.includes(ENodeFeature.dataContainer)
|
||||
|| node.objectFeatures.includes(ENodeFeature.container);
|
||||
}
|
||||
|
||||
getNodeContainerInfo(nodeId: string): INodeContainerInfo {
|
||||
const initial: INodeContainerInfo = {};
|
||||
|
||||
const scanParents = (res: INodeContainerInfo,
|
||||
nodeId?: string): INodeContainerInfo => {
|
||||
if (!nodeId) {
|
||||
return res;
|
||||
}
|
||||
const object = this.getNode(nodeId);
|
||||
if (!object) {
|
||||
return res;
|
||||
}
|
||||
if (object.objectFeatures.includes(EObjectFeature.dataSource)) {
|
||||
res.connectionId = object.id;
|
||||
}
|
||||
if (object.objectFeatures.includes(EObjectFeature.catalog)) {
|
||||
res.catalogId = object.name; // note that catalogId is node name
|
||||
}
|
||||
if (object.objectFeatures.includes(EObjectFeature.schema)) {
|
||||
res.schemaId = object.name; // note that schemaId is node name
|
||||
}
|
||||
return scanParents(res, object.parentId);
|
||||
};
|
||||
|
||||
return scanParents(initial, nodeId);
|
||||
}
|
||||
|
||||
async remove(path = ROOT_NODE_PATH) {
|
||||
await this.navTree.refresh(true, path, true);
|
||||
}
|
||||
|
||||
navigationNavNodeContext = async (
|
||||
contexts: IContextProvider<INodeNavigationData>,
|
||||
data: INodeNavigationData
|
||||
): Promise<INodeNavigationContext> => {
|
||||
let nodeId = data.nodeId;
|
||||
let parentId = data.parentId;
|
||||
let folderId = '';
|
||||
let name: string | undefined;
|
||||
let icon: string | undefined;
|
||||
|
||||
if (NodeManagerUtils.isDatabaseObject(nodeId) && data.type !== NavigationType.closeConnection) {
|
||||
const node = await this.loadNode({ nodeId, parentId });
|
||||
|
||||
name = node.name;
|
||||
icon = node.icon;
|
||||
|
||||
if (node.folder) {
|
||||
const parent = this.getNode(node.parentId);
|
||||
folderId = nodeId;
|
||||
if (parent && !parent.folder) {
|
||||
nodeId = parent.id;
|
||||
parentId = parent.parentId;
|
||||
name = parent.name;
|
||||
icon = parent.icon;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.folderId) {
|
||||
folderId = data.folderId;
|
||||
}
|
||||
}
|
||||
|
||||
const getParents = () => {
|
||||
const parents: string[] = [];
|
||||
let parent = this.getNode(nodeId);
|
||||
|
||||
while (parent && parent.parentId !== ROOT_NODE_PATH) {
|
||||
parents.unshift(parent.parentId);
|
||||
parent = this.getNode(parent.parentId);
|
||||
}
|
||||
|
||||
return parents;
|
||||
};
|
||||
|
||||
const loadParents = async (parents: string[]) => {
|
||||
let parentId = ROOT_NODE_PATH;
|
||||
|
||||
await this.navNode.load({
|
||||
nodes: parents.map((nodeId) => {
|
||||
try {
|
||||
return { nodeId, parentId };
|
||||
} finally {
|
||||
parentId = nodeId;
|
||||
}
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
type: data.type,
|
||||
nodeId,
|
||||
parentId,
|
||||
folderId,
|
||||
name,
|
||||
icon,
|
||||
getParents,
|
||||
loadParents,
|
||||
};
|
||||
}
|
||||
|
||||
private async navigateHandler(contexts: IContextProvider<INodeNavigationData>) {
|
||||
}
|
||||
|
||||
private async loadNavNodeInfo(
|
||||
navNode: Map<string, NavNode>,
|
||||
metadata: MetadataMap<string, INavNodeMetadata>,
|
||||
load: boolean,
|
||||
data: INavNodePath | INavNodeData | INavNodeId,
|
||||
) {
|
||||
if (data.nodesValue) {
|
||||
for (const nodeValue of data.nodesValue) {
|
||||
const itemMetadata = metadata.get(nodeValue.node.id);
|
||||
navNode.set(nodeValue.node.id, {
|
||||
...nodeValue.node,
|
||||
objectFeatures: nodeValue.node.object?.features || [],
|
||||
parentId: nodeValue.parentId,
|
||||
});
|
||||
itemMetadata.loaded = true;
|
||||
}
|
||||
return navNode;
|
||||
}
|
||||
|
||||
const nodesId = data.navNodeId
|
||||
? data.navNodeId
|
||||
: data.nodes.map(node => node.nodeId);
|
||||
|
||||
if (data.remove) {
|
||||
for (const navNodeId of nodesId) {
|
||||
navNode.delete(navNodeId);
|
||||
metadata.delete(navNodeId);
|
||||
}
|
||||
|
||||
await this.dbObjectService.remove(nodesId);
|
||||
return navNode;
|
||||
}
|
||||
|
||||
for (const navNodeId of nodesId) {
|
||||
const itemMetadata = metadata.get(navNodeId);
|
||||
|
||||
try {
|
||||
itemMetadata.loaded = false;
|
||||
if (load) {
|
||||
itemMetadata.loading = true;
|
||||
const { navNodeInfo } = await this.graphQLService.gql.navNodeInfo({
|
||||
nodePath: navNodeId,
|
||||
});
|
||||
|
||||
let parentId = ROOT_NODE_PATH;
|
||||
if (data.navNodeId) {
|
||||
parentId = navNode.get(navNodeId)?.parentId || parentId;
|
||||
} else {
|
||||
parentId = data.nodes.find(node => node.nodeId === navNodeId)?.parentId || parentId;
|
||||
}
|
||||
|
||||
navNode.set(navNodeId, {
|
||||
...navNodeInfo,
|
||||
objectFeatures: navNodeInfo.object?.features || [],
|
||||
parentId,
|
||||
});
|
||||
itemMetadata.loaded = true;
|
||||
}
|
||||
} finally {
|
||||
itemMetadata.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
await this.dbObjectService.dbObject.refresh(false, { navNodeId: nodesId });
|
||||
|
||||
return navNode;
|
||||
}
|
||||
|
||||
private async loadNavTree(
|
||||
navTree: Map<string, string[]>,
|
||||
metadata: MetadataMap<string, INavTreeMetadata>,
|
||||
load: boolean,
|
||||
parentId: string,
|
||||
remove: boolean
|
||||
) {
|
||||
const itemMetadata = metadata.get(parentId);
|
||||
let childrenToRemove = navTree.get(parentId)?.concat() || [];
|
||||
|
||||
if (remove) {
|
||||
childrenToRemove = [parentId];
|
||||
} else {
|
||||
try {
|
||||
itemMetadata.loaded = false;
|
||||
if (load) {
|
||||
itemMetadata.loading = true;
|
||||
|
||||
const { navNodeChildren } = await this.graphQLService.gql.navNodeChildren({
|
||||
parentPath: parentId,
|
||||
});
|
||||
|
||||
await this.navNode.refresh(true, {
|
||||
nodesValue: navNodeChildren.map(node => ({ node, parentId })),
|
||||
});
|
||||
navTree.set(parentId, navNodeChildren.map(node => node.id));
|
||||
itemMetadata.loaded = true;
|
||||
}
|
||||
} finally {
|
||||
itemMetadata.loading = false;
|
||||
}
|
||||
|
||||
const newChildren = navTree.get(parentId);
|
||||
const nestedChildren = this.getNestedChildren(parentId);
|
||||
|
||||
await this.navNode.refresh(false, {
|
||||
navNodeId: nestedChildren.filter(navNodeId => !newChildren || !newChildren.includes(navNodeId)),
|
||||
});
|
||||
|
||||
for (const navNodeId of nestedChildren) {
|
||||
const itemMetadata = metadata.get(navNodeId);
|
||||
itemMetadata.loaded = false;
|
||||
}
|
||||
|
||||
if (newChildren) {
|
||||
childrenToRemove = childrenToRemove.filter(navNodeId => !newChildren.includes(navNodeId));
|
||||
}
|
||||
}
|
||||
|
||||
const nestedChildren = this.getNestedChildren(childrenToRemove);
|
||||
await this.navNode.refresh(true, { navNodeId: nestedChildren, remove: true });
|
||||
for (const navNodeId of nestedChildren) {
|
||||
navTree.delete(navNodeId);
|
||||
metadata.delete(navNodeId);
|
||||
}
|
||||
|
||||
return navTree;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { DatabaseObjectInfo, NavigatorNodeInfo } from '@dbeaver/core/sdk';
|
||||
|
||||
// Node Info without ObjectProperties
|
||||
export type NavigatorNodeInfoLight = Omit<NavigatorNodeInfo, 'object'> & {
|
||||
object?: {
|
||||
features?: string[]; // EObjectFeature
|
||||
};
|
||||
}
|
||||
|
||||
export type NodeWithParent = NavigatorNodeInfoLight & {
|
||||
parentId: string;
|
||||
}
|
||||
|
||||
export type DatabaseObjectInfoWithId = DatabaseObjectInfo & {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type NodeChildren = {
|
||||
children: string[];
|
||||
isLoaded: boolean;
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { PermissionsService, EPermission } from '@dbeaver/core/root';
|
||||
import { DatabaseObjectInfo, GraphQLService } from '@dbeaver/core/sdk';
|
||||
|
||||
import { INavigator } from '../Navigation/INavigator';
|
||||
import { IContextProvider } from '../Navigation/NavigationContext';
|
||||
import { NavigationService } from '../Navigation/NavigationService';
|
||||
import { ENodeFeature } from './ENodeFeature';
|
||||
import { EObjectFeature } from './EObjectFeature';
|
||||
import { NodesStore } from './NodesStore';
|
||||
import { DatabaseObjectInfoWithId, NodeChildren, NodeWithParent } from './NodeWithParent';
|
||||
|
||||
export enum NavigationType {
|
||||
open,
|
||||
closeConnection
|
||||
}
|
||||
|
||||
export interface INodeContainerInfo {
|
||||
connectionId?: string;
|
||||
catalogId?: string;
|
||||
schemaId?: string;
|
||||
}
|
||||
|
||||
export interface INodeNavigationContext {
|
||||
type: NavigationType;
|
||||
nodeId: string;
|
||||
folderId: string;
|
||||
name?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface INodeNavigationData {
|
||||
type: NavigationType;
|
||||
nodeId: string;
|
||||
folderId?: string;
|
||||
}
|
||||
|
||||
const ROOT_NODE_PATH = '/';
|
||||
|
||||
// TODO: should be renamed to DBObjectManagerService
|
||||
@injectable()
|
||||
export class NodesManagerService {
|
||||
readonly navigator!: INavigator<INodeNavigationData>;
|
||||
private nodesStore = new NodesStore();
|
||||
|
||||
constructor(private graphQLService: GraphQLService,
|
||||
private navigationService: NavigationService,
|
||||
private permissionsService: PermissionsService) {
|
||||
|
||||
this.navigator = this.navigationService.createNavigator<INodeNavigationData>(
|
||||
data => data.nodeId,
|
||||
this.navigateHandler.bind(this),
|
||||
{
|
||||
type: NavigationType.open,
|
||||
nodeId: ROOT_NODE_PATH,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async closeConnection(nodeId: string) {
|
||||
await this.navigator.navigateTo({
|
||||
type: NavigationType.closeConnection,
|
||||
nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
async navToNode(nodeId: string, folderId?: string) {
|
||||
await this.navigator.navigateTo({
|
||||
type: NavigationType.open,
|
||||
nodeId,
|
||||
folderId,
|
||||
});
|
||||
}
|
||||
|
||||
async refreshNode(nodeId: string) {
|
||||
await this.purgeServerCache(nodeId);
|
||||
this.nodesStore.removeNode(nodeId);
|
||||
await this.updateNodeInfo(nodeId);
|
||||
}
|
||||
|
||||
getDatabaseObjectInfo(nodeId: string): DatabaseObjectInfoWithId | undefined {
|
||||
return this.nodesStore.getDatabaseObjectInfo(nodeId);
|
||||
}
|
||||
|
||||
getNode(nodeId: string): NodeWithParent | undefined {
|
||||
return this.nodesStore.getNode(nodeId);
|
||||
}
|
||||
|
||||
getChildren(nodeId: string): NodeChildren | undefined {
|
||||
return this.nodesStore.getChildren(nodeId);
|
||||
}
|
||||
|
||||
async updateNodeInfo(nodeId: string) {
|
||||
const { navNodeInfo } = await this.graphQLService.gql.navNodeInfo({
|
||||
nodePath: nodeId,
|
||||
});
|
||||
|
||||
let parentId = nodeId.match(/^(.*)\/.*?$/)![1];
|
||||
if (parentId === 'database:/') {
|
||||
parentId = ROOT_NODE_PATH;
|
||||
}
|
||||
|
||||
const newNode = { ...navNodeInfo, parentId };
|
||||
this.nodesStore.updateNodeInfo(newNode);
|
||||
return newNode;
|
||||
}
|
||||
|
||||
async updateDatabaseObjectInfo(nodeId: string) {
|
||||
const node = this.nodesStore.getNode(nodeId);
|
||||
if (!node) {
|
||||
await this.updateNodeInfo(nodeId);
|
||||
}
|
||||
|
||||
const { objectInfo } = await this.graphQLService.gql.queryDatabaseObjectInfo({
|
||||
nodeId,
|
||||
// filter: { features: ['viewable', 'editPossible'] },
|
||||
});
|
||||
|
||||
const info = { ...objectInfo.object, id: objectInfo.id };
|
||||
this.nodesStore.updateDatabaseObjectInfo(info);
|
||||
return info;
|
||||
}
|
||||
|
||||
async purgeServerCache(nodeId: string) {
|
||||
await this.graphQLService.gql.navRefreshNode({
|
||||
nodePath: nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
async updateChildren(parentId: string) {
|
||||
const { navNodeChildren } = await this.graphQLService.gql.navNodeChildren({
|
||||
parentPath: parentId,
|
||||
});
|
||||
const children = navNodeChildren.map(child => child.id);
|
||||
this.nodesStore.updateChildren(parentId, children);
|
||||
for (const child of navNodeChildren) {
|
||||
this.nodesStore.updateNodeInfo({ ...child, parentId });
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
async updateRootChildren() {
|
||||
if (!await this.permissionsService.hasAsync(EPermission.public)) {
|
||||
return;
|
||||
}
|
||||
this.updateChildren(ROOT_NODE_PATH);
|
||||
}
|
||||
|
||||
async updateChildrenDatabaseObjectInfo(nodeId: string) {
|
||||
const {
|
||||
childrenDatabaseObjectInfo,
|
||||
} = await this.graphQLService.gql.queryChildrenDatabaseObjectInfo({
|
||||
nodePath: nodeId,
|
||||
// filter: { features: ['viewable', 'editPossible'] }, // filter properties for view
|
||||
});
|
||||
|
||||
const children = childrenDatabaseObjectInfo.map(children => ({
|
||||
...children.object,
|
||||
id: children.id,
|
||||
}));
|
||||
this.nodesStore.updateChildrenDatabaseObjectInfo(children);
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
async loadNodeInfo(nodeId: string): Promise<NodeWithParent> {
|
||||
let node = this.nodesStore.getNode(nodeId);
|
||||
if (node) {
|
||||
if (node.parentId !== ROOT_NODE_PATH) {
|
||||
await this.loadNodeInfo(node.parentId);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
node = await this.updateNodeInfo(nodeId);
|
||||
|
||||
if (node.parentId !== ROOT_NODE_PATH) {
|
||||
await this.loadNodeInfo(node.parentId);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
async loadChildren(parentId = ROOT_NODE_PATH): Promise<string[]> {
|
||||
const nodeChildren = this.nodesStore.getChildren(parentId);
|
||||
if (nodeChildren?.isLoaded) {
|
||||
return nodeChildren.children;
|
||||
}
|
||||
|
||||
return this.updateChildren(parentId);
|
||||
}
|
||||
|
||||
async loadDatabaseObjectInfo(
|
||||
nodeId: string
|
||||
): Promise<DatabaseObjectInfo> {
|
||||
const databaseObjectInfo = this.nodesStore.getDatabaseObjectInfo(nodeId);
|
||||
if (databaseObjectInfo) {
|
||||
return databaseObjectInfo;
|
||||
}
|
||||
|
||||
return this.updateDatabaseObjectInfo(nodeId);
|
||||
}
|
||||
|
||||
async loadChildrenDatabaseObjectInfo(parentId: string): Promise<DatabaseObjectInfoWithId[]> {
|
||||
const children = await this.loadChildren(parentId);
|
||||
const childrenDatabaseObjectInfo: DatabaseObjectInfoWithId[] = [];
|
||||
for (const child of children) {
|
||||
const databaseObjectInfo = this.nodesStore.getDatabaseObjectInfo(child);
|
||||
if (!databaseObjectInfo) {
|
||||
return this.updateChildrenDatabaseObjectInfo(parentId);
|
||||
}
|
||||
childrenDatabaseObjectInfo.push(databaseObjectInfo);
|
||||
}
|
||||
|
||||
return childrenDatabaseObjectInfo;
|
||||
}
|
||||
|
||||
getParent(node: NodeWithParent) {
|
||||
return this.nodesStore.getNode(node.parentId);
|
||||
}
|
||||
|
||||
getNodeContainerInfo(nodeId: string): INodeContainerInfo {
|
||||
const initial: INodeContainerInfo = {};
|
||||
|
||||
const scanParents = (res: INodeContainerInfo,
|
||||
nodeId?: string): INodeContainerInfo => {
|
||||
if (!nodeId) {
|
||||
return res;
|
||||
}
|
||||
const node = this.getNode(nodeId);
|
||||
if (!node) {
|
||||
return res;
|
||||
}
|
||||
if (node?.object?.features?.includes(EObjectFeature.dataSource)) {
|
||||
res.connectionId = node.id;
|
||||
}
|
||||
if (node?.object?.features?.includes(EObjectFeature.catalog)) {
|
||||
res.catalogId = node.name; // note that catalogId is node name
|
||||
}
|
||||
if (node?.object?.features?.includes(EObjectFeature.schema)) {
|
||||
res.schemaId = node.name; // note that schemaId is node name
|
||||
}
|
||||
return scanParents(res, node.parentId);
|
||||
};
|
||||
|
||||
return scanParents(initial, nodeId);
|
||||
}
|
||||
|
||||
removeNodes(path = ROOT_NODE_PATH) {
|
||||
this.nodesStore.removeNode(path);
|
||||
}
|
||||
|
||||
navigationNodeContext = async (
|
||||
contexts: IContextProvider<INodeNavigationData>,
|
||||
data: INodeNavigationData
|
||||
): Promise<INodeNavigationContext> => {
|
||||
let nodeId = data.nodeId;
|
||||
let folderId = '';
|
||||
let name: string | undefined;
|
||||
let icon: string | undefined;
|
||||
|
||||
if (isDatabaseObject(nodeId) && data.type !== NavigationType.closeConnection) {
|
||||
const node = await this.loadNodeInfo(nodeId);
|
||||
name = node.name;
|
||||
icon = node.icon;
|
||||
|
||||
if (node.folder) {
|
||||
const parent = await this.loadNodeInfo(node.parentId);
|
||||
folderId = nodeId;
|
||||
if (parent && !parent.folder) {
|
||||
nodeId = parent.id;
|
||||
name = parent.name;
|
||||
icon = parent.icon;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.folderId) {
|
||||
folderId = data.folderId;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: data.type,
|
||||
nodeId,
|
||||
folderId,
|
||||
name,
|
||||
icon,
|
||||
};
|
||||
}
|
||||
|
||||
async navigateHandler(contexts: IContextProvider<INodeNavigationData>) {
|
||||
}
|
||||
|
||||
isNodeHasData(info?: string | DatabaseObjectInfo) {
|
||||
if (typeof info === 'string') {
|
||||
info = this.nodesStore.getDatabaseObjectInfo(info);
|
||||
}
|
||||
|
||||
if (!info || !info.features) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return info.features.includes(ENodeFeature.dataContainer)
|
||||
|| info.features.includes(ENodeFeature.container);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
import {
|
||||
DatabaseObjectInfoWithId,
|
||||
NodeChildren,
|
||||
NodeWithParent,
|
||||
} from './NodeWithParent';
|
||||
|
||||
|
||||
export class NodesStore {
|
||||
@observable
|
||||
private nodesMap: Map<string, NodeWithParent> = new Map()
|
||||
@observable
|
||||
private databaseObjectInfoMap: Map<string, DatabaseObjectInfoWithId> = new Map()
|
||||
@observable
|
||||
private nodeChildrenMap: Map<string, NodeChildren> = new Map()
|
||||
|
||||
getNode(nodeId: string): NodeWithParent | undefined {
|
||||
return this.nodesMap.get(nodeId);
|
||||
}
|
||||
|
||||
getChildren(nodeId: string): NodeChildren | undefined {
|
||||
return this.nodeChildrenMap.get(nodeId);
|
||||
}
|
||||
|
||||
getDatabaseObjectInfo(nodeId: string): DatabaseObjectInfoWithId | undefined {
|
||||
return this.databaseObjectInfoMap.get(nodeId);
|
||||
}
|
||||
|
||||
@action
|
||||
updateNodeInfo(node: NodeWithParent) {
|
||||
this.nodesMap.set(node.id, node);
|
||||
|
||||
const parent = this.nodeChildrenMap.get(node.parentId);
|
||||
|
||||
if (!parent) {
|
||||
this.nodeChildrenMap.set(node.parentId, {
|
||||
children: [node.id],
|
||||
isLoaded: false,
|
||||
});
|
||||
} else if (!parent.children.includes(node.id)) {
|
||||
parent.children.push(node.id);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
updateDatabaseObjectInfo(databaseObjectInfo: DatabaseObjectInfoWithId) {
|
||||
this.databaseObjectInfoMap.set(databaseObjectInfo.id, databaseObjectInfo);
|
||||
}
|
||||
|
||||
@action
|
||||
updateChildren(parentId: string, children: string[]) {
|
||||
this.nodeChildrenMap.set(parentId, {
|
||||
children,
|
||||
isLoaded: true,
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
updateChildrenDatabaseObjectInfo(children: DatabaseObjectInfoWithId[]) {
|
||||
for (const child of children) {
|
||||
this.databaseObjectInfoMap.set(child.id, child);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
removeNode(nodeId: string) {
|
||||
const node = this.nodesMap.get(nodeId);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
const parent = this.nodeChildrenMap.get(node.id);
|
||||
if (parent) {
|
||||
for (const children of parent.children) {
|
||||
this.removeNode(children);
|
||||
}
|
||||
this.nodeChildrenMap.delete(node.id);
|
||||
}
|
||||
this.databaseObjectInfoMap.delete(nodeId);
|
||||
this.nodesMap.delete(nodeId);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
query queryChildrenDatabaseObjectInfo(
|
||||
$nodePath: ID!
|
||||
query getChildrenDBObjectInfo(
|
||||
$navNodeId: ID!
|
||||
$filter: ObjectPropertyFilter
|
||||
) {
|
||||
childrenDatabaseObjectInfo: navNodeChildren(parentPath: $nodePath) {
|
||||
dbObjects: navNodeChildren(parentPath: $navNodeId) {
|
||||
id
|
||||
object {
|
||||
features
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
query queryDatabaseObjectInfo($nodeId: ID!, $filter: ObjectPropertyFilter) {
|
||||
objectInfo: navNodeInfo(nodePath: $nodeId) {
|
||||
id
|
||||
query getDBObjectInfo($navNodeId: ID!, $filter: ObjectPropertyFilter) {
|
||||
objectInfo: navNodeInfo(nodePath: $navNodeId) {
|
||||
object {
|
||||
features
|
||||
properties(filter: $filter) {
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
|
||||
import { NodesManagerService } from './NodesManagerService';
|
||||
import { DBObjectService } from './DBObjectService';
|
||||
|
||||
export function useDatabaseObjectInfo(nodeId: string) {
|
||||
const nodesManagerService = useService(NodesManagerService);
|
||||
export function useDatabaseObjectInfo(navNodeId: string) {
|
||||
const dbObjectService = useService(DBObjectService);
|
||||
|
||||
return nodesManagerService.getDatabaseObjectInfo(nodeId);
|
||||
return dbObjectService.getDBObject(navNodeId);
|
||||
}
|
||||
|
||||
@@ -8,10 +8,19 @@
|
||||
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
|
||||
import { NodesManagerService } from './NodesManagerService';
|
||||
import { NavNodeManagerService } from './NavNodeManagerService';
|
||||
|
||||
export function useNode(nodeId: string) {
|
||||
const nodesManagerService = useService(NodesManagerService);
|
||||
export function useNode(navNodeId: string) {
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const node = navNodeManagerService.getNode(navNodeId);
|
||||
const isLoading = navNodeManagerService.navNode.isDataLoading({
|
||||
navNodeId: [navNodeId],
|
||||
});
|
||||
const isLoaded = navNodeManagerService.navNode.isLoaded({
|
||||
navNodeId: [navNodeId],
|
||||
});
|
||||
|
||||
return nodesManagerService.getNode(nodeId);
|
||||
return {
|
||||
node, isLoading, isLoaded,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,10 +8,17 @@
|
||||
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
|
||||
import { NodesManagerService } from './NodesManager/NodesManagerService';
|
||||
import { NavNodeManagerService, ROOT_NODE_PATH } from './NodesManager/NavNodeManagerService';
|
||||
|
||||
export function useChildren(parentId = '/') {
|
||||
const nodesManagerService = useService(NodesManagerService);
|
||||
export function useChildren(navNodeId = ROOT_NODE_PATH) {
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const children = navNodeManagerService.getTree(navNodeId);
|
||||
const isLoading = navNodeManagerService.navTree.isDataLoading(navNodeId, false);
|
||||
const isLoaded = navNodeManagerService.navTree.isLoaded(navNodeId, false);
|
||||
|
||||
return nodesManagerService.getChildren(parentId);
|
||||
return {
|
||||
children,
|
||||
isLoaded,
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import {
|
||||
ConnectionDialogsService, ConnectionsManagerService,
|
||||
MainMenuService, NavigationService, NavigationTreeService, NodesManagerService,
|
||||
MainMenuService, NavigationService, NavigationTreeService, NavNodeManagerService, DBObjectService,
|
||||
ConnectionSchemaManagerService, NavigationTabsService,
|
||||
NavigationTreeContextMenuService,
|
||||
SettingsMenuService, LogViewerService, LogViewerMenuService, TopNavService,
|
||||
@@ -64,7 +64,8 @@ export const coreManifest: PluginManifest = {
|
||||
NavigationTabsService,
|
||||
NavigationTreeContextMenuService,
|
||||
NavigationTreeService,
|
||||
NodesManagerService,
|
||||
NavNodeManagerService,
|
||||
DBObjectService,
|
||||
NotificationService,
|
||||
SessionService,
|
||||
SettingsMenuService,
|
||||
|
||||
@@ -52,7 +52,7 @@ export class PermissionsService {
|
||||
}
|
||||
|
||||
async update() {
|
||||
await this.permissions.refresh();
|
||||
await this.permissions.refresh(true);
|
||||
this.updateSubject.next();
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export class SessionService {
|
||||
}
|
||||
|
||||
async update() {
|
||||
await this.session.refresh();
|
||||
await this.session.refresh(true);
|
||||
}
|
||||
|
||||
private async refreshSessionStateAsync(data: SessionState | undefined): Promise<SessionState> {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
|
||||
export type Loader<TData, TMetadata = {}, TArgs extends any[] = []> = (
|
||||
current: TData,
|
||||
@@ -21,13 +22,23 @@ export type IsLoaded<TData, TMetadata = {}, TArgs extends any[] = []> = (
|
||||
...args: TArgs
|
||||
) => boolean
|
||||
|
||||
export type IsLoading<TData, TMetadata = {}, TArgs extends any[] = []> = (
|
||||
current: TData,
|
||||
metadata: TMetadata,
|
||||
...args: TArgs
|
||||
) => boolean
|
||||
|
||||
export class CachedResource<TData, TMetadata = {}, TArgs extends any[] = []> {
|
||||
@observable data: TData;
|
||||
|
||||
@observable private loading = false;
|
||||
@observable private metadata: TMetadata;
|
||||
|
||||
readonly onDataUpdate: Observable<TData>;
|
||||
private dataSubject: Subject<TData>;
|
||||
private loader: Loader<TData, TMetadata, TArgs>;
|
||||
private isLoadedCheck: IsLoaded<TData, TMetadata, TArgs>;
|
||||
private isLoadingCheck?: IsLoading<TData, TMetadata, TArgs>;
|
||||
private activePromise: Promise<TData> | null = null;
|
||||
|
||||
constructor(
|
||||
@@ -35,11 +46,15 @@ export class CachedResource<TData, TMetadata = {}, TArgs extends any[] = []> {
|
||||
loader: Loader<TData, TMetadata, TArgs>,
|
||||
isLoadedCheck: IsLoaded<TData, TMetadata, TArgs>,
|
||||
metadata?: TMetadata,
|
||||
isLoadingCheck?: IsLoading<TData, TMetadata, TArgs>,
|
||||
) {
|
||||
this.data = defaultValue;
|
||||
this.loader = loader;
|
||||
this.isLoadedCheck = isLoadedCheck;
|
||||
this.isLoadingCheck = isLoadingCheck;
|
||||
this.metadata = metadata || {} as TMetadata;
|
||||
this.dataSubject = new Subject<TData>();
|
||||
this.onDataUpdate = this.dataSubject.asObservable();
|
||||
}
|
||||
|
||||
isLoaded(...args: TArgs): boolean {
|
||||
@@ -49,36 +64,56 @@ export class CachedResource<TData, TMetadata = {}, TArgs extends any[] = []> {
|
||||
isLoading(): boolean {
|
||||
return this.loading;
|
||||
}
|
||||
isDataLoading(...args: TArgs): boolean {
|
||||
if (this.isLoadingCheck) {
|
||||
return this.loading && this.isLoadingCheck(this.data, this.metadata, ...args);
|
||||
}
|
||||
return this.loading;
|
||||
}
|
||||
|
||||
async refresh(...args: TArgs): Promise<TData> {
|
||||
return this.loadAll(true, args);
|
||||
async refresh(load = false, ...args: TArgs): Promise<TData> {
|
||||
return this.loadData(load, true, args);
|
||||
}
|
||||
|
||||
async load(...args: TArgs): Promise<TData> {
|
||||
return this.loadAll(false, args);
|
||||
return this.loadData(true, false, args);
|
||||
}
|
||||
|
||||
private async loadAll(update: boolean, args: TArgs) {
|
||||
async refreshUnblocked(load = false, ...args: TArgs): Promise<TData> {
|
||||
return this.loadData(load, true, args, true);
|
||||
}
|
||||
|
||||
async loadUnblocked(...args: TArgs): Promise<TData> {
|
||||
return this.loadData(true, false, args, true);
|
||||
}
|
||||
|
||||
private async loadData(load: boolean, update: boolean, args: TArgs, unblocked?: boolean) {
|
||||
if (unblocked) {
|
||||
return this.loadingTask(load, update, args);
|
||||
}
|
||||
await this.waitActive();
|
||||
this.activePromise = this.loadingTask(update, args);
|
||||
this.activePromise = this.loadingTask(load, update, args);
|
||||
try {
|
||||
return await this.activePromise;
|
||||
} finally {
|
||||
this.activePromise = null;
|
||||
this.dataSubject.next(this.data);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadingTask(update: boolean, args: TArgs): Promise<TData> {
|
||||
private async loadingTask(load: boolean, update: boolean, args: TArgs): Promise<TData> {
|
||||
const prevState = this.loading;
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
if (this.isLoaded(...args) && !update) {
|
||||
// don't load existed data & don't refresh doesn't loaded data
|
||||
if ((this.isLoaded(...args) && !update) || (!this.isLoaded(...args) && update && !load)) {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
this.data = await this.loader(this.data, this.metadata, update, ...args);
|
||||
this.data = await this.loader(this.data, this.metadata, load, ...args);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.loading = prevState;
|
||||
}
|
||||
|
||||
return this.data;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable max-len */
|
||||
import { GraphQLClient } from 'graphql-request';
|
||||
|
||||
export type Maybe<T> = T;
|
||||
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
@@ -686,7 +687,7 @@ export type NavGetStructContainersQueryVariables = {
|
||||
};
|
||||
|
||||
|
||||
export type NavGetStructContainersQuery = { navGetStructContainers: { catalogList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>>, schemaList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>> } };
|
||||
export type NavGetStructContainersQuery = { navGetStructContainers: { catalogList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>>; schemaList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>> } };
|
||||
|
||||
export type CloseConnectionMutationVariables = {
|
||||
id: Scalars['ID'];
|
||||
@@ -727,7 +728,7 @@ export type DriverPropertiesQueryVariables = {
|
||||
export type DriverPropertiesQuery = { driver: Array<(
|
||||
Pick<DriverInfo, 'driverParameters'>
|
||||
& { driverProperties?: Maybe<Array<Maybe<Pick<DriverPropertyInfo, 'id' | 'displayName' | 'description' | 'category' | 'dataType' | 'defaultValue' | 'validValues'>>>> }
|
||||
)> };
|
||||
)>; };
|
||||
|
||||
export type GetDriverByIdQueryVariables = {
|
||||
driverId: Scalars['ID'];
|
||||
@@ -750,6 +751,31 @@ export type TestConnectionMutationVariables = {
|
||||
|
||||
export type TestConnectionMutation = { testConnection: Pick<ConnectionInfo, 'id'> };
|
||||
|
||||
export type GetChildrenDbObjectInfoQueryVariables = {
|
||||
navNodeId: Scalars['ID'];
|
||||
filter?: Maybe<ObjectPropertyFilter>;
|
||||
};
|
||||
|
||||
|
||||
export type GetChildrenDbObjectInfoQuery = { dbObjects: Array<(
|
||||
Pick<NavigatorNodeInfo, 'id'>
|
||||
& { object?: Maybe<(
|
||||
Pick<DatabaseObjectInfo, 'features'>
|
||||
& { properties?: Maybe<Array<Maybe<Pick<ObjectPropertyInfo, 'id' | 'category' | 'dataType' | 'description' | 'displayName' | 'features' | 'value'>>>> }
|
||||
)>; }
|
||||
)>; };
|
||||
|
||||
export type GetDbObjectInfoQueryVariables = {
|
||||
navNodeId: Scalars['ID'];
|
||||
filter?: Maybe<ObjectPropertyFilter>;
|
||||
};
|
||||
|
||||
|
||||
export type GetDbObjectInfoQuery = { objectInfo: { object?: Maybe<(
|
||||
Pick<DatabaseObjectInfo, 'features'>
|
||||
& { properties?: Maybe<Array<Maybe<Pick<ObjectPropertyInfo, 'id' | 'category' | 'dataType' | 'description' | 'displayName' | 'features' | 'value'>>>> }
|
||||
)>; }; };
|
||||
|
||||
export type NavNodeChildrenQueryVariables = {
|
||||
parentPath: Scalars['ID'];
|
||||
};
|
||||
@@ -758,7 +784,7 @@ export type NavNodeChildrenQueryVariables = {
|
||||
export type NavNodeChildrenQuery = { navNodeChildren: Array<(
|
||||
Pick<NavigatorNodeInfo, 'id' | 'name' | 'hasChildren' | 'nodeType' | 'icon' | 'folder' | 'inline' | 'navigable' | 'features'>
|
||||
& { object?: Maybe<Pick<DatabaseObjectInfo, 'features'>> }
|
||||
)> };
|
||||
)>; };
|
||||
|
||||
export type NavNodeInfoQueryVariables = {
|
||||
nodePath: Scalars['ID'];
|
||||
@@ -768,7 +794,7 @@ export type NavNodeInfoQueryVariables = {
|
||||
export type NavNodeInfoQuery = { navNodeInfo: (
|
||||
Pick<NavigatorNodeInfo, 'id' | 'name' | 'hasChildren' | 'nodeType' | 'icon' | 'folder' | 'inline' | 'navigable' | 'features'>
|
||||
& { object?: Maybe<Pick<DatabaseObjectInfo, 'features'>> }
|
||||
) };
|
||||
); };
|
||||
|
||||
export type NavRefreshNodeQueryVariables = {
|
||||
nodePath: Scalars['ID'];
|
||||
@@ -777,34 +803,6 @@ export type NavRefreshNodeQueryVariables = {
|
||||
|
||||
export type NavRefreshNodeQuery = Pick<Query, 'navRefreshNode'>;
|
||||
|
||||
export type QueryChildrenDatabaseObjectInfoQueryVariables = {
|
||||
nodePath: Scalars['ID'];
|
||||
filter?: Maybe<ObjectPropertyFilter>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryChildrenDatabaseObjectInfoQuery = { childrenDatabaseObjectInfo: Array<(
|
||||
Pick<NavigatorNodeInfo, 'id'>
|
||||
& { object?: Maybe<(
|
||||
Pick<DatabaseObjectInfo, 'features'>
|
||||
& { properties?: Maybe<Array<Maybe<Pick<ObjectPropertyInfo, 'id' | 'category' | 'dataType' | 'description' | 'displayName' | 'features' | 'value'>>>> }
|
||||
)> }
|
||||
)> };
|
||||
|
||||
export type QueryDatabaseObjectInfoQueryVariables = {
|
||||
nodeId: Scalars['ID'];
|
||||
filter?: Maybe<ObjectPropertyFilter>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryDatabaseObjectInfoQuery = { objectInfo: (
|
||||
Pick<NavigatorNodeInfo, 'id'>
|
||||
& { object?: Maybe<(
|
||||
Pick<DatabaseObjectInfo, 'features'>
|
||||
& { properties?: Maybe<Array<Maybe<Pick<ObjectPropertyInfo, 'id' | 'category' | 'dataType' | 'description' | 'displayName' | 'features' | 'value'>>>> }
|
||||
)> }
|
||||
) };
|
||||
|
||||
export type ReadSessionLogQueryVariables = {
|
||||
maxEntries: Scalars['Int'];
|
||||
clearEntries: Scalars['Boolean'];
|
||||
@@ -891,7 +889,7 @@ export type GetAuthProvidersQueryVariables = {};
|
||||
export type GetAuthProvidersQuery = { providers: Array<(
|
||||
Pick<AuthProviderInfo, 'id' | 'label' | 'icon' | 'description' | 'defaultProvider'>
|
||||
& { credentialParameters: Array<Pick<AuthCredentialInfo, 'id' | 'displayName' | 'description' | 'admin' | 'user' | 'possibleValues' | 'encryption'>> }
|
||||
)> };
|
||||
)>; };
|
||||
|
||||
export type GetSessionUserQueryVariables = {};
|
||||
|
||||
@@ -906,7 +904,7 @@ export type AsyncExportTaskStatusMutationVariables = {
|
||||
export type AsyncExportTaskStatusMutation = { taskInfo: (
|
||||
Pick<AsyncTaskInfo, 'id' | 'name' | 'running' | 'status' | 'taskResult'>
|
||||
& { error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
|
||||
) };
|
||||
); };
|
||||
|
||||
export type ExportDataFromContainerQueryVariables = {
|
||||
connectionId: Scalars['ID'];
|
||||
@@ -918,7 +916,7 @@ export type ExportDataFromContainerQueryVariables = {
|
||||
export type ExportDataFromContainerQuery = { taskInfo: (
|
||||
Pick<AsyncTaskInfo, 'id' | 'running' | 'taskResult'>
|
||||
& { error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
|
||||
) };
|
||||
); };
|
||||
|
||||
export type ExportDataFromResultsQueryVariables = {
|
||||
connectionId: Scalars['ID'];
|
||||
@@ -931,7 +929,7 @@ export type ExportDataFromResultsQueryVariables = {
|
||||
export type ExportDataFromResultsQuery = { taskInfo: (
|
||||
Pick<AsyncTaskInfo, 'id' | 'running' | 'taskResult'>
|
||||
& { error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
|
||||
) };
|
||||
); };
|
||||
|
||||
export type GetDataTransferProcessorsQueryVariables = {};
|
||||
|
||||
@@ -939,7 +937,7 @@ export type GetDataTransferProcessorsQueryVariables = {};
|
||||
export type GetDataTransferProcessorsQuery = { processors: Array<(
|
||||
Pick<DataTransferProcessorInfo, 'id' | 'name' | 'description' | 'fileExtension' | 'appFileExtension' | 'appName' | 'order' | 'icon' | 'isBinary' | 'isHTML'>
|
||||
& { properties?: Maybe<Array<Maybe<Pick<ObjectPropertyInfo, 'id' | 'displayName' | 'description' | 'category' | 'dataType' | 'defaultValue' | 'validValues' | 'features'>>>> }
|
||||
)> };
|
||||
)>; };
|
||||
|
||||
export type RemoveDataTransferFileQueryVariables = {
|
||||
dataFileId: Scalars['String'];
|
||||
@@ -965,10 +963,10 @@ export type AsyncSqlExecuteQueryMutation = { taskInfo: (
|
||||
& { resultSet?: Maybe<(
|
||||
Pick<SqlResultSet, 'id' | 'rows'>
|
||||
& { columns?: Maybe<Array<Maybe<Pick<SqlResultColumn, 'dataKind' | 'entityName' | 'fullTypeName' | 'icon' | 'label' | 'maxLength' | 'name' | 'position' | 'precision' | 'scale' | 'typeName'>>>> }
|
||||
)> }
|
||||
)> }
|
||||
)>, error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
|
||||
) };
|
||||
)>; }
|
||||
)>; }
|
||||
)>; error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>>; }
|
||||
); };
|
||||
|
||||
export type AsyncTaskCancelMutationVariables = {
|
||||
taskId: Scalars['String'];
|
||||
@@ -991,10 +989,10 @@ export type AsyncTaskStatusMutation = { taskInfo: (
|
||||
& { resultSet?: Maybe<(
|
||||
Pick<SqlResultSet, 'id' | 'rows'>
|
||||
& { columns?: Maybe<Array<Maybe<Pick<SqlResultColumn, 'dataKind' | 'entityName' | 'fullTypeName' | 'icon' | 'label' | 'maxLength' | 'name' | 'position' | 'precision' | 'scale' | 'typeName'>>>> }
|
||||
)> }
|
||||
)> }
|
||||
)>, error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
|
||||
) };
|
||||
)>; }
|
||||
)>; }
|
||||
)>; error?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>>; }
|
||||
); };
|
||||
|
||||
export type ExecuteSqlQueryMutationVariables = {
|
||||
connectionId: Scalars['ID'];
|
||||
@@ -1011,9 +1009,9 @@ export type ExecuteSqlQueryMutation = { result?: Maybe<(
|
||||
& { resultSet?: Maybe<(
|
||||
Pick<SqlResultSet, 'id' | 'rows'>
|
||||
& { columns?: Maybe<Array<Maybe<Pick<SqlResultColumn, 'dataKind' | 'entityName' | 'fullTypeName' | 'icon' | 'label' | 'maxLength' | 'name' | 'position' | 'precision' | 'scale' | 'typeName'>>>> }
|
||||
)> }
|
||||
)> }
|
||||
)> };
|
||||
)>; }
|
||||
)>; }
|
||||
)>; };
|
||||
|
||||
export type MetadataGetNodeDdlQueryVariables = {
|
||||
nodeId: Scalars['ID'];
|
||||
@@ -1055,9 +1053,9 @@ export type ReadDataFromContainerMutation = { readDataFromContainer?: Maybe<(
|
||||
& { resultSet?: Maybe<(
|
||||
Pick<SqlResultSet, 'id' | 'rows'>
|
||||
& { columns?: Maybe<Array<Maybe<Pick<SqlResultColumn, 'dataKind' | 'entityName' | 'fullTypeName' | 'icon' | 'label' | 'maxLength' | 'name' | 'position' | 'precision' | 'scale' | 'typeName'>>>> }
|
||||
)> }
|
||||
)> }
|
||||
)> };
|
||||
)>; }
|
||||
)>; }
|
||||
)>; };
|
||||
|
||||
export type SqlContextCreateMutationVariables = {
|
||||
connectionId: Scalars['ID'];
|
||||
@@ -1109,8 +1107,8 @@ export type UpdateResultsDataMutation = { result?: Maybe<(
|
||||
& { results: Array<(
|
||||
Pick<SqlQueryResults, 'updateRowCount'>
|
||||
& { resultSet?: Maybe<Pick<SqlResultSet, 'id' | 'rows'>> }
|
||||
)> }
|
||||
)> };
|
||||
)>; }
|
||||
)>; };
|
||||
|
||||
export type OpenSessionMutationVariables = {};
|
||||
|
||||
@@ -1118,7 +1116,7 @@ export type OpenSessionMutationVariables = {};
|
||||
export type OpenSessionMutation = { session: (
|
||||
Pick<SessionInfo, 'createTime' | 'lastAccessTime' | 'cacheExpired' | 'locale'>
|
||||
& { connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected'>> }
|
||||
) };
|
||||
); };
|
||||
|
||||
export type ServerConfigQueryVariables = {};
|
||||
|
||||
@@ -1126,7 +1124,7 @@ export type ServerConfigQueryVariables = {};
|
||||
export type ServerConfigQuery = { serverConfig: (
|
||||
Pick<ServerConfig, 'name' | 'version' | 'productConfiguration' | 'supportsPredefinedConnections' | 'supportsProvidedConnections' | 'supportsCustomConnections' | 'supportsConnectionBrowser' | 'supportsWorkspaces' | 'anonymousAccessEnabled' | 'authenticationEnabled'>
|
||||
& { supportedLanguages: Array<Pick<ServerLanguage, 'isoCode' | 'displayName' | 'nativeName'>> }
|
||||
) };
|
||||
); };
|
||||
|
||||
export type SessionPermissionsQueryVariables = {};
|
||||
|
||||
@@ -1139,7 +1137,7 @@ export type SessionStateQueryVariables = {};
|
||||
export type SessionStateQuery = { sessionState: (
|
||||
Pick<SessionInfo, 'createTime' | 'lastAccessTime' | 'locale' | 'cacheExpired'>
|
||||
& { connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'driverId' | 'connected'>> }
|
||||
) };
|
||||
); };
|
||||
|
||||
export type TouchSessionMutationVariables = {};
|
||||
|
||||
@@ -1257,6 +1255,43 @@ export const TestConnectionDocument = `
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const GetChildrenDbObjectInfoDocument = `
|
||||
query getChildrenDBObjectInfo($navNodeId: ID!, $filter: ObjectPropertyFilter) {
|
||||
dbObjects: navNodeChildren(parentPath: $navNodeId) {
|
||||
id
|
||||
object {
|
||||
features
|
||||
properties(filter: $filter) {
|
||||
id
|
||||
category
|
||||
dataType
|
||||
description
|
||||
displayName
|
||||
features
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const GetDbObjectInfoDocument = `
|
||||
query getDBObjectInfo($navNodeId: ID!, $filter: ObjectPropertyFilter) {
|
||||
objectInfo: navNodeInfo(nodePath: $navNodeId) {
|
||||
object {
|
||||
features
|
||||
properties(filter: $filter) {
|
||||
id
|
||||
category
|
||||
dataType
|
||||
description
|
||||
displayName
|
||||
features
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const NavNodeChildrenDocument = `
|
||||
query navNodeChildren($parentPath: ID!) {
|
||||
navNodeChildren(parentPath: $parentPath) {
|
||||
@@ -1298,44 +1333,6 @@ export const NavRefreshNodeDocument = `
|
||||
navRefreshNode(nodePath: $nodePath)
|
||||
}
|
||||
`;
|
||||
export const QueryChildrenDatabaseObjectInfoDocument = `
|
||||
query queryChildrenDatabaseObjectInfo($nodePath: ID!, $filter: ObjectPropertyFilter) {
|
||||
childrenDatabaseObjectInfo: navNodeChildren(parentPath: $nodePath) {
|
||||
id
|
||||
object {
|
||||
features
|
||||
properties(filter: $filter) {
|
||||
id
|
||||
category
|
||||
dataType
|
||||
description
|
||||
displayName
|
||||
features
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const QueryDatabaseObjectInfoDocument = `
|
||||
query queryDatabaseObjectInfo($nodeId: ID!, $filter: ObjectPropertyFilter) {
|
||||
objectInfo: navNodeInfo(nodePath: $nodeId) {
|
||||
id
|
||||
object {
|
||||
features
|
||||
properties(filter: $filter) {
|
||||
id
|
||||
category
|
||||
dataType
|
||||
description
|
||||
displayName
|
||||
features
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const ReadSessionLogDocument = `
|
||||
query readSessionLog($maxEntries: Int!, $clearEntries: Boolean!) {
|
||||
log: readSessionLog(maxEntries: $maxEntries, clearEntries: $clearEntries) {
|
||||
@@ -1841,6 +1838,12 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
|
||||
testConnection(variables: TestConnectionMutationVariables): Promise<TestConnectionMutation> {
|
||||
return withWrapper(() => client.request<TestConnectionMutation>(TestConnectionDocument, variables));
|
||||
},
|
||||
getChildrenDBObjectInfo(variables: GetChildrenDbObjectInfoQueryVariables): Promise<GetChildrenDbObjectInfoQuery> {
|
||||
return withWrapper(() => client.request<GetChildrenDbObjectInfoQuery>(GetChildrenDbObjectInfoDocument, variables));
|
||||
},
|
||||
getDBObjectInfo(variables: GetDbObjectInfoQueryVariables): Promise<GetDbObjectInfoQuery> {
|
||||
return withWrapper(() => client.request<GetDbObjectInfoQuery>(GetDbObjectInfoDocument, variables));
|
||||
},
|
||||
navNodeChildren(variables: NavNodeChildrenQueryVariables): Promise<NavNodeChildrenQuery> {
|
||||
return withWrapper(() => client.request<NavNodeChildrenQuery>(NavNodeChildrenDocument, variables));
|
||||
},
|
||||
@@ -1850,12 +1853,6 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
|
||||
navRefreshNode(variables: NavRefreshNodeQueryVariables): Promise<NavRefreshNodeQuery> {
|
||||
return withWrapper(() => client.request<NavRefreshNodeQuery>(NavRefreshNodeDocument, variables));
|
||||
},
|
||||
queryChildrenDatabaseObjectInfo(variables: QueryChildrenDatabaseObjectInfoQueryVariables): Promise<QueryChildrenDatabaseObjectInfoQuery> {
|
||||
return withWrapper(() => client.request<QueryChildrenDatabaseObjectInfoQuery>(QueryChildrenDatabaseObjectInfoDocument, variables));
|
||||
},
|
||||
queryDatabaseObjectInfo(variables: QueryDatabaseObjectInfoQueryVariables): Promise<QueryDatabaseObjectInfoQuery> {
|
||||
return withWrapper(() => client.request<QueryDatabaseObjectInfoQuery>(QueryDatabaseObjectInfoDocument, variables));
|
||||
},
|
||||
readSessionLog(variables: ReadSessionLogQueryVariables): Promise<ReadSessionLogQuery> {
|
||||
return withWrapper(() => client.request<ReadSessionLogQuery>(ReadSessionLogDocument, variables));
|
||||
},
|
||||
@@ -1963,7 +1960,7 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
|
||||
},
|
||||
touchSession(variables?: TouchSessionMutationVariables): Promise<TouchSessionMutation> {
|
||||
return withWrapper(() => client.request<TouchSessionMutation>(TouchSessionDocument, variables));
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
export type Sdk = ReturnType<typeof getSdk>;
|
||||
export type Sdk = ReturnType<typeof getSdk>;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
NavigationTreeContextMenuService, NodeManagerUtils, NodeWithParent, EObjectFeature
|
||||
NavigationTreeContextMenuService, NodeManagerUtils, NavNode, EObjectFeature
|
||||
} from '@dbeaver/core/app';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import {
|
||||
@@ -39,13 +39,13 @@ export class DataExportMenuService {
|
||||
};
|
||||
this.tableFooterMenuService.registerMenuItem(exportData);
|
||||
|
||||
this.contextMenuService.addMenuItem<NodeWithParent>(
|
||||
this.contextMenuService.addMenuItem<NavNode>(
|
||||
this.contextMenuService.getRootMenuToken(),
|
||||
{
|
||||
id: 'export',
|
||||
isPresent(context) {
|
||||
return context.contextType === NavigationTreeContextMenuService.nodeContextType
|
||||
&& !!context.data.object?.features?.includes(EObjectFeature.dataContainer);
|
||||
&& context.data.objectFeatures.includes(EObjectFeature.dataContainer);
|
||||
},
|
||||
order: 2,
|
||||
title: 'Export',
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@
|
||||
|
||||
import { observable, computed } from 'mobx';
|
||||
|
||||
import { ErrorDetailsDialog, NodesManagerService } from '@dbeaver/core/app';
|
||||
import { ErrorDetailsDialog, NavNodeManagerService } from '@dbeaver/core/app';
|
||||
import { IInitializableController, injectable } from '@dbeaver/core/di';
|
||||
import { CommonDialogService } from '@dbeaver/core/dialogs';
|
||||
import { INotification } from '@dbeaver/core/eventsLog';
|
||||
@@ -46,7 +46,7 @@ export class ExportNotificationController implements IInitializableController {
|
||||
return '';
|
||||
}
|
||||
if (this.task.context.containerNodePath) {
|
||||
const node = this.nodesManagerService.getNode(this.task.context.containerNodePath);
|
||||
const node = this.navNodeManagerService.getNode(this.task.context.containerNodePath);
|
||||
|
||||
return `${this.localization.translate('data_transfer_exporting_table')} ${node?.name}`;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export class ExportNotificationController implements IInitializableController {
|
||||
constructor(
|
||||
private commonDialogService: CommonDialogService,
|
||||
private dataExportProcessService: DataExportProcessService,
|
||||
private nodesManagerService: NodesManagerService,
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private localization: LocalizationService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { observer } from 'mobx-react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { NodesManagerService } from '@dbeaver/core/app';
|
||||
import { NavNodeManagerService } from '@dbeaver/core/app';
|
||||
import { TabIcon, Tab, TabTitle } from '@dbeaver/core/blocks';
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
import { useStyles } from '@dbeaver/core/theming';
|
||||
@@ -18,9 +18,9 @@ import { ObjectPageTabProps } from '@dbeaver/object-viewer-plugin';
|
||||
export const DataViewerTab = observer(function DataViewerTab({
|
||||
tab, page, onSelect, style,
|
||||
}: ObjectPageTabProps) {
|
||||
const nodesManagerService = useService(NodesManagerService);
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
|
||||
if (!nodesManagerService.isNodeHasData(tab.handlerState.objectId)) {
|
||||
if (!navNodeManagerService.isNodeHasData(tab.handlerState.objectId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
NodesManagerService,
|
||||
NavNodeManagerService,
|
||||
INodeNavigationData,
|
||||
IContextProvider,
|
||||
ITab,
|
||||
@@ -29,11 +29,13 @@ import { DataViewerTableService } from './DataViewerTableService';
|
||||
export class DataViewerTabService {
|
||||
page: ObjectPage;
|
||||
|
||||
constructor(private nodesManagerService: NodesManagerService,
|
||||
private dataViewerTableService: DataViewerTableService,
|
||||
private objectViewerTabService: ObjectViewerTabService,
|
||||
private dbObjectPageService: DBObjectPageService,
|
||||
private notificationService: NotificationService) {
|
||||
constructor(
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private dataViewerTableService: DataViewerTableService,
|
||||
private objectViewerTabService: ObjectViewerTabService,
|
||||
private dbObjectPageService: DBObjectPageService,
|
||||
private notificationService: NotificationService
|
||||
) {
|
||||
|
||||
this.page = this.dbObjectPageService.register({
|
||||
key: dataViewerHandlerKey,
|
||||
@@ -49,7 +51,7 @@ export class DataViewerTabService {
|
||||
}
|
||||
|
||||
registerTabHandler() {
|
||||
this.nodesManagerService.navigator.addHandler(this.navigationHandler.bind(this));
|
||||
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
|
||||
}
|
||||
|
||||
private async navigationHandler(contexts: IContextProvider<INodeNavigationData>) {
|
||||
@@ -60,14 +62,12 @@ export class DataViewerTabService {
|
||||
trySwitchPage,
|
||||
} = await contexts.getContext(this.objectViewerTabService.objectViewerTabContext);
|
||||
|
||||
|
||||
if (nodeInfo.type === NavigationType.closeConnection) {
|
||||
return;
|
||||
}
|
||||
// const tabInfo = await contexts.getContext(this.navigationTabsService.navigationTabContext);
|
||||
const objectInfo = await this.nodesManagerService.loadDatabaseObjectInfo(nodeInfo.nodeId);
|
||||
const node = await this.navNodeManagerService.loadNode(nodeInfo);
|
||||
|
||||
if (!this.nodesManagerService.isNodeHasData(objectInfo)) {
|
||||
if (!this.navNodeManagerService.isNodeHasData(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,9 +83,12 @@ export class DataViewerTabService {
|
||||
}
|
||||
|
||||
private async handleTabSelect(tab: ITab<IObjectViewerTabState>) {
|
||||
const objectInfo = await this.nodesManagerService.loadDatabaseObjectInfo(tab.handlerState.objectId);
|
||||
const node = await this.navNodeManagerService.loadNode({
|
||||
nodeId: tab.handlerState.objectId,
|
||||
parentId: tab.handlerState.parentId,
|
||||
});
|
||||
|
||||
if (!this.nodesManagerService.isNodeHasData(objectInfo)) {
|
||||
if (!this.navNodeManagerService.isNodeHasData(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,7 +96,7 @@ export class DataViewerTabService {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeInfo = this.nodesManagerService
|
||||
const nodeInfo = this.navNodeManagerService
|
||||
.getNodeContainerInfo(tab.handlerState.objectId);
|
||||
|
||||
if (!nodeInfo.connectionId) {
|
||||
@@ -111,11 +114,12 @@ export class DataViewerTabService {
|
||||
// if (!this.nodesManagerService.isNodeHasData(tab.handlerState.objectId)) {
|
||||
// return;
|
||||
// }
|
||||
const info = await this.nodesManagerService.loadDatabaseObjectInfo(tab.handlerState.objectId);
|
||||
if (info) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
await this.navNodeManagerService.loadNode({
|
||||
nodeId: tab.handlerState.objectId,
|
||||
parentId: tab.handlerState.parentId,
|
||||
});
|
||||
// await this.dbObjectService.load(tab.handlerState.objectId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleTabClose(tab: ITab<IObjectViewerTabState>) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { observable, when } from 'mobx';
|
||||
|
||||
import { NodeManagerUtils, NodesManagerService } from '@dbeaver/core/app';
|
||||
import { NodeManagerUtils, NavNodeManagerService } from '@dbeaver/core/app';
|
||||
import { IDestructibleController, IInitializableController, injectable } from '@dbeaver/core/di';
|
||||
import { NotificationService } from '@dbeaver/core/eventsLog';
|
||||
import { SqlDialectInfo } from '@dbeaver/core/sdk';
|
||||
@@ -25,11 +25,12 @@ export class DdlViewerController implements IInitializableController, IDestructi
|
||||
|
||||
private nodeId!: string;
|
||||
|
||||
constructor(private ddlViewerService: DdlViewerService,
|
||||
private nodesManagerService: NodesManagerService,
|
||||
private sqlDialectInfoService: SqlDialectInfoService,
|
||||
private notificationService: NotificationService) {
|
||||
}
|
||||
constructor(
|
||||
private ddlViewerService: DdlViewerService,
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private sqlDialectInfoService: SqlDialectInfoService,
|
||||
private notificationService: NotificationService
|
||||
) { }
|
||||
|
||||
init(nodeId: string) {
|
||||
this.nodeId = nodeId;
|
||||
@@ -55,7 +56,7 @@ export class DdlViewerController implements IInitializableController, IDestructi
|
||||
}
|
||||
|
||||
private async loadDialect(nodeId: string): Promise<void> {
|
||||
const { connectionId } = this.nodesManagerService.getNodeContainerInfo(nodeId);
|
||||
const { connectionId } = this.navNodeManagerService.getNodeContainerInfo(nodeId);
|
||||
if (!connectionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
EObjectFeature, NodesManagerService, IContextProvider, INodeNavigationData
|
||||
EObjectFeature, NavNodeManagerService, IContextProvider, INodeNavigationData
|
||||
} from '@dbeaver/core/app';
|
||||
import { ITab } from '@dbeaver/core/blocks';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
@@ -22,16 +22,18 @@ const ddlViewerTabId = 'ddl-viewer';
|
||||
@injectable()
|
||||
export class DdlViewerTabService {
|
||||
|
||||
constructor(private nodesManagerService: NodesManagerService,
|
||||
private objectViewerTabService: ObjectViewerTabService,
|
||||
private notificationService: NotificationService,
|
||||
private ddlViewerService: DdlViewerService) {
|
||||
this.nodesManagerService.navigator.addHandler(this.navigationHandler.bind(this));
|
||||
constructor(
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private objectViewerTabService: ObjectViewerTabService,
|
||||
private notificationService: NotificationService,
|
||||
private ddlViewerService: DdlViewerService
|
||||
) {
|
||||
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
|
||||
}
|
||||
|
||||
buildTab(nodeId: string): ITab | null {
|
||||
const node = this.nodesManagerService.getNode(nodeId);
|
||||
const isDdlRequired = node?.object?.features?.includes(EObjectFeature.script);
|
||||
const node = this.navNodeManagerService.getNode(nodeId);
|
||||
const isDdlRequired = node?.objectFeatures.includes(EObjectFeature.script);
|
||||
if (!isDdlRequired) {
|
||||
return null;
|
||||
}
|
||||
@@ -39,7 +41,7 @@ export class DdlViewerTabService {
|
||||
tabId: ddlViewerTabId,
|
||||
title: 'DDL',
|
||||
icon: 'sql-text',
|
||||
onActivate: () => this.activateDDLTab(nodeId),
|
||||
onActivate: () => this.activateDDLTab(nodeId, node?.parentId!),
|
||||
panel: () => ddlViewer(nodeId),
|
||||
};
|
||||
return ddlTab;
|
||||
@@ -57,7 +59,7 @@ export class DdlViewerTabService {
|
||||
}
|
||||
}
|
||||
|
||||
private activateDDLTab(nodeId: string) {
|
||||
this.nodesManagerService.navToNode(nodeId, ddlViewerTabId);
|
||||
private activateDDLTab(nodeId: string, parentId: string) {
|
||||
this.navNodeManagerService.navToNode(nodeId, parentId, ddlViewerTabId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
export interface IObjectViewerTabState {
|
||||
objectId: string;
|
||||
parentId: string;
|
||||
parents: string[];
|
||||
folderId: string;
|
||||
tabIcon?: string;
|
||||
tabTitle?: string;
|
||||
|
||||
+6
-6
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { NodesManagerService, TabEntity } from '@dbeaver/core/app';
|
||||
import { NavNodeManagerService, TabEntity } from '@dbeaver/core/app';
|
||||
import { ITab } from '@dbeaver/core/blocks';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ObjectFolderTabModel } from './ObjectFolderTabModel';
|
||||
@injectable()
|
||||
export class ObjectFoldersTabService {
|
||||
|
||||
constructor(private nodesManagerService: NodesManagerService) {
|
||||
constructor(private navNodeManagerService: NavNodeManagerService) {
|
||||
}
|
||||
|
||||
createTabEntities(nodeId: string): TabEntity[] {
|
||||
@@ -24,22 +24,22 @@ export class ObjectFoldersTabService {
|
||||
|
||||
|
||||
private tabBuilder(nodeId: string): ITab[] {
|
||||
const children = this.nodesManagerService.getChildren(nodeId)?.children || [];
|
||||
const children = this.navNodeManagerService.getTree(nodeId) || [];
|
||||
|
||||
const folderTabs = children.filter((nodeId) => {
|
||||
const node = this.nodesManagerService.getNode(nodeId);
|
||||
const node = this.navNodeManagerService.getNode(nodeId);
|
||||
return node ? node.folder : false;
|
||||
});
|
||||
|
||||
const tabList: ITab[] = folderTabs.map((nodeId) => {
|
||||
const node = this.nodesManagerService.getNode(nodeId);
|
||||
const node = this.navNodeManagerService.getNode(nodeId);
|
||||
if (!node) {
|
||||
throw Error(`node not found: ${nodeId}`);
|
||||
}
|
||||
|
||||
return new ObjectFolderTabModel(
|
||||
node,
|
||||
() => this.nodesManagerService.navToNode(node.id)
|
||||
() => this.navNodeManagerService.navToNode(node.id, node.parentId)
|
||||
);
|
||||
});
|
||||
return tabList;
|
||||
|
||||
+2
-2
@@ -35,9 +35,9 @@ const viewerStyles = composes(
|
||||
export const ObjectPropertiesPagePanel = observer(function ObjectPropertiesPagePanel({
|
||||
tab,
|
||||
}: ObjectPagePanelProps) {
|
||||
const children = useChildren(tab.handlerState.objectId);
|
||||
const { children, isLoading } = useChildren(tab.handlerState.objectId);
|
||||
|
||||
if (!children?.isLoaded) {
|
||||
if (!children || isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
NodesManagerService, ITab, IContextProvider, INodeNavigationData
|
||||
NavNodeManagerService, ITab, IContextProvider, INodeNavigationData
|
||||
} from '@dbeaver/core/app';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { NotificationService } from '@dbeaver/core/eventsLog';
|
||||
@@ -24,7 +24,7 @@ export class ObjectPropertiesPageService {
|
||||
page?: ObjectPage;
|
||||
|
||||
constructor(
|
||||
private nodesManagerService: NodesManagerService,
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private notificationService: NotificationService,
|
||||
private objectViewerTabService: ObjectViewerTabService,
|
||||
private dbObjectPageService: DBObjectPageService
|
||||
@@ -41,7 +41,7 @@ export class ObjectPropertiesPageService {
|
||||
getPanelComponent: () => ObjectPropertiesPagePanel,
|
||||
onSelect: this.handlePageSelect.bind(this),
|
||||
});
|
||||
this.nodesManagerService.navigator.addHandler(this.navigationHandler.bind(this));
|
||||
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
|
||||
}
|
||||
|
||||
private async handlePageSelect(tab: ITab<IObjectViewerTabState>) {
|
||||
|
||||
+6
-6
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react';
|
||||
import { useCallback } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { NodesManagerService, useDatabaseObjectInfo, useNode } from '@dbeaver/core/app';
|
||||
import { NavNodeManagerService, useDatabaseObjectInfo, useNode } from '@dbeaver/core/app';
|
||||
import {
|
||||
StaticImage, TableItem, TableColumnValue, TableItemSelect
|
||||
} from '@dbeaver/core/blocks';
|
||||
@@ -51,12 +51,12 @@ type ItemProps = {
|
||||
export const Item = observer(function Item({
|
||||
objectId, columns,
|
||||
}: ItemProps) {
|
||||
const nodesManagerService = useService(NodesManagerService);
|
||||
const object = useNode(objectId);
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const { node } = useNode(objectId);
|
||||
const databaseObjectInfo = useDatabaseObjectInfo(objectId);
|
||||
const handleOpen = useCallback(() => nodesManagerService.navToNode(objectId), [objectId]);
|
||||
const handleOpen = useCallback(() => navNodeManagerService.navToNode(node!.id, node!.parentId), [node]);
|
||||
|
||||
if (!object || !databaseObjectInfo?.properties) {
|
||||
if (!node || !databaseObjectInfo?.properties) {
|
||||
return styled(useStyles(itemStyles))(
|
||||
<TableItem item={objectId} onDoubleClick={handleOpen}>
|
||||
<TableColumnValue centerContent><TableItemSelect /></TableColumnValue>
|
||||
@@ -81,7 +81,7 @@ export const Item = observer(function Item({
|
||||
<TableColumnValue centerContent><TableItemSelect /></TableColumnValue>
|
||||
<TableColumnValue>
|
||||
<icon as="div">
|
||||
<StaticImage icon={object.icon} />
|
||||
<StaticImage icon={node.icon} />
|
||||
</icon>
|
||||
</TableColumnValue>
|
||||
{databaseObjectInfo.properties.map(property => (
|
||||
|
||||
+8
-5
@@ -22,15 +22,18 @@ const styles = css`
|
||||
`;
|
||||
|
||||
type ObjectPropertyTableProps = {
|
||||
parentId: string;
|
||||
objectId: string;
|
||||
parentId: string;
|
||||
}
|
||||
|
||||
export const ObjectPropertyTable = observer(function ObjectPropertyTable(props: ObjectPropertyTableProps) {
|
||||
const children = useChildren(props.objectId);
|
||||
const { isLoading } = useObjectFolder(props.parentId, props.objectId);
|
||||
export const ObjectPropertyTable = observer(function ObjectPropertyTable({
|
||||
objectId,
|
||||
parentId,
|
||||
}: ObjectPropertyTableProps) {
|
||||
const children = useChildren(objectId);
|
||||
const { isLoading } = useObjectFolder(parentId);
|
||||
|
||||
if (!children?.isLoaded || isLoading) {
|
||||
if (!children.children || children.isLoading || isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -8,7 +8,7 @@
|
||||
|
||||
import { observable } from 'mobx';
|
||||
|
||||
import { NodesManagerService } from '@dbeaver/core/app';
|
||||
import { DBObjectService } from '@dbeaver/core/app';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
|
||||
import { VirtualFolderTabData } from './VirtualFolderTabData';
|
||||
@@ -18,9 +18,10 @@ export class VirtualFolderTabMixin {
|
||||
|
||||
@observable isActivated = false;
|
||||
|
||||
constructor(private virtualFolderTabData: VirtualFolderTabData,
|
||||
private nodesManagerService: NodesManagerService) {
|
||||
}
|
||||
constructor(
|
||||
private virtualFolderTabData: VirtualFolderTabData,
|
||||
private dbObjectService: DBObjectService
|
||||
) {}
|
||||
|
||||
getChildrenId() {
|
||||
return this.virtualFolderTabData.childrenIds;
|
||||
@@ -37,7 +38,7 @@ export class VirtualFolderTabMixin {
|
||||
|
||||
try {
|
||||
for (const nodeId of this.getChildrenId()) {
|
||||
await this.nodesManagerService.loadDatabaseObjectInfo(nodeId);
|
||||
await this.dbObjectService.load(nodeId);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
|
||||
+5
-6
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { NodesManagerService, NodeWithParent, TabEntity } from '@dbeaver/core/app';
|
||||
import { NavNodeManagerService, NavNode, TabEntity } from '@dbeaver/core/app';
|
||||
import { injectable, MixinProvider } from '@dbeaver/core/di';
|
||||
|
||||
import { VirtualFolderTabData } from './VirtualFolderTabData';
|
||||
@@ -17,15 +17,14 @@ import { VirtualFolderTabModel } from './VirtualFolderTabModel';
|
||||
@injectable()
|
||||
export class VirtualFolderTabService {
|
||||
|
||||
constructor(private nodesManagerService: NodesManagerService) {
|
||||
}
|
||||
constructor(private navNodeManagerService: NavNodeManagerService) { }
|
||||
|
||||
createTabEntities(nodeId: string): TabEntity[] {
|
||||
const children = this.nodesManagerService.getChildren(nodeId)?.children || [];
|
||||
const children = this.navNodeManagerService.getTree(nodeId) || [];
|
||||
|
||||
const notFolderNodes = children.reduce<NodeWithParent[]>(
|
||||
const notFolderNodes = children.reduce<NavNode[]>(
|
||||
(nodes, nodeId) => {
|
||||
const node = this.nodesManagerService.getNode(nodeId);
|
||||
const node = this.navNodeManagerService.getNode(nodeId);
|
||||
if (node && !node.folder) {
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
@@ -39,11 +39,11 @@ const stylesArray = [styles];
|
||||
export const ObjectViewerPanel = observer(function ObjectViewerPanel({
|
||||
tab, handler,
|
||||
}: TabHandlerPanelProps<IObjectViewerTabState>) {
|
||||
const children = useChildren(tab.handlerState.objectId);
|
||||
const { children, isLoading } = useChildren(tab.handlerState.objectId);
|
||||
const dbObjectPagesService = useService(DBObjectPageService);
|
||||
const pages = dbObjectPagesService.orderedPages;
|
||||
|
||||
if (!children?.isLoaded) {
|
||||
if (!children || isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { IObjectViewerTabState } from './IObjectViewerTabState';
|
||||
export const ObjectViewerTab = observer(function ObjectViewerTab({
|
||||
tab, handler, onSelect, onClose, style,
|
||||
}: TabHandlerTabProps<IObjectViewerTabState>) {
|
||||
const node = useNode(tab.handlerState.objectId);
|
||||
const { node } = useNode(tab.handlerState.objectId);
|
||||
|
||||
return styled(useStyles(...style))(
|
||||
<Tab tabId={tab.id} onOpen={onSelect} onClose={onClose} >
|
||||
|
||||
@@ -6,10 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
|
||||
import {
|
||||
NodesManagerService,
|
||||
NavigationTabsService,
|
||||
INodeNavigationData,
|
||||
IContextProvider,
|
||||
@@ -21,6 +18,9 @@ import {
|
||||
objectCatalogProvider,
|
||||
objectSchemaProvider,
|
||||
NavNodeManagerService,
|
||||
DBObjectService,
|
||||
ROOT_NODE_PATH,
|
||||
NavNode,
|
||||
} from '@dbeaver/core/app';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { NotificationService } from '@dbeaver/core/eventsLog';
|
||||
@@ -35,13 +35,15 @@ import { objectViewerTabHandlerKey } from './objectViewerTabHandlerKey';
|
||||
|
||||
@injectable()
|
||||
export class ObjectViewerTabService {
|
||||
@observable private propertiesTabLoadingState: Map<string, boolean> = new Map();
|
||||
readonly tabHandler: TabHandler<IObjectViewerTabState>
|
||||
|
||||
constructor(private nodesManagerService: NodesManagerService,
|
||||
private dbObjectPageService: DBObjectPageService,
|
||||
private notificationService: NotificationService,
|
||||
private navigationTabsService: NavigationTabsService) {
|
||||
constructor(
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private dbObjectService: DBObjectService,
|
||||
private dbObjectPageService: DBObjectPageService,
|
||||
private notificationService: NotificationService,
|
||||
private navigationTabsService: NavigationTabsService
|
||||
) {
|
||||
|
||||
this.tabHandler = this.navigationTabsService
|
||||
.registerTabHandler<IObjectViewerTabState>({
|
||||
@@ -60,95 +62,9 @@ export class ObjectViewerTabService {
|
||||
});
|
||||
}
|
||||
|
||||
getConnection(context: ITab<IObjectViewerTabState>) {
|
||||
const nodeInfo = this.nodesManagerService
|
||||
.getNodeContainerInfo(context.handlerState.objectId);
|
||||
|
||||
if (!nodeInfo.connectionId) {
|
||||
return;
|
||||
}
|
||||
// connection node id differs from connection id
|
||||
return NodeManagerUtils.connectionNodeIdToConnectionId(nodeInfo.connectionId);
|
||||
}
|
||||
|
||||
getDBObjectCatalog(context: ITab<IObjectViewerTabState>) {
|
||||
const nodeInfo = this.nodesManagerService
|
||||
.getNodeContainerInfo(context.handlerState.objectId);
|
||||
|
||||
if (nodeInfo.catalogId) {
|
||||
return;
|
||||
}
|
||||
return nodeInfo.catalogId;
|
||||
}
|
||||
|
||||
getDBObjectSchema(context: ITab<IObjectViewerTabState>) {
|
||||
const nodeInfo = this.nodesManagerService
|
||||
.getNodeContainerInfo(context.handlerState.objectId);
|
||||
|
||||
if (!nodeInfo.schemaId) {
|
||||
return;
|
||||
}
|
||||
return nodeInfo.schemaId;
|
||||
}
|
||||
|
||||
registerTabHandler() {
|
||||
this.nodesManagerService.navigator.addHandler(this.navigationHandler.bind(this));
|
||||
}
|
||||
|
||||
isTabLoading(id: string): boolean {
|
||||
return !this.propertiesTabLoadingState.get(id);
|
||||
}
|
||||
|
||||
async selectObjectTab(tab: ITab<IObjectViewerTabState>) {
|
||||
try {
|
||||
const currentPage = this.dbObjectPageService.getPage(tab.handlerState.pageId);
|
||||
if (currentPage) {
|
||||
await this.dbObjectPageService.selectPage(tab, currentPage);
|
||||
}
|
||||
|
||||
// TODO: must be loaded by info folder?
|
||||
await this.nodesManagerService.loadDatabaseObjectInfo(tab.handlerState.objectId);
|
||||
await this.nodesManagerService.loadNodeInfo(tab.handlerState.objectId);
|
||||
|
||||
const children = await this.nodesManagerService.loadChildren(tab.handlerState.objectId);
|
||||
const folderId = tab.handlerState.folderId;
|
||||
const propertiesTabLoadingKey = `${tab.handlerState.objectId}_${folderId}`;
|
||||
|
||||
try {
|
||||
if (children.length === 0 || !NodeManagerUtils.isDatabaseObject(folderId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.propertiesTabLoadingState.set(propertiesTabLoadingKey, false);
|
||||
await this.nodesManagerService.loadChildrenDatabaseObjectInfo(folderId);
|
||||
} finally {
|
||||
this.propertiesTabLoadingState.set(propertiesTabLoadingKey, true);
|
||||
}
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Error in Object Viewer while tab selecting');
|
||||
}
|
||||
}
|
||||
|
||||
async restoreObjectTab(tab: ITab<IObjectViewerTabState>) {
|
||||
if (
|
||||
typeof tab.handlerState?.folderId === 'string'
|
||||
&& typeof tab.handlerState?.objectId === 'string'
|
||||
&& (!tab.handlerState.tabIcon || typeof tab.handlerState.tabIcon === 'string')
|
||||
&& (!tab.handlerState.tabTitle || typeof tab.handlerState.tabTitle === 'string')
|
||||
) {
|
||||
const node = await this.nodesManagerService.loadNodeInfo(tab.handlerState.objectId);
|
||||
if (node) {
|
||||
tab.handlerState.tabIcon = node.icon;
|
||||
tab.handlerState.tabTitle = node.name;
|
||||
|
||||
return this.dbObjectPageService.restorePages(tab);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async closeObjectTab(tab: ITab<IObjectViewerTabState>) {
|
||||
await this.dbObjectPageService.closePages(tab);
|
||||
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
|
||||
this.navNodeManagerService.navNode.onDataUpdate.subscribe(this.updateTabs.bind(this));
|
||||
}
|
||||
|
||||
objectViewerTabContext = async (
|
||||
@@ -156,7 +72,7 @@ export class ObjectViewerTabService {
|
||||
data: INodeNavigationData
|
||||
): Promise<IObjectViewerTabContext> => {
|
||||
const tabInfo = await contexts.getContext(this.navigationTabsService.navigationTabContext);
|
||||
const nodeInfo = await contexts.getContext(this.nodesManagerService.navigationNodeContext);
|
||||
const nodeInfo = await contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
|
||||
|
||||
if (data.type !== NavigationType.closeConnection) {
|
||||
// check if tab already exist for object
|
||||
@@ -173,6 +89,8 @@ export class ObjectViewerTabService {
|
||||
handlerId: objectViewerTabHandlerKey,
|
||||
handlerState: {
|
||||
objectId: nodeInfo.nodeId,
|
||||
parentId: nodeInfo.parentId,
|
||||
parents: await nodeInfo.getParents(),
|
||||
folderId: nodeInfo.folderId,
|
||||
pageId: '',
|
||||
pagesState: new Map(),
|
||||
@@ -214,6 +132,115 @@ export class ObjectViewerTabService {
|
||||
};
|
||||
}
|
||||
|
||||
private async updateTabs(data: Map<string, NavNode>) {
|
||||
for (const tab of this.navigationTabsService.findTabs(isObjectViewerTab(tab => tab.restored))) {
|
||||
if (!data.has(tab.handlerState.objectId)) {
|
||||
await this.navigationTabsService.closeTab(tab.id, true);
|
||||
} else if (tab.id === this.navigationTabsService.currentTabId) {
|
||||
const loaded = this.navNodeManagerService.navNode.isLoaded({
|
||||
nodes: [{ nodeId: tab.handlerState.objectId, parentId: tab.handlerState.parentId }],
|
||||
});
|
||||
const loading = this.navNodeManagerService.navNode.isDataLoading({
|
||||
nodes: [{ nodeId: tab.handlerState.objectId, parentId: tab.handlerState.parentId }],
|
||||
});
|
||||
|
||||
if (!loaded && !loading) {
|
||||
await this.navigationTabsService.selectTab(tab.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getConnection(context: ITab<IObjectViewerTabState>) {
|
||||
const nodeInfo = this.navNodeManagerService
|
||||
.getNodeContainerInfo(context.handlerState.objectId);
|
||||
|
||||
if (!nodeInfo.connectionId) {
|
||||
return;
|
||||
}
|
||||
// connection node id differs from connection id
|
||||
return NodeManagerUtils.connectionNodeIdToConnectionId(nodeInfo.connectionId);
|
||||
}
|
||||
|
||||
private getDBObjectCatalog(context: ITab<IObjectViewerTabState>) {
|
||||
const nodeInfo = this.navNodeManagerService
|
||||
.getNodeContainerInfo(context.handlerState.objectId);
|
||||
|
||||
if (nodeInfo.catalogId) {
|
||||
return;
|
||||
}
|
||||
return nodeInfo.catalogId;
|
||||
}
|
||||
|
||||
private getDBObjectSchema(context: ITab<IObjectViewerTabState>) {
|
||||
const nodeInfo = this.navNodeManagerService
|
||||
.getNodeContainerInfo(context.handlerState.objectId);
|
||||
|
||||
if (!nodeInfo.schemaId) {
|
||||
return;
|
||||
}
|
||||
return nodeInfo.schemaId;
|
||||
}
|
||||
|
||||
private async selectObjectTab(tab: ITab<IObjectViewerTabState>) {
|
||||
try {
|
||||
const currentPage = this.dbObjectPageService.getPage(tab.handlerState.pageId);
|
||||
if (currentPage) {
|
||||
await this.dbObjectPageService.selectPage(tab, currentPage);
|
||||
}
|
||||
|
||||
// TODO: must be loaded by info folder?
|
||||
await this.navNodeManagerService.loadNode({
|
||||
nodeId: tab.handlerState.objectId,
|
||||
parentId: tab.handlerState.parentId,
|
||||
});
|
||||
const children = await this.navNodeManagerService.loadTree(tab.handlerState.objectId);
|
||||
await this.dbObjectService.load(tab.handlerState.objectId);
|
||||
|
||||
const folderId = tab.handlerState.folderId;
|
||||
|
||||
if (children.length === 0 || !NodeManagerUtils.isDatabaseObject(folderId)) {
|
||||
return;
|
||||
}
|
||||
const folderChildren = await this.navNodeManagerService.loadTree(folderId);
|
||||
|
||||
await this.dbObjectService.load(folderChildren);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Error in Object Viewer while tab selecting');
|
||||
}
|
||||
}
|
||||
|
||||
private async restoreObjectTab(tab: ITab<IObjectViewerTabState>) {
|
||||
if (
|
||||
typeof tab.handlerState?.folderId === 'string'
|
||||
&& typeof tab.handlerState.parentId === 'string'
|
||||
&& Array.isArray(tab.handlerState.parents)
|
||||
&& typeof tab.handlerState.objectId === 'string'
|
||||
&& (!tab.handlerState.tabIcon || typeof tab.handlerState.tabIcon === 'string')
|
||||
&& (!tab.handlerState.tabTitle || typeof tab.handlerState.tabTitle === 'string')
|
||||
) {
|
||||
for (const nodeId of tab.handlerState.parents) {
|
||||
await this.navNodeManagerService.loadTree(nodeId);
|
||||
}
|
||||
|
||||
const node = await this.navNodeManagerService.loadNode({
|
||||
nodeId: tab.handlerState.objectId,
|
||||
parentId: tab.handlerState.parentId,
|
||||
});
|
||||
if (node) {
|
||||
tab.handlerState.tabIcon = node.icon;
|
||||
tab.handlerState.tabTitle = node.name;
|
||||
|
||||
return this.dbObjectPageService.restorePages(tab);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async closeObjectTab(tab: ITab<IObjectViewerTabState>) {
|
||||
await this.dbObjectPageService.closePages(tab);
|
||||
}
|
||||
|
||||
private async navigationHandler(contexts: IContextProvider<INodeNavigationData>) {
|
||||
try {
|
||||
const { tab, tabInfo, nodeInfo } = await contexts.getContext(this.objectViewerTabContext);
|
||||
|
||||
@@ -6,14 +6,18 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { NavNodeManagerService, DBObjectService } from '@dbeaver/core/app';
|
||||
import { useService } from '@dbeaver/core/di';
|
||||
|
||||
import { ObjectViewerTabService } from './ObjectViewerTabService';
|
||||
|
||||
export function useObjectFolder(objectId: string, folderId: string) {
|
||||
const objectViewerTabService = useService(ObjectViewerTabService);
|
||||
export function useObjectFolder(objectId: string) {
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const dbObjectService = useService(DBObjectService);
|
||||
|
||||
const isLoading = objectViewerTabService.isTabLoading(`${objectId}_${folderId}`);
|
||||
const children = navNodeManagerService.getTree(objectId) || [];
|
||||
|
||||
const isLoading = children.some(navNodeId => !dbObjectService.getDBObject(navNodeId))
|
||||
&& dbObjectService.dbObject.isDataLoading({ navNodeId: children });
|
||||
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
NavigationTreeContextMenuService,
|
||||
EObjectFeature,
|
||||
NodeManagerUtils,
|
||||
NodeWithParent, ConnectionsManagerService,
|
||||
NavNode, ConnectionsManagerService,
|
||||
} from '@dbeaver/core/app';
|
||||
import { injectable } from '@dbeaver/core/di';
|
||||
import { ContextMenuService, IContextMenuItem, IMenuContext } from '@dbeaver/core/dialogs';
|
||||
@@ -21,11 +21,13 @@ import { SqlEditorTabService } from './SqlEditorTabService';
|
||||
|
||||
@injectable()
|
||||
export class SqlEditorBootstrap {
|
||||
constructor(private mainMenuService: MainMenuService,
|
||||
private contextMenuService: ContextMenuService,
|
||||
private connectionsManagerService: ConnectionsManagerService,
|
||||
private sqlEditorTabService: SqlEditorTabService,
|
||||
private sqlEditorNavigatorService: SqlEditorNavigatorService) {}
|
||||
constructor(
|
||||
private mainMenuService: MainMenuService,
|
||||
private contextMenuService: ContextMenuService,
|
||||
private connectionsManagerService: ConnectionsManagerService,
|
||||
private sqlEditorTabService: SqlEditorTabService,
|
||||
private sqlEditorNavigatorService: SqlEditorNavigatorService
|
||||
) {}
|
||||
|
||||
async bootstrap() {
|
||||
this.sqlEditorTabService.registerTabHandler();
|
||||
@@ -40,20 +42,20 @@ export class SqlEditorBootstrap {
|
||||
}
|
||||
);
|
||||
|
||||
const openSqlEditor: IContextMenuItem<NodeWithParent> = {
|
||||
const openSqlEditor: IContextMenuItem<NavNode> = {
|
||||
id: 'open-sql-editor',
|
||||
isPresent(context) {
|
||||
return context.contextType === NavigationTreeContextMenuService.nodeContextType
|
||||
&& Boolean((context.data as NodeWithParent)?.object?.features?.includes(EObjectFeature.dataSource));
|
||||
&& context.data.objectFeatures.includes(EObjectFeature.dataSource);
|
||||
},
|
||||
title: 'SQL',
|
||||
order: 2,
|
||||
onClick: (context: IMenuContext<NodeWithParent>) => {
|
||||
onClick: (context: IMenuContext<NavNode>) => {
|
||||
const node = context.data;
|
||||
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(node.id);
|
||||
this.sqlEditorNavigatorService.openNewEditor(connectionId);
|
||||
},
|
||||
};
|
||||
this.contextMenuService.addMenuItem<NodeWithParent>(this.contextMenuService.getRootMenuToken(), openSqlEditor);
|
||||
this.contextMenuService.addMenuItem<NavNode>(this.contextMenuService.getRootMenuToken(), openSqlEditor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
NavigationService,
|
||||
IContextProvider,
|
||||
ITabOptions,
|
||||
NodesManagerService,
|
||||
NavNodeManagerService,
|
||||
INodeNavigationData,
|
||||
NavigationType,
|
||||
} from '@dbeaver/core/app';
|
||||
@@ -57,19 +57,21 @@ export interface SQLEditorAction extends SQLEditorActionContext {
|
||||
export class SqlEditorNavigatorService {
|
||||
|
||||
private readonly navigator: INavigator<SQLCreateAction | SQLEditorAction>;
|
||||
constructor(private navigationTabsService: NavigationTabsService,
|
||||
private connectionsManagerService: ConnectionsManagerService,
|
||||
private notificationService: NotificationService,
|
||||
private gql: GraphQLService,
|
||||
private sqlDialectInfoService: SqlDialectInfoService,
|
||||
private nodesManagerService: NodesManagerService,
|
||||
private navigationService: NavigationService) {
|
||||
constructor(
|
||||
private navigationTabsService: NavigationTabsService,
|
||||
private connectionsManagerService: ConnectionsManagerService,
|
||||
private notificationService: NotificationService,
|
||||
private gql: GraphQLService,
|
||||
private sqlDialectInfoService: SqlDialectInfoService,
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private navigationService: NavigationService
|
||||
) {
|
||||
|
||||
this.navigator = this.navigationService.createNavigator<SQLCreateAction | SQLEditorAction>(
|
||||
null,
|
||||
this.navigateHandler.bind(this)
|
||||
);
|
||||
this.nodesManagerService.navigator.addHandler(this.nodeNavigationHandler.bind(this));
|
||||
this.navNodeManagerService.navigator.addHandler(this.nodeNavigationHandler.bind(this));
|
||||
}
|
||||
|
||||
registerTabHandler() {
|
||||
@@ -100,7 +102,7 @@ export class SqlEditorNavigatorService {
|
||||
|
||||
private async nodeNavigationHandler(contexts: IContextProvider<INodeNavigationData>) {
|
||||
try {
|
||||
const nodeInfo = await contexts.getContext(this.nodesManagerService.navigationNodeContext);
|
||||
const nodeInfo = await contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
|
||||
|
||||
if (nodeInfo.type === NavigationType.closeConnection) {
|
||||
for (const tab of this.navigationTabsService.findTabs(
|
||||
|
||||
Reference in New Issue
Block a user