mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Feat CB-3293 node info transformer (#1778)
* CB-3293 feat: node info transformer * CB-3293 feat: hide resource extension in nav-tree * CB-3293 feat: rename resources without extension * CB-3293 fix: resource folder renaming * CB-3293 fix: add resource delete confirmation * CB-3293 chore: remove unused code * CB-3293 fix: resource deletion * CB-2393 chore: unify delete confirmation dialog --------- Co-authored-by: dariamarutkina <125263541+dariamarutkina@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export const NAV_NODE_TYPE_CONNECTION = 'Connection';
|
||||
@@ -14,6 +14,7 @@ export * from './NavTree/getConnectionFolderIdFromNodeId';
|
||||
export * from './NavTree/getConnectionFolderId';
|
||||
export * from './NavTree/getConnectionParentId';
|
||||
export * from './NavTree/getFolderNodeParents';
|
||||
export * from './NavTree/NAV_NODE_TYPE_CONNECTION';
|
||||
|
||||
export * from './extensions/IConnectionProvider';
|
||||
export * from './extensions/IConnectionSetter';
|
||||
|
||||
@@ -13,5 +13,12 @@ export const ConfirmationDialogDelete: DialogComponent<ConfirmationDialogPayload
|
||||
payload,
|
||||
...rest
|
||||
}) {
|
||||
return <ConfirmationDialog payload={{ ...payload, icon: '/icons/error_icon.svg', bigIcon: true }} {...rest} />;
|
||||
const bigIcon = payload.bigIcon ?? payload.subTitle === undefined ?? true;
|
||||
let icon = payload.icon ?? '/icons/error_icon_sm.svg';
|
||||
|
||||
if (bigIcon) {
|
||||
icon = '/icons/error_icon.svg';
|
||||
}
|
||||
|
||||
return <ConfirmationDialog payload={{ ...payload, icon, bigIcon }} {...rest} />;
|
||||
};
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
ProjectInfo as SchemaProjectInfo,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { createResourceOfType } from './createResourceOfType';
|
||||
|
||||
export type ProjectInfo = SchemaProjectInfo;
|
||||
export type ProjectInfoResourceType = RmResourceType;
|
||||
|
||||
@@ -42,6 +44,37 @@ export class ProjectInfoResource extends CachedMapResource<string, ProjectInfo>
|
||||
});
|
||||
}
|
||||
|
||||
getNameWithoutExtension(projectId: string, resourceTypeId: string, fileName: string): string {
|
||||
const project = this.get(projectId);
|
||||
|
||||
if (project) {
|
||||
const resourceType = this.getResourceType(project, resourceTypeId);
|
||||
|
||||
for (let ext of resourceType?.fileExtensions || []) {
|
||||
ext = `.${ext}`;
|
||||
if (fileName.toLowerCase().endsWith(ext)) {
|
||||
return fileName.slice(0, fileName.length - ext.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
getNameWithExtension(projectId: string, resourceTypeId: string, fileName: string): string {
|
||||
const project = this.get(projectId);
|
||||
|
||||
if (project) {
|
||||
const resourceType = this.getResourceType(project, resourceTypeId);
|
||||
|
||||
if (resourceType?.fileExtensions.length) {
|
||||
return createResourceOfType(resourceType, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
getUserProject(userId: string): ProjectInfo | undefined {
|
||||
return this.get(`u_${userId}`);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { isResourceOfType } from './isResourceOfType';
|
||||
import type { ProjectInfoResourceType } from './ProjectInfoResource';
|
||||
|
||||
export function createResourceOfType(resourceType: ProjectInfoResourceType, name: string): string {
|
||||
if (isResourceOfType(resourceType, name)) {
|
||||
return name;
|
||||
}
|
||||
|
||||
const extension = resourceType.fileExtensions[0];
|
||||
return `${name}${extension ? '.' + extension : ''}`;
|
||||
}
|
||||
|
||||
+1
-1
@@ -46,11 +46,11 @@ import { DATA_CONTEXT_ELEMENTS_TREE, MENU_ELEMENTS_TREE_TOOLS } from '@cloudbeav
|
||||
import { FolderDialog } from '@cloudbeaver/plugin-projects';
|
||||
|
||||
import { NavResourceNodeService } from '../NavResourceNodeService';
|
||||
import { ResourceManagerService } from '../ResourceManagerService';
|
||||
import { DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID } from '../Tree/DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID';
|
||||
import { getResourceKeyFromNodeId } from './getResourceKeyFromNodeId';
|
||||
import { getResourceNodeId } from './getResourceNodeId';
|
||||
import { getRmProjectNodeId } from './getRmProjectNodeId';
|
||||
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
interface ITargetNode {
|
||||
projectId: string;
|
||||
+18
-1
@@ -6,13 +6,20 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, ConfirmationDialogDelete, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
import { ResourceManagerResource } from '@cloudbeaver/core-resource-manager';
|
||||
import { getPathName } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { getResourceNodeId } from './NavNodes/getResourceNodeId';
|
||||
|
||||
@injectable()
|
||||
export class NavResourceNodeService {
|
||||
constructor(private readonly resourceManagerResource: ResourceManagerResource) {}
|
||||
constructor(
|
||||
private readonly resourceManagerResource: ResourceManagerResource,
|
||||
private readonly commonDialogService: CommonDialogService,
|
||||
private readonly localizationService: LocalizationService,
|
||||
) {}
|
||||
|
||||
async move(key: string, newKey: string): Promise<string> {
|
||||
await this.resourceManagerResource.move(key, newKey);
|
||||
@@ -21,6 +28,16 @@ export class NavResourceNodeService {
|
||||
}
|
||||
|
||||
async delete(key: string) {
|
||||
const result = await this.commonDialogService.open(ConfirmationDialogDelete, {
|
||||
title: 'ui_data_delete_confirmation',
|
||||
message: this.localizationService.translate('plugin_navigation_tree_rm_delete_confirmation_message', undefined, { name: getPathName(key) }),
|
||||
confirmActionText: 'ui_delete',
|
||||
});
|
||||
|
||||
if (result === DialogueStateResult.Rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.resourceManagerResource.deleteResource(key);
|
||||
}
|
||||
|
||||
@@ -10,16 +10,20 @@ import { CommonDialogService, DialogueStateResult, RenameDialog } from '@cloudbe
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
import { DATA_CONTEXT_NAV_NODE } from '@cloudbeaver/core-navigation-tree';
|
||||
import { isResourceOfType, ProjectInfoResource, ProjectInfoResourceType } from '@cloudbeaver/core-projects';
|
||||
import { getRmResourceKey, NAV_NODE_TYPE_RM_FOLDER, NAV_NODE_TYPE_RM_RESOURCE } from '@cloudbeaver/core-resource-manager';
|
||||
import { createPath, getPathParent } from '@cloudbeaver/core-utils';
|
||||
import { ACTION_DELETE, ACTION_RENAME, ActionService } from '@cloudbeaver/core-view';
|
||||
import { DATA_CONTEXT_NAV_NODE_ACTIONS } from '@cloudbeaver/plugin-navigation-tree';
|
||||
import { getResourceKeyFromNodeId, NavResourceNodeService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
import { getResourceKeyFromNodeId } from './NavNodes/getResourceKeyFromNodeId';
|
||||
import { NavResourceNodeService } from './NavResourceNodeService';
|
||||
|
||||
@injectable()
|
||||
export class NavTreeRMContextMenuService extends Bootstrap {
|
||||
constructor(
|
||||
private readonly actionService: ActionService,
|
||||
private readonly projectInfoResource: ProjectInfoResource,
|
||||
private readonly commonDialogService: CommonDialogService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly navResourceNodeService: NavResourceNodeService,
|
||||
@@ -53,6 +57,17 @@ export class NavTreeRMContextMenuService extends Bootstrap {
|
||||
}
|
||||
|
||||
const key = getRmResourceKey(resourceKey);
|
||||
const project = this.projectInfoResource.get(key.projectId);
|
||||
|
||||
let resourceType: ProjectInfoResourceType | undefined = undefined;
|
||||
if (project) {
|
||||
for (const type of project.resourceTypes) {
|
||||
if (isResourceOfType(type, node.id)) {
|
||||
resourceType = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case ACTION_RENAME: {
|
||||
@@ -60,6 +75,10 @@ export class NavTreeRMContextMenuService extends Bootstrap {
|
||||
|
||||
const save = async (newName: string) => {
|
||||
if (key.name !== newName && newName.trim().length) {
|
||||
if (resourceType && node.nodeType === NAV_NODE_TYPE_RM_RESOURCE && !node.folder) {
|
||||
newName = this.projectInfoResource.getNameWithExtension(key.projectId, resourceType.id, newName);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.navResourceNodeService.move(resourceKey, createPath(getPathParent(resourceKey), newName));
|
||||
node.name = newName; // fix name flickering in tree
|
||||
|
||||
+1
-1
@@ -25,8 +25,8 @@ import {
|
||||
} from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import { getRmProjectNodeId } from '../../NavNodes/getRmProjectNodeId';
|
||||
import { ResourceManagerService } from '../../ResourceManagerService';
|
||||
import { DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID } from '../DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID';
|
||||
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
const styles = css`
|
||||
TreeNodeControl {
|
||||
+1
-2
@@ -11,8 +11,7 @@ import { NAV_NODE_TYPE_RM_PROJECT, RESOURCES_NODE_PATH } from '@cloudbeaver/core
|
||||
import { resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
import { createPath, isDefined } from '@cloudbeaver/core-utils';
|
||||
import type { IElementsTreeFilter } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import type { ResourceManagerService } from '../../ResourceManagerService';
|
||||
import type { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
export function navigationTreeProjectFilter(
|
||||
projectsNavNodeService: ProjectsNavNodeService,
|
||||
+1
-1
@@ -24,8 +24,8 @@ import {
|
||||
useNode,
|
||||
} from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import type { ResourceManagerService } from '../../ResourceManagerService';
|
||||
import { NavigationNodeProjectControl } from './NavigationNodeProjectControl';
|
||||
import type { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
const nestedStyles = css`
|
||||
TreeNode {
|
||||
+1
-2
@@ -9,8 +9,7 @@ import type { NavNodeInfoResource, ProjectsNavNodeService } from '@cloudbeaver/c
|
||||
import { RESOURCES_NODE_PATH } from '@cloudbeaver/core-resource-manager';
|
||||
import { createPath } from '@cloudbeaver/core-utils';
|
||||
import type { IElementsTreeNodeExpandInfoGetter } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import type { ResourceManagerService } from '../../ResourceManagerService';
|
||||
import type { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
export function navigationTreeResourceExpandStateGetter(
|
||||
navNodeInfoResource: NavNodeInfoResource,
|
||||
+7
-1
@@ -26,7 +26,6 @@ import {
|
||||
validateElementsTreeSettings,
|
||||
} from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import { ResourceManagerService } from '../ResourceManagerService';
|
||||
import { navigationTreeProjectFilter } from './ProjectsRenderer/navigationTreeProjectFilter';
|
||||
import { navigationTreeProjectSearchCompare } from './ProjectsRenderer/navigationTreeProjectSearchCompare';
|
||||
import { navigationTreeProjectsExpandStateGetter } from './ProjectsRenderer/navigationTreeProjectsExpandStateGetter';
|
||||
@@ -35,6 +34,8 @@ import { navigationTreeResourceTypeFilter } from './ProjectsRenderer/navigationT
|
||||
import { ProjectsSettingsPlaceholderElement } from './ProjectsRenderer/ProjectsSettingsForm';
|
||||
import { navigationTreeResourceExpandStateGetter } from './ResourceFolderRenderer/navigationTreeResourceExpandStateGetter';
|
||||
import { ResourceManagerTreeCaptureViewContext } from './ResourceManagerTreeCaptureViewContext';
|
||||
import { transformResourceNodeInfo } from './ResourceRenderer/transformResourceNodeInfo';
|
||||
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
const styles = css`
|
||||
CaptureView {
|
||||
@@ -107,6 +108,10 @@ export const ResourceManagerTree: React.FC<Props> = observer(function ResourceMa
|
||||
() => navigationTreeProjectsExpandStateGetter(navNodeInfoResource, projectsService, projectsNavNodeService),
|
||||
[navNodeInfoResource, projectsService, projectsNavNodeService],
|
||||
);
|
||||
const transformResourceNode = useMemo(
|
||||
() => transformResourceNodeInfo(projectInfoResource, projectsNavNodeService, navNodeInfoResource, resourceTypeId),
|
||||
[projectInfoResource, projectsNavNodeService, navNodeInfoResource, resourceTypeId],
|
||||
);
|
||||
const projectFilter = useMemo(
|
||||
() =>
|
||||
navigationTreeProjectFilter(
|
||||
@@ -134,6 +139,7 @@ export const ResourceManagerTree: React.FC<Props> = observer(function ResourceMa
|
||||
getChildren={navTreeService.getChildren}
|
||||
loadChildren={navTreeService.loadNestedNodes}
|
||||
settings={settings}
|
||||
nodeInfoTransformers={[transformResourceNode]}
|
||||
filters={[resourceTypeFilter, projectFilter]}
|
||||
renderers={[projectsRendererRenderer]}
|
||||
expandStateGetters={[projectsExpandStateGetter, resourceExpandStateGetter]}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 type { NavNodeInfoResource, ProjectsNavNodeService } from '@cloudbeaver/core-navigation-tree';
|
||||
import type { ProjectInfoResource } from '@cloudbeaver/core-projects';
|
||||
import { NAV_NODE_TYPE_RM_RESOURCE } from '@cloudbeaver/core-resource-manager';
|
||||
import type { IElementsTreeCustomNodeInfo } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
export function transformResourceNodeInfo(
|
||||
projectInfoResource: ProjectInfoResource,
|
||||
projectsNavNodeService: ProjectsNavNodeService,
|
||||
navNodeInfoResource: NavNodeInfoResource,
|
||||
resourceTypeId: string | undefined,
|
||||
): IElementsTreeCustomNodeInfo {
|
||||
return function transformResourceNodeInfo(nodeId, info) {
|
||||
const node = navNodeInfoResource.get(nodeId);
|
||||
if (node?.nodeType === NAV_NODE_TYPE_RM_RESOURCE && resourceTypeId) {
|
||||
const project = projectsNavNodeService.getProject(node.id);
|
||||
|
||||
if (project) {
|
||||
return {
|
||||
...info,
|
||||
name: projectInfoResource.getNameWithoutExtension(project.id, resourceTypeId, info.name),
|
||||
};
|
||||
}
|
||||
}
|
||||
return info;
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
import { navigationTreeRMPlugin } from './manifest';
|
||||
export * from './NavNodes/getResourceKeyFromNodeId';
|
||||
export * from './NavNodes/getResourceNodeId';
|
||||
export * from './NavNodes/getRmProjectNodeId';
|
||||
export * from './Tree/ResourceManagerTree';
|
||||
export * from './NavResourceNodeService';
|
||||
|
||||
export { navigationTreeRMPlugin };
|
||||
export default navigationTreeRMPlugin;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default [];
|
||||
export default [['plugin_navigation_tree_rm_delete_confirmation_message', 'You\'re going to delete "{arg:name}" resource. Are you sure?']];
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default [];
|
||||
export default [['plugin_navigation_tree_rm_delete_confirmation_message', 'You\'re going to delete "{arg:name}" resource. Are you sure?']];
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default [];
|
||||
export default [['plugin_navigation_tree_rm_delete_confirmation_message', 'Вы собираетесь удалить "{arg:name}" ресурс. Вы уверены?']];
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default [];
|
||||
export default [['plugin_navigation_tree_rm_delete_confirmation_message', 'You\'re going to delete "{arg:name}" resource. Are you sure?']];
|
||||
|
||||
@@ -9,8 +9,15 @@ import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { LocaleService } from './LocaleService';
|
||||
import { NavTreeRMContextMenuService } from './NavTreeRMContextMenuService';
|
||||
import { ResourceFoldersBootstrap } from './NavNodes/ResourceFoldersBootstrap';
|
||||
import { NavResourceNodeService } from './NavResourceNodeService';
|
||||
|
||||
export const navigationTreeRMPlugin: PluginManifest = {
|
||||
info: { name: 'Navigation Tree RM plugin' },
|
||||
providers: [LocaleService, NavTreeRMContextMenuService],
|
||||
providers: [
|
||||
LocaleService,
|
||||
NavResourceNodeService,
|
||||
ResourceFoldersBootstrap,
|
||||
NavTreeRMContextMenuService
|
||||
],
|
||||
};
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { observer } from 'mobx-react-lite';
|
||||
import React, { forwardRef, useContext } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import {
|
||||
ConnectionImageWithMask,
|
||||
getComputed,
|
||||
Loader,
|
||||
TREE_NODE_STYLES,
|
||||
TreeNodeContext,
|
||||
TreeNodeControl,
|
||||
TreeNodeIcon,
|
||||
TreeNodeName,
|
||||
useMouseContextMenu,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import { EObjectFeature, NavNodeInfoResource, NavTreeResource } from '@cloudbeaver/core-navigation-tree';
|
||||
|
||||
import type { NavTreeControlComponent, NavTreeControlProps } from '../ElementsTree/NavigationNodeComponent';
|
||||
import { NAVIGATION_NODE_CONTROL_STYLES } from '../ElementsTree/NavigationTreeNode/NavigationNode/NAVIGATION_NODE_CONTROL_STYLES';
|
||||
import { NavigationNodeExpand } from '../ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeExpand';
|
||||
import { TreeNodeMenuLoader } from '../ElementsTree/NavigationTreeNode/TreeNodeMenu/TreeNodeMenuLoader';
|
||||
|
||||
export const ConnectionNavNodeControl: NavTreeControlComponent = observer<NavTreeControlProps, HTMLDivElement>(
|
||||
forwardRef(function ConnectionNavNodeControl({ node, nodeInfo, dndElement, dndPlaceholder, className, onClick }, ref) {
|
||||
const mouseContextMenu = useMouseContextMenu();
|
||||
const treeNodeContext = useContext(TreeNodeContext);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const navTreeResource = useService(NavTreeResource);
|
||||
const selected = treeNodeContext.selected;
|
||||
|
||||
const error = getComputed(() => !!navNodeInfoResource.getException(node.id) || !!navTreeResource.getException(node.id));
|
||||
const connected = getComputed(() => node.objectFeatures.includes(EObjectFeature.dataSourceConnected));
|
||||
|
||||
let icon = nodeInfo.icon;
|
||||
const name = nodeInfo.name;
|
||||
const title = nodeInfo.name;
|
||||
|
||||
if (error) {
|
||||
icon = '/icons/error_icon_sm.svg';
|
||||
}
|
||||
|
||||
function handlePortalClick(event: React.MouseEvent<HTMLDivElement>) {
|
||||
EventContext.set(event, EventStopPropagationFlag);
|
||||
treeNodeContext.select();
|
||||
}
|
||||
|
||||
function handleContextMenuOpen(event: React.MouseEvent<HTMLDivElement>) {
|
||||
mouseContextMenu.handleContextMenuOpen(event);
|
||||
treeNodeContext.select();
|
||||
}
|
||||
|
||||
return styled(
|
||||
TREE_NODE_STYLES,
|
||||
NAVIGATION_NODE_CONTROL_STYLES,
|
||||
)(
|
||||
<TreeNodeControl ref={ref} className={className} onClick={onClick} onContextMenu={handleContextMenuOpen} {...use({ dragging: dndElement })}>
|
||||
<NavigationNodeExpand nodeId={node.id} />
|
||||
<TreeNodeIcon {...use({ connected })}>
|
||||
<ConnectionImageWithMask icon={icon} connected={connected} maskId="tree-node-icon" />
|
||||
</TreeNodeIcon>
|
||||
<TreeNodeName title={title}>
|
||||
<Loader suspense inline fullSize>
|
||||
<name-box>{name}</name-box>
|
||||
</Loader>
|
||||
</TreeNodeName>
|
||||
{!dndPlaceholder && (
|
||||
<portal onClick={handlePortalClick}>
|
||||
<TreeNodeMenuLoader mouseContextMenu={mouseContextMenu} node={node} selected={selected} />
|
||||
</portal>
|
||||
)}
|
||||
</TreeNodeControl>,
|
||||
);
|
||||
}),
|
||||
);
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { Translate, TREE_NODE_STYLES, TreeNodeNestedMessage } from '@cloudbeaver/core-blocks';
|
||||
import { NAV_NODE_TYPE_CONNECTION } from '@cloudbeaver/core-connections';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
|
||||
import type { NavigationNodeRendererComponent } from '../ElementsTree/NavigationNodeComponent';
|
||||
import { NavigationNodeRendererLoader } from '../ElementsTree/NavigationTreeNode/NavigationNodeRendererLoader';
|
||||
import type { IElementsTreeCustomRenderer } from '../ElementsTree/useElementsTree';
|
||||
import { ConnectionNavNodeControl } from './ConnectionNavNodeControl';
|
||||
|
||||
export function navTreeConnectionRenderer(navNodeInfoResource: NavNodeInfoResource): IElementsTreeCustomRenderer {
|
||||
return nodeId => {
|
||||
const node = navNodeInfoResource.get(nodeId);
|
||||
|
||||
if (node?.nodeType === NAV_NODE_TYPE_CONNECTION) {
|
||||
return ConnectionRenderer;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
const ConnectionRenderer: NavigationNodeRendererComponent = observer(function ManageableGroup({
|
||||
nodeId,
|
||||
path,
|
||||
dragging,
|
||||
component,
|
||||
className,
|
||||
expanded,
|
||||
}) {
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const node = navNodeInfoResource.get(nodeId);
|
||||
|
||||
if (!node) {
|
||||
return styled(TREE_NODE_STYLES)(
|
||||
<TreeNodeNestedMessage>
|
||||
<Translate token="app_navigationTree_node_not_found" />
|
||||
</TreeNodeNestedMessage>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationNodeRendererLoader
|
||||
node={node}
|
||||
path={path}
|
||||
expanded={expanded}
|
||||
dragging={dragging}
|
||||
className={className}
|
||||
control={ConnectionNavNodeControl}
|
||||
component={component}
|
||||
/>
|
||||
);
|
||||
});
|
||||
+2
@@ -129,6 +129,7 @@ export const ElementsTree = observer<ElementsTreeProps>(function ElementsTree({
|
||||
selectionTree = false,
|
||||
emptyPlaceholder,
|
||||
navNodeFilterCompare,
|
||||
nodeInfoTransformers = [],
|
||||
filters = [],
|
||||
renderers = [],
|
||||
expandStateGetters,
|
||||
@@ -172,6 +173,7 @@ export const ElementsTree = observer<ElementsTreeProps>(function ElementsTree({
|
||||
root,
|
||||
disabled,
|
||||
localState,
|
||||
nodeInfoTransformers,
|
||||
filters: [nameFilter, ...filters, limitFilter],
|
||||
renderers: [...renderers, elementsTreeLimitRenderer],
|
||||
expandStateGetters,
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export interface INavTreeNodeInfo {
|
||||
name: string;
|
||||
tooltip?: string;
|
||||
icon?: string;
|
||||
}
|
||||
+4
@@ -10,6 +10,8 @@ import type React from 'react';
|
||||
import type { NavNode } from '@cloudbeaver/core-navigation-tree';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import type { INavTreeNodeInfo } from './INavTreeNodeInfo';
|
||||
|
||||
export type NavTreeNodeComponent = React.FC<{
|
||||
nodeId: string;
|
||||
path: string[];
|
||||
@@ -20,12 +22,14 @@ export type NavTreeNodeComponent = React.FC<{
|
||||
|
||||
export type NavTreeControlProps = {
|
||||
node: NavNode;
|
||||
nodeInfo: INavTreeNodeInfo;
|
||||
dndElement?: boolean;
|
||||
dndPlaceholder?: boolean;
|
||||
expanded?: boolean;
|
||||
className?: string;
|
||||
style?: ComponentStyle;
|
||||
ref?: React.Ref<HTMLDivElement> | undefined;
|
||||
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
};
|
||||
|
||||
export type NavTreeControlComponent = React.FC<NavTreeControlProps>;
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { css } from 'reshadow';
|
||||
|
||||
export const NAVIGATION_NODE_CONTROL_STYLES = css`
|
||||
TreeNodeControl:hover > portal,
|
||||
TreeNodeControl:global([aria-selected='true']) > portal,
|
||||
portal:focus-within {
|
||||
visibility: visible;
|
||||
}
|
||||
TreeNodeName {
|
||||
height: 100%;
|
||||
max-width: 320px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&[|editing] {
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
portal {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin-left: auto !important;
|
||||
margin-right: 8px !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
name-box {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`;
|
||||
+18
-67
@@ -7,17 +7,15 @@
|
||||
*/
|
||||
import { action, observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { forwardRef, useContext, useDeferredValue } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
import React, { forwardRef, useContext } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import {
|
||||
ConnectionImageWithMask,
|
||||
getComputed,
|
||||
Loader,
|
||||
TREE_NODE_STYLES,
|
||||
TreeNodeContext,
|
||||
TreeNodeControl,
|
||||
TreeNodeExpand,
|
||||
TreeNodeIcon,
|
||||
TreeNodeName,
|
||||
useMouseContextMenu,
|
||||
@@ -26,53 +24,15 @@ import {
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import { EObjectFeature, type INodeActions, NavNodeInfoResource, NavTreeResource } from '@cloudbeaver/core-navigation-tree';
|
||||
import { type INodeActions, NavNodeInfoResource, NavTreeResource } from '@cloudbeaver/core-navigation-tree';
|
||||
|
||||
import { ElementsTreeContext } from '../../ElementsTreeContext';
|
||||
import type { NavTreeControlComponent, NavTreeControlProps } from '../../NavigationNodeComponent';
|
||||
import { TreeNodeMenuLoader } from '../TreeNodeMenu/TreeNodeMenuLoader';
|
||||
import { DATA_ATTRIBUTE_NODE_EDITING } from './DATA_ATTRIBUTE_NODE_EDITING';
|
||||
import { NAVIGATION_NODE_CONTROL_STYLES } from './NAVIGATION_NODE_CONTROL_STYLES';
|
||||
import { NavigationNodeExpand } from './NavigationNodeExpand';
|
||||
import { NavigationNodeEditorLoader } from './NavigationNodeLoaders';
|
||||
|
||||
const styles = css`
|
||||
TreeNodeControl {
|
||||
transition: opacity 0.3s ease;
|
||||
opacity: 1;
|
||||
|
||||
&[|outdated] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
TreeNodeControl:hover > portal,
|
||||
TreeNodeControl:global([aria-selected='true']) > portal,
|
||||
portal:focus-within {
|
||||
visibility: visible;
|
||||
}
|
||||
TreeNodeName {
|
||||
height: 100%;
|
||||
max-width: 320px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&[|editing] {
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
portal {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin-left: auto !important;
|
||||
margin-right: 8px !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
name-box {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`;
|
||||
|
||||
interface IEditingState {
|
||||
saving: boolean;
|
||||
editing: boolean;
|
||||
@@ -88,17 +48,13 @@ interface IEditingState {
|
||||
}
|
||||
|
||||
export const NavigationNodeControl: NavTreeControlComponent = observer<NavTreeControlProps, HTMLDivElement>(
|
||||
forwardRef(function NavigationNodeControl({ node, dndElement, dndPlaceholder }, ref) {
|
||||
forwardRef(function NavigationNodeControl({ node, nodeInfo, dndElement, dndPlaceholder, className, onClick }, ref) {
|
||||
const mouseContextMenu = useMouseContextMenu();
|
||||
const treeNodeContext = useContext(TreeNodeContext);
|
||||
const treeContext = useContext(ElementsTreeContext);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const navTreeResource = useService(NavTreeResource);
|
||||
const outdated = getComputed(() => navNodeInfoResource.isOutdated(node.id) && !treeNodeContext.loading);
|
||||
const error = getComputed(() => !!navNodeInfoResource.getException(node.id) || !!navTreeResource.getException(node.id));
|
||||
const connected = getComputed(() => node.objectFeatures.includes(EObjectFeature.dataSourceConnected));
|
||||
const selected = treeNodeContext.selected;
|
||||
|
||||
const editingState = useObservableRef<IEditingState>(
|
||||
() => ({
|
||||
saving: false,
|
||||
@@ -151,7 +107,9 @@ export const NavigationNodeControl: NavTreeControlComponent = observer<NavTreeCo
|
||||
rename: editingState.startEditing,
|
||||
});
|
||||
|
||||
let icon = node.icon;
|
||||
let icon = nodeInfo.icon;
|
||||
const name = nodeInfo.name;
|
||||
const title = nodeInfo.tooltip;
|
||||
|
||||
if (error) {
|
||||
icon = '/icons/error_icon_sm.svg';
|
||||
@@ -162,42 +120,35 @@ export const NavigationNodeControl: NavTreeControlComponent = observer<NavTreeCo
|
||||
treeNodeContext.select();
|
||||
}
|
||||
|
||||
function onClickHandler(event: React.MouseEvent<HTMLDivElement>) {
|
||||
treeNodeContext.select(event.ctrlKey || event.metaKey);
|
||||
}
|
||||
|
||||
function handleContextMenuOpen(event: React.MouseEvent<HTMLDivElement>) {
|
||||
mouseContextMenu.handleContextMenuOpen(event);
|
||||
treeNodeContext.select();
|
||||
}
|
||||
|
||||
const expandable = useDeferredValue(getComputed(() => treeContext?.tree.isNodeExpandable(node.id) ?? true));
|
||||
const filterActive = useDeferredValue(getComputed(() => treeContext?.tree.filtering));
|
||||
const { editing, saving } = editingState;
|
||||
|
||||
const attributes = { [DATA_ATTRIBUTE_NODE_EDITING]: editing };
|
||||
|
||||
return styled(
|
||||
TREE_NODE_STYLES,
|
||||
styles,
|
||||
NAVIGATION_NODE_CONTROL_STYLES,
|
||||
)(
|
||||
<TreeNodeControl
|
||||
ref={ref}
|
||||
{...attributes}
|
||||
onClick={onClickHandler}
|
||||
className={className}
|
||||
onClick={onClick}
|
||||
onContextMenu={handleContextMenuOpen}
|
||||
{...use({ outdated, editing, dragging: dndElement })}
|
||||
{...use({ editing, dragging: dndElement })}
|
||||
>
|
||||
{expandable && <TreeNodeExpand filterActive={filterActive} />}
|
||||
<TreeNodeIcon {...use({ connected })}>
|
||||
<ConnectionImageWithMask icon={icon} connected={connected} maskId="tree-node-icon" />
|
||||
</TreeNodeIcon>
|
||||
<TreeNodeName title={node.name} {...use({ editing })}>
|
||||
<NavigationNodeExpand nodeId={node.id} />
|
||||
<TreeNodeIcon icon={icon} />
|
||||
<TreeNodeName title={title} {...use({ editing })}>
|
||||
<Loader suspense inline fullSize>
|
||||
{editing ? (
|
||||
<NavigationNodeEditorLoader node={node} disabled={saving} onSave={editingState.save} onClose={editingState.cancel} />
|
||||
<NavigationNodeEditorLoader name={name} disabled={saving} onSave={editingState.save} onClose={editingState.cancel} />
|
||||
) : (
|
||||
<name-box>{node.name}</name-box>
|
||||
<name-box>{name}</name-box>
|
||||
)}
|
||||
</Loader>
|
||||
</TreeNodeName>
|
||||
|
||||
+6
-5
@@ -10,28 +10,29 @@ import { useState } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import type { NavNode } from '@cloudbeaver/core-navigation-tree';
|
||||
import { InlineEditor } from '@cloudbeaver/core-ui';
|
||||
|
||||
const styles = css`
|
||||
InlineEditor {
|
||||
height: 22px;
|
||||
|
||||
& input {
|
||||
& input,
|
||||
& input[disabled],
|
||||
& input[readonly] {
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
node: NavNode;
|
||||
name: string;
|
||||
disabled?: boolean;
|
||||
onSave: (name: string) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const NavigationNodeEditor = observer<Props>(function NavigationNodeEditor({ node, disabled, onSave, onClose }) {
|
||||
const [name, setName] = useState(node.name || '');
|
||||
export const NavigationNodeEditor = observer<Props>(function NavigationNodeEditor({ name: initialName, disabled, onSave, onClose }) {
|
||||
const [name, setName] = useState(initialName);
|
||||
|
||||
function save() {
|
||||
onSave(name);
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { observer } from 'mobx-react-lite';
|
||||
import { useContext, useDeferredValue } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { getComputed, TREE_NODE_STYLES, TreeNodeExpand } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import { ElementsTreeContext } from '../../ElementsTreeContext';
|
||||
|
||||
interface Props {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
export const NavigationNodeExpand = observer<Props>(function NavigationNodeExpand({ nodeId }) {
|
||||
const treeContext = useContext(ElementsTreeContext);
|
||||
|
||||
const expandable = useDeferredValue(getComputed(() => treeContext?.tree.isNodeExpandable(nodeId) ?? true));
|
||||
const filterActive = useDeferredValue(getComputed(() => treeContext?.tree.filtering ?? false));
|
||||
|
||||
if (!expandable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(TREE_NODE_STYLES)(<TreeNodeExpand filterActive={filterActive} />);
|
||||
});
|
||||
+29
-4
@@ -7,13 +7,16 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { forwardRef, useContext, useDeferredValue, useEffect, useRef, useState } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import type { NavNode } from '@cloudbeaver/core-navigation-tree';
|
||||
import { getComputed, TreeNodeContext, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { type NavNode, NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { ElementsTreeContext } from '../ElementsTreeContext';
|
||||
import type { NavTreeControlComponent } from '../NavigationNodeComponent';
|
||||
import { transformNodeInfo } from '../transformNodeInfo';
|
||||
import { NavigationNodeControlLoader } from './NavigationNode/NavigationNodeLoaders';
|
||||
import type { INavigationNode } from './useNavigationNode';
|
||||
|
||||
@@ -25,6 +28,17 @@ interface Props {
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
const styles = css`
|
||||
Control {
|
||||
transition: opacity 0.3s ease;
|
||||
opacity: 1;
|
||||
|
||||
&[|outdated] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const NavigationNodeControlRenderer = observer<Props, HTMLDivElement>(
|
||||
forwardRef(function NavigationNodeControlRenderer({ node, navNode, dragging, control: externalControl, style }, ref) {
|
||||
const elementRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -32,7 +46,8 @@ export const NavigationNodeControlRenderer = observer<Props, HTMLDivElement>(
|
||||
const contextRef = useObjectRef({
|
||||
context: useContext(ElementsTreeContext),
|
||||
});
|
||||
const Control = navNode.control || externalControl || NavigationNodeControlLoader;
|
||||
const treeNodeContext = useContext(TreeNodeContext);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const observer = useRef<IntersectionObserver | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -77,6 +92,16 @@ export const NavigationNodeControlRenderer = observer<Props, HTMLDivElement>(
|
||||
return <div ref={setRef} style={{ height: `${size}px` }} />;
|
||||
}
|
||||
|
||||
return <Control ref={setRef} dndElement={dragging} style={style} node={node} />;
|
||||
const Control = navNode.control || externalControl || NavigationNodeControlLoader;
|
||||
const outdated = getComputed(() => navNodeInfoResource.isOutdated(node.id) && !treeNodeContext.loading);
|
||||
const nodeInfo = transformNodeInfo(node, contextRef.context?.tree.nodeInfoTransformers ?? []);
|
||||
|
||||
function onClickHandler(event: React.MouseEvent<HTMLDivElement>) {
|
||||
treeNodeContext.select(event.ctrlKey || event.metaKey);
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<Control ref={setRef} {...use({ outdated })} nodeInfo={nodeInfo} dndElement={dragging} style={style} node={node} onClick={onClickHandler} />,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
+3
-1
@@ -13,6 +13,7 @@ import { getComputed, TreeNode, useObjectRef, useStyles } from '@cloudbeaver/cor
|
||||
|
||||
import { ElementsTreeContext } from '../ElementsTreeContext';
|
||||
import type { NavigationNodeComponent } from '../NavigationNodeComponent';
|
||||
import { transformNodeInfo } from '../transformNodeInfo';
|
||||
import { NavigationNodeControlLoader } from './NavigationNode/NavigationNodeLoaders';
|
||||
|
||||
export const NavigationNodeDragged: NavigationNodeComponent = observer(function NavigationNode({ node, className, control: externalControl, style }) {
|
||||
@@ -22,11 +23,12 @@ export const NavigationNodeDragged: NavigationNodeComponent = observer(function
|
||||
const control = getComputed(() => contextRef.context?.control);
|
||||
|
||||
const Control = control || externalControl || NavigationNodeControlLoader;
|
||||
const nodeInfo = transformNodeInfo(node, contextRef.context?.tree.nodeInfoTransformers ?? []);
|
||||
|
||||
return styled(useStyles(style))(
|
||||
<TreeNode externalExpanded={false} className={className} leaf>
|
||||
{/* <DNDPreview data={dndData} src="/icons/empty.svg" /> */}
|
||||
<Control node={node} dndPlaceholder />
|
||||
<Control nodeInfo={nodeInfo} node={node} dndPlaceholder />
|
||||
</TreeNode>,
|
||||
);
|
||||
});
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 type { NavNode } from '@cloudbeaver/core-navigation-tree';
|
||||
|
||||
import type { INavTreeNodeInfo } from './INavTreeNodeInfo';
|
||||
import type { IElementsTreeCustomNodeInfo } from './useElementsTree';
|
||||
|
||||
export function transformNodeInfo(node: NavNode, transformers: IElementsTreeCustomNodeInfo[]): INavTreeNodeInfo {
|
||||
return transformers.reduce((info, transformer) => transformer(node.id, info), {
|
||||
name: node.name,
|
||||
tooltip: node.name,
|
||||
icon: node.icon,
|
||||
} as INavTreeNodeInfo);
|
||||
}
|
||||
+7
@@ -20,9 +20,11 @@ import type { IDNDData } from '@cloudbeaver/core-ui';
|
||||
import { ILoadableState, MetadataMap, throttle } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { IElementsTreeAction } from './IElementsTreeAction';
|
||||
import type { INavTreeNodeInfo } from './INavTreeNodeInfo';
|
||||
import type { NavigationNodeRendererComponent } from './NavigationNodeComponent';
|
||||
|
||||
export type IElementsTreeCustomRenderer = (nodeId: string) => NavigationNodeRendererComponent | undefined;
|
||||
export type IElementsTreeCustomNodeInfo = (nodeId: string, info: INavTreeNodeInfo) => INavTreeNodeInfo;
|
||||
|
||||
export interface IElementsTreeNodeExpandedInfo {
|
||||
expanded: boolean;
|
||||
@@ -66,6 +68,7 @@ export interface IElementsTreeOptions {
|
||||
disabled?: boolean;
|
||||
filters?: IElementsTreeFilter[];
|
||||
renderers?: IElementsTreeCustomRenderer[];
|
||||
nodeInfoTransformers?: IElementsTreeCustomNodeInfo[];
|
||||
expandStateGetters?: IElementsTreeNodeExpandInfoGetter[];
|
||||
localState?: MetadataMap<string, ITreeNodeState>;
|
||||
getChildren: (id: string) => string[] | undefined;
|
||||
@@ -97,6 +100,7 @@ export interface IElementsTree extends ILoadableState {
|
||||
loading: boolean;
|
||||
disabled: boolean;
|
||||
activeDnDData: IDNDData[];
|
||||
nodeInfoTransformers: IElementsTreeCustomNodeInfo[];
|
||||
renderers: IElementsTreeCustomRenderer[];
|
||||
state: MetadataMap<string, ITreeNodeState>;
|
||||
userData: IElementsTreeUserState;
|
||||
@@ -141,6 +145,7 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
);
|
||||
|
||||
options = useObjectRef(options);
|
||||
options.nodeInfoTransformers = useMemo(() => options.nodeInfoTransformers || [], [...(options.nodeInfoTransformers || [])]);
|
||||
options.renderers = useMemo(() => options.renderers || [], [...(options.renderers || [])]);
|
||||
options.filters = useMemo(() => options.filters || [], [...(options.filters || [])]);
|
||||
options.expandStateGetters = useMemo(() => options.expandStateGetters || [], [...(options.expandStateGetters || [])]);
|
||||
@@ -627,6 +632,7 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
filtering: computed,
|
||||
loading: observable.ref,
|
||||
renderers: observable.ref,
|
||||
nodeInfoTransformers: observable.ref,
|
||||
baseRoot: observable.ref,
|
||||
collapse: action.bound,
|
||||
userData: observable.ref,
|
||||
@@ -639,6 +645,7 @@ export function useElementsTree(options: IOptions): IElementsTree {
|
||||
settings: options.settings,
|
||||
baseRoot: options.baseRoot,
|
||||
renderers: options.renderers,
|
||||
nodeInfoTransformers: options.nodeInfoTransformers,
|
||||
userData,
|
||||
},
|
||||
['isLoading', 'isLoaded'],
|
||||
|
||||
@@ -9,7 +9,6 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { AppAuthService } from '@cloudbeaver/core-authentication';
|
||||
import { Translate, useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NavNodeInfoResource, NavTreeResource, ProjectsNavNodeService, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree';
|
||||
@@ -19,6 +18,7 @@ import { CaptureView } from '@cloudbeaver/core-view';
|
||||
import { NavNodeViewService } from '../NodesManager/NavNodeView/NavNodeViewService';
|
||||
import { navigationTreeConnectionGroupFilter } from './ConnectionGroup/navigationTreeConnectionGroupFilter';
|
||||
import { navigationTreeConnectionGroupRenderer } from './ConnectionGroup/navigationTreeConnectionGroupRenderer';
|
||||
import { navTreeConnectionRenderer } from './ConnectionsRenderer/navTreeConnectionRenderer';
|
||||
import { ElementsTree } from './ElementsTree/ElementsTree';
|
||||
import {
|
||||
createElementsTreeSettings,
|
||||
@@ -98,6 +98,7 @@ export const NavigationTree = observer(function NavigationTree() {
|
||||
);
|
||||
|
||||
const duplicateFilter = useMemo(() => navigationTreeDuplicateFilter(navNodeViewService), [navNodeViewService]);
|
||||
const connectionRenderer = useMemo(() => navTreeConnectionRenderer(navNodeInfoResource), [navNodeInfoResource]);
|
||||
const projectsRendererRenderer = useMemo(() => navigationTreeProjectsRendererRenderer(navNodeInfoResource), [navNodeInfoResource]);
|
||||
const projectsExpandStateGetter = useMemo(
|
||||
() => navigationTreeProjectsExpandStateGetter(navNodeInfoResource, projectsService, projectsNavNodeService),
|
||||
@@ -116,7 +117,7 @@ export const NavigationTree = observer(function NavigationTree() {
|
||||
root={root}
|
||||
localState={navTreeService.treeState}
|
||||
filters={[duplicateFilter, connectionGroupFilter, projectFilter]}
|
||||
renderers={[projectsRendererRenderer, navigationTreeConnectionGroupRenderer]}
|
||||
renderers={[projectsRendererRenderer, navigationTreeConnectionGroupRenderer, connectionRenderer]}
|
||||
navNodeFilterCompare={navigationTreeProjectSearchCompare}
|
||||
expandStateGetters={[projectsExpandStateGetter]}
|
||||
settingsElements={settingsElements}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"validate-dependencies": "core-cli-validate-dependencies"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudbeaver/plugin-navigation-tree-rm": "~0.1.0",
|
||||
"@cloudbeaver/plugin-resource-manager": "~0.1.0",
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
|
||||
@@ -10,7 +10,7 @@ import styled, { css } from 'reshadow';
|
||||
|
||||
import { useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui';
|
||||
import { ResourceManagerTree } from '@cloudbeaver/plugin-resource-manager';
|
||||
import { ResourceManagerTree } from '@cloudbeaver/plugin-navigation-tree-rm';
|
||||
|
||||
import { SCRIPTS_TYPE_ID } from './SCRIPTS_TYPE_ID';
|
||||
|
||||
|
||||
@@ -3,10 +3,5 @@ import { resourceManagerPlugin } from './manifest';
|
||||
export default resourceManagerPlugin;
|
||||
|
||||
export * from './ResourceManagerSettingsService';
|
||||
export * from './NavResourceNodeService';
|
||||
export * from './RESOURCE_NAME_REGEX';
|
||||
export * from './ResourceManagerService';
|
||||
export * from './NavNodes/getResourceKeyFromNodeId';
|
||||
export * from './NavNodes/getResourceNodeId';
|
||||
export * from './NavNodes/getRmProjectNodeId';
|
||||
export * from './Tree/ResourceManagerTree';
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { LocaleService } from './LocaleService';
|
||||
import { ResourceFoldersBootstrap } from './NavNodes/ResourceFoldersBootstrap';
|
||||
import { NavResourceNodeService } from './NavResourceNodeService';
|
||||
import { PluginBootstrap } from './PluginBootstrap';
|
||||
import { ResourceManagerService } from './ResourceManagerService';
|
||||
import { ResourceManagerSettingsService } from './ResourceManagerSettingsService';
|
||||
@@ -23,8 +21,6 @@ export const resourceManagerPlugin: PluginManifest = {
|
||||
PluginBootstrap,
|
||||
LocaleService,
|
||||
ResourceManagerService,
|
||||
NavResourceNodeService,
|
||||
ResourceFoldersBootstrap,
|
||||
// ResourceProjectsResource,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@cloudbeaver/plugin-resource-manager": "~0.1.0",
|
||||
"@cloudbeaver/plugin-sql-editor": "~0.1.0",
|
||||
"@cloudbeaver/plugin-sql-editor-navigation-tab": "~0.1.0",
|
||||
"@cloudbeaver/plugin-navigation-tree-rm": "~0.1.0",
|
||||
"@cloudbeaver/core-connections": "~0.1.0",
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-dialogs": "~0.1.0",
|
||||
|
||||
@@ -10,13 +10,14 @@ import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dial
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
||||
import { type INodeNavigationData, NavNodeInfoResource, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
|
||||
import { createResourceOfType, isResourceOfType, ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
|
||||
import { isResourceOfType, ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
|
||||
import { NAV_NODE_TYPE_RM_RESOURCE, ResourceManagerResource, RESOURCES_NODE_PATH } from '@cloudbeaver/core-resource-manager';
|
||||
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
|
||||
import { createPath, getPathName } from '@cloudbeaver/core-utils';
|
||||
import { ACTION_SAVE, ActionService, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view';
|
||||
import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
|
||||
import { getResourceKeyFromNodeId, NavResourceNodeService, ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
import { getResourceKeyFromNodeId, NavResourceNodeService } from '@cloudbeaver/plugin-navigation-tree-rm';
|
||||
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
import { ResourceManagerScriptsService, SaveScriptDialog, SCRIPTS_TYPE_ID } from '@cloudbeaver/plugin-resource-manager-scripts';
|
||||
import {
|
||||
DATA_CONTEXT_SQL_EDITOR_STATE,
|
||||
@@ -123,12 +124,7 @@ export class PluginBootstrap extends Bootstrap {
|
||||
throw new Error('Project not found');
|
||||
}
|
||||
|
||||
const resourceType = this.projectInfoResource.getResourceType(project, SCRIPTS_TYPE_ID);
|
||||
if (!resourceType) {
|
||||
throw new Error('Script Resource type not found');
|
||||
}
|
||||
|
||||
const scriptName = createResourceOfType(resourceType, result.name.trim());
|
||||
const scriptName = this.projectInfoResource.getNameWithExtension(projectId, SCRIPTS_TYPE_ID, result.name.trim());
|
||||
const scriptsRootFolder = this.resourceManagerScriptsService.getRootFolder(project);
|
||||
const folderResourceKey = getResourceKeyFromNodeId(createPath(RESOURCES_NODE_PATH, projectId, scriptsRootFolder));
|
||||
|
||||
|
||||
+11
-11
@@ -14,9 +14,11 @@ import {
|
||||
NOT_INITIALIZED_CONTEXT_ID,
|
||||
} from '@cloudbeaver/core-connections';
|
||||
import { TaskScheduler } from '@cloudbeaver/core-executor';
|
||||
import type { ProjectInfoResource } from '@cloudbeaver/core-projects';
|
||||
import { getRmResourceKey, ResourceManagerResource } from '@cloudbeaver/core-resource-manager';
|
||||
import { isResourceAlias, ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
|
||||
import { createPath, debounce, getPathName, getPathParent, isArraysEqual, isObjectsEqual, isValuesEqual } from '@cloudbeaver/core-utils';
|
||||
import { SCRIPTS_TYPE_ID } from '@cloudbeaver/plugin-resource-manager-scripts';
|
||||
import { BaseSqlDataSource, ESqlDataSourceFeatures } from '@cloudbeaver/plugin-sql-editor';
|
||||
|
||||
import type { IResourceSqlDataSourceState } from './IResourceSqlDataSourceState';
|
||||
@@ -43,13 +45,13 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
static key = 'resource';
|
||||
|
||||
get name(): string | null {
|
||||
if (!this.resourceKey) {
|
||||
if (!this.resourceKey || !this.projectId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const resource = this.resourceManagerResource.get(this.resourceKey);
|
||||
|
||||
return resource?.name ?? getPathName(this.resourceKey);
|
||||
return this.projectInfoResource.getNameWithoutExtension(this.projectId, SCRIPTS_TYPE_ID, resource?.name ?? getPathName(this.resourceKey));
|
||||
}
|
||||
|
||||
get script(): string {
|
||||
@@ -101,6 +103,7 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
private resourceUseKeyId: string | null;
|
||||
|
||||
constructor(
|
||||
private readonly projectInfoResource: ProjectInfoResource,
|
||||
private readonly connectionInfoResource: ConnectionInfoResource,
|
||||
private readonly resourceManagerResource: ResourceManagerResource,
|
||||
state: IResourceSqlDataSourceState,
|
||||
@@ -269,17 +272,14 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
await this.write();
|
||||
|
||||
await this.scheduler.schedule(undefined, async () => {
|
||||
if (!this.actions || !this.resourceKey || !this.saved || !name?.trim()) {
|
||||
if (!this.actions || !this.resourceKey || !this.projectId || !this.saved || !name?.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: use createResourceOfType instead
|
||||
if (!name.toLowerCase().endsWith('.sql')) {
|
||||
name += '.sql';
|
||||
}
|
||||
name = this.projectInfoResource.getNameWithExtension(this.projectId, SCRIPTS_TYPE_ID, name);
|
||||
|
||||
this.lastAction = this.rename.bind(this, name);
|
||||
this.message = 'Renaming script...';
|
||||
this.message = 'plugin_sql_editor_navigation_tab_script_state_renaming';
|
||||
|
||||
try {
|
||||
this.exception = null;
|
||||
@@ -299,7 +299,7 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
}
|
||||
|
||||
this.lastAction = this.read.bind(this);
|
||||
this.message = 'Reading script...';
|
||||
this.message = 'plugin_sql_editor_navigation_tab_script_state_reading';
|
||||
|
||||
try {
|
||||
this.exception = null;
|
||||
@@ -334,7 +334,7 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
}
|
||||
|
||||
this.lastAction = this.write.bind(this);
|
||||
this.message = 'Saving script...';
|
||||
this.message = 'plugin_sql_editor_navigation_tab_script_state_saving';
|
||||
|
||||
try {
|
||||
this.exception = null;
|
||||
@@ -360,7 +360,7 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
}
|
||||
|
||||
this.lastAction = this.saveProperties.bind(this);
|
||||
this.message = 'Update info...';
|
||||
this.message = 'plugin_sql_editor_navigation_tab_script_state_updating';
|
||||
|
||||
try {
|
||||
this.exception = null;
|
||||
|
||||
+27
-12
@@ -18,7 +18,8 @@ import { CachedMapAllKey, resourceKeyList, ResourceKeySimple, ResourceKeyUtils }
|
||||
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
|
||||
import { throttle } from '@cloudbeaver/core-utils';
|
||||
import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
|
||||
import { NavResourceNodeService, ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
import { NavResourceNodeService } from '@cloudbeaver/plugin-navigation-tree-rm';
|
||||
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
import { ResourceManagerScriptsService } from '@cloudbeaver/plugin-resource-manager-scripts';
|
||||
import { getSqlEditorName, SqlDataSourceService } from '@cloudbeaver/plugin-sql-editor';
|
||||
import { SqlEditorTabService } from '@cloudbeaver/plugin-sql-editor-navigation-tab';
|
||||
@@ -90,7 +91,12 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
||||
this.sqlDataSourceService.register({
|
||||
key: ResourceSqlDataSource.key,
|
||||
getDataSource: (editorId, options) => {
|
||||
const dataSource = new ResourceSqlDataSource(this.connectionInfoResource, this.resourceManagerResource, this.createState(editorId));
|
||||
const dataSource = new ResourceSqlDataSource(
|
||||
this.projectInfoResource,
|
||||
this.connectionInfoResource,
|
||||
this.resourceManagerResource,
|
||||
this.createState(editorId),
|
||||
);
|
||||
|
||||
if (options?.executionContext) {
|
||||
dataSource.setExecutionContext(options.executionContext);
|
||||
@@ -204,12 +210,15 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataSource = this.sqlDataSourceService.dataSources
|
||||
const dataSources = this.sqlDataSourceService.dataSources
|
||||
.filter(([, dataSource]) => dataSource instanceof ResourceSqlDataSource)
|
||||
.map(([, dataSource]) => dataSource as ResourceSqlDataSource)
|
||||
.find(ds => ds.resourceKey && ds.resourceKey === data.from);
|
||||
.map(([, dataSource]) => dataSource as ResourceSqlDataSource);
|
||||
|
||||
dataSource?.setResourceKey(data.to);
|
||||
for (const dataSource of dataSources) {
|
||||
if (dataSource.resourceKey && dataSource.resourceKey.startsWith(data.from)) {
|
||||
dataSource?.setResourceKey(data.to + dataSource.resourceKey.slice(data.from.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private resourceDeleteHandler(keyObj: ResourceKeySimple<string>) {
|
||||
@@ -219,14 +228,20 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
||||
|
||||
const tabs: string[] = [];
|
||||
|
||||
const dataSources = this.sqlDataSourceService.dataSources
|
||||
.filter(([, dataSource]) => dataSource instanceof ResourceSqlDataSource)
|
||||
.map(([, dataSource]) => dataSource as ResourceSqlDataSource);
|
||||
|
||||
ResourceKeyUtils.forEach(keyObj, key => {
|
||||
const tab = this.sqlEditorTabResourceService.getResourceTab(key);
|
||||
for (const dataSource of dataSources) {
|
||||
if (dataSource.resourceKey && dataSource.resourceKey.startsWith(key)) {
|
||||
const tab = this.sqlEditorTabResourceService.getResourceTab(dataSource.resourceKey);
|
||||
|
||||
if (tab) {
|
||||
const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId) as ResourceSqlDataSource;
|
||||
|
||||
dataSource?.setResourceKey(undefined); // prevent deleted resource refresh
|
||||
tabs.push(tab.id);
|
||||
if (tab) {
|
||||
tabs.push(tab.id);
|
||||
}
|
||||
dataSource.setResourceKey(undefined); // prevent deleted resource refresh
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export default [
|
||||
['plugin_sql_editor_navigation_tab_script_state_renaming', 'Renaming script...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_reading', 'Reading script...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_saving', 'Saving script...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_updating', 'Updating script info...'],
|
||||
['plugin_sql_editor_navigation_tab_resource_save_script_success', 'Script successfully saved'],
|
||||
['plugin_sql_editor_navigation_tab_resource_open_script_error', 'Failed to open the script'],
|
||||
['plugin_sql_editor_navigation_tab_resource_save_script_error', 'Error occurred while trying to save the script'],
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export default [
|
||||
['plugin_sql_editor_navigation_tab_script_state_renaming', 'Renaming script...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_reading', 'Reading script...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_saving', 'Saving script...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_updating', 'Updating script info...'],
|
||||
['plugin_sql_editor_navigation_tab_resource_save_script_success', 'Script successfully saved'],
|
||||
['plugin_sql_editor_navigation_tab_resource_open_script_error', 'Failed to open the script'],
|
||||
['plugin_sql_editor_navigation_tab_resource_save_script_error', 'Error occurred while trying to save the script'],
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export default [
|
||||
['plugin_sql_editor_navigation_tab_script_state_renaming', 'Переименовывание скрипта...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_reading', 'Чтение скрипта...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_saving', 'Сохранение скрипта...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_updating', 'Обновление информации о скрипте...'],
|
||||
['plugin_sql_editor_navigation_tab_resource_save_script_success', 'Скрипт успешно сохранен'],
|
||||
['plugin_sql_editor_navigation_tab_resource_open_script_error', 'Не удалось открыть скрипт'],
|
||||
['plugin_sql_editor_navigation_tab_resource_save_script_error', 'Возникла ошибка при попытки сохранить скрипт'],
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export default [
|
||||
['plugin_sql_editor_navigation_tab_script_state_renaming', 'Renaming script...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_reading', 'Reading script...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_saving', 'Saving script...'],
|
||||
['plugin_sql_editor_navigation_tab_script_state_updating', 'Updating script info...'],
|
||||
['plugin_sql_editor_navigation_tab_resource_save_script_success', '脚本保存成功'],
|
||||
['plugin_sql_editor_navigation_tab_resource_open_script_error', '打开脚本失败'],
|
||||
['plugin_sql_editor_navigation_tab_resource_save_script_error', '打开脚本出错'],
|
||||
|
||||
@@ -144,7 +144,7 @@ export class SqlEditorBootstrap extends Bootstrap {
|
||||
const result = await this.commonDialogService.open(RenameDialog, {
|
||||
value: regexp?.[1] ?? name,
|
||||
objectName: name,
|
||||
icon: '/icons/sql_script_m.svg',
|
||||
icon: dataSource.icon,
|
||||
validation: name =>
|
||||
!this.sqlEditorTabService.sqlEditorTabs.some(
|
||||
tab => tab.handlerState.order !== state.order && this.sqlEditorService.getName(tab.handlerState) === name.trim(),
|
||||
|
||||
@@ -42,16 +42,18 @@ export abstract class BaseSqlDataSource implements ISqlDataSource {
|
||||
return [ESqlDataSourceFeatures.script, ESqlDataSourceFeatures.query, ESqlDataSourceFeatures.executable];
|
||||
}
|
||||
|
||||
protected saved: boolean;
|
||||
readonly icon: string;
|
||||
readonly history: ISqlDataSourceHistory;
|
||||
readonly onUpdate: ISyncExecutor;
|
||||
readonly onSetScript: ISyncExecutor<ISetScriptData>;
|
||||
readonly onDatabaseModelUpdate: ISyncExecutor<IDatabaseDataModel<IDataQueryOptions, IDatabaseResultSet>[]>;
|
||||
|
||||
protected saved: boolean;
|
||||
protected outdated: boolean;
|
||||
protected editing: boolean;
|
||||
|
||||
constructor() {
|
||||
constructor(icon = '/icons/sql_script_m.svg') {
|
||||
this.icon = icon;
|
||||
this.databaseModels = [];
|
||||
this.exception = undefined;
|
||||
this.message = undefined;
|
||||
|
||||
Reference in New Issue
Block a user