From d4739a0dba21be7e2a6ce4fde76dbf90ff23f4c2 Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 27 Jun 2023 13:46:55 +0400 Subject: [PATCH] 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> --- .../src/NavTree/NAV_NODE_TYPE_CONNECTION.ts | 9 ++ webapp/packages/core-connections/src/index.ts | 1 + .../CommonDialog/ConfirmationDialogDelete.tsx | 9 +- .../core-projects/src/ProjectInfoResource.ts | 33 +++++++ .../core-projects/src/createResourceOfType.ts | 5 ++ .../src/NavNodes/ResourceFoldersBootstrap.ts | 2 +- .../src/NavNodes/getResourceKeyFromNodeId.ts | 0 .../src/NavNodes/getResourceNodeId.ts | 0 .../src/NavNodes/getRmProjectNodeId.ts | 0 .../src/NavResourceNodeService.ts | 19 ++++- .../src/NavTreeRMContextMenuService.ts | 21 ++++- ..._RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID.ts | 0 .../NavigationNodeProjectControl.tsx | 2 +- .../ProjectsRenderer/ProjectsSettingsForm.tsx | 0 .../navigationTreeProjectFilter.ts | 3 +- .../navigationTreeProjectSearchCompare.ts | 0 ...navigationTreeProjectsExpandStateGetter.ts | 0 ...navigationTreeProjectsRendererRenderer.tsx | 2 +- .../navigationTreeResourceTypeFilter.ts | 0 ...navigationTreeResourceExpandStateGetter.ts | 3 +- .../src/Tree/ResourceManagerTree.tsx | 8 +- .../ResourceManagerTreeCaptureViewContext.tsx | 0 .../transformResourceNodeInfo.ts | 33 +++++++ .../plugin-navigation-tree-rm/src/index.ts | 5 ++ .../src/locales/en.ts | 2 +- .../src/locales/it.ts | 2 +- .../src/locales/ru.ts | 2 +- .../src/locales/zh.ts | 2 +- .../plugin-navigation-tree-rm/src/manifest.ts | 9 +- .../ConnectionNavNodeControl.tsx | 83 ++++++++++++++++++ .../navTreeConnectionRenderer.tsx | 63 ++++++++++++++ .../ElementsTree/ElementsTree.tsx | 2 + .../ElementsTree/INavTreeNodeInfo.ts | 13 +++ .../ElementsTree/NavigationNodeComponent.ts | 4 + .../NAVIGATION_NODE_CONTROL_STYLES.ts | 39 +++++++++ .../NavigationNode/NavigationNodeControl.tsx | 85 ++++--------------- .../NavigationNode/NavigationNodeEditor.tsx | 11 +-- .../NavigationNode/NavigationNodeExpand.tsx | 31 +++++++ .../NavigationNodeControlRenderer.tsx | 33 ++++++- .../NavigationNodeDragged.tsx | 4 +- .../ElementsTree/transformNodeInfo.ts | 19 +++++ .../ElementsTree/useElementsTree.ts | 7 ++ .../src/NavigationTree/NavigationTree.tsx | 5 +- .../package.json | 1 + .../src/ResourceManagerScripts.tsx | 2 +- .../plugin-resource-manager/src/index.ts | 5 -- .../plugin-resource-manager/src/manifest.ts | 4 - .../package.json | 1 + .../src/PluginBootstrap.ts | 12 +-- .../src/ResourceSqlDataSource.ts | 22 ++--- .../src/ResourceSqlDataSourceBootstrap.ts | 39 ++++++--- .../src/locales/en.ts | 4 + .../src/locales/it.ts | 4 + .../src/locales/ru.ts | 4 + .../src/locales/zh.ts | 4 + .../src/SqlEditorBootstrap.ts | 2 +- .../src/SqlDataSource/BaseSqlDataSource.ts | 6 +- 57 files changed, 542 insertions(+), 139 deletions(-) create mode 100644 webapp/packages/core-connections/src/NavTree/NAV_NODE_TYPE_CONNECTION.ts rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/NavNodes/ResourceFoldersBootstrap.ts (99%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/NavNodes/getResourceKeyFromNodeId.ts (100%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/NavNodes/getResourceNodeId.ts (100%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/NavNodes/getRmProjectNodeId.ts (100%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/NavResourceNodeService.ts (57%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID.ts (100%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/ProjectsRenderer/NavigationNodeProjectControl.tsx (98%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/ProjectsRenderer/ProjectsSettingsForm.tsx (100%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts (97%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/ProjectsRenderer/navigationTreeProjectSearchCompare.ts (100%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts (100%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx (97%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/ProjectsRenderer/navigationTreeResourceTypeFilter.ts (100%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/ResourceFolderRenderer/navigationTreeResourceExpandStateGetter.ts (94%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/ResourceManagerTree.tsx (92%) rename webapp/packages/{plugin-resource-manager => plugin-navigation-tree-rm}/src/Tree/ResourceManagerTreeCaptureViewContext.tsx (100%) create mode 100644 webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceRenderer/transformResourceNodeInfo.ts create mode 100644 webapp/packages/plugin-navigation-tree/src/NavigationTree/ConnectionsRenderer/ConnectionNavNodeControl.tsx create mode 100644 webapp/packages/plugin-navigation-tree/src/NavigationTree/ConnectionsRenderer/navTreeConnectionRenderer.tsx create mode 100644 webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/INavTreeNodeInfo.ts create mode 100644 webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NAVIGATION_NODE_CONTROL_STYLES.ts create mode 100644 webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeExpand.tsx create mode 100644 webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/transformNodeInfo.ts diff --git a/webapp/packages/core-connections/src/NavTree/NAV_NODE_TYPE_CONNECTION.ts b/webapp/packages/core-connections/src/NavTree/NAV_NODE_TYPE_CONNECTION.ts new file mode 100644 index 0000000000..88a7ac870e --- /dev/null +++ b/webapp/packages/core-connections/src/NavTree/NAV_NODE_TYPE_CONNECTION.ts @@ -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'; diff --git a/webapp/packages/core-connections/src/index.ts b/webapp/packages/core-connections/src/index.ts index 8b380f25b6..c344d7f560 100644 --- a/webapp/packages/core-connections/src/index.ts +++ b/webapp/packages/core-connections/src/index.ts @@ -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'; diff --git a/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialogDelete.tsx b/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialogDelete.tsx index bdc781d486..a84711fde1 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialogDelete.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialogDelete.tsx @@ -13,5 +13,12 @@ export const ConfirmationDialogDelete: DialogComponent; + 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 ; }; diff --git a/webapp/packages/core-projects/src/ProjectInfoResource.ts b/webapp/packages/core-projects/src/ProjectInfoResource.ts index 5ce6fa6ba9..b43bcf72c5 100644 --- a/webapp/packages/core-projects/src/ProjectInfoResource.ts +++ b/webapp/packages/core-projects/src/ProjectInfoResource.ts @@ -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 }); } + 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}`); } diff --git a/webapp/packages/core-projects/src/createResourceOfType.ts b/webapp/packages/core-projects/src/createResourceOfType.ts index a8a3adc34d..d6df4ab443 100644 --- a/webapp/packages/core-projects/src/createResourceOfType.ts +++ b/webapp/packages/core-projects/src/createResourceOfType.ts @@ -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 : ''}`; } diff --git a/webapp/packages/plugin-resource-manager/src/NavNodes/ResourceFoldersBootstrap.ts b/webapp/packages/plugin-navigation-tree-rm/src/NavNodes/ResourceFoldersBootstrap.ts similarity index 99% rename from webapp/packages/plugin-resource-manager/src/NavNodes/ResourceFoldersBootstrap.ts rename to webapp/packages/plugin-navigation-tree-rm/src/NavNodes/ResourceFoldersBootstrap.ts index d9ddc87dc7..5e5c9955e2 100644 --- a/webapp/packages/plugin-resource-manager/src/NavNodes/ResourceFoldersBootstrap.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/NavNodes/ResourceFoldersBootstrap.ts @@ -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; diff --git a/webapp/packages/plugin-resource-manager/src/NavNodes/getResourceKeyFromNodeId.ts b/webapp/packages/plugin-navigation-tree-rm/src/NavNodes/getResourceKeyFromNodeId.ts similarity index 100% rename from webapp/packages/plugin-resource-manager/src/NavNodes/getResourceKeyFromNodeId.ts rename to webapp/packages/plugin-navigation-tree-rm/src/NavNodes/getResourceKeyFromNodeId.ts diff --git a/webapp/packages/plugin-resource-manager/src/NavNodes/getResourceNodeId.ts b/webapp/packages/plugin-navigation-tree-rm/src/NavNodes/getResourceNodeId.ts similarity index 100% rename from webapp/packages/plugin-resource-manager/src/NavNodes/getResourceNodeId.ts rename to webapp/packages/plugin-navigation-tree-rm/src/NavNodes/getResourceNodeId.ts diff --git a/webapp/packages/plugin-resource-manager/src/NavNodes/getRmProjectNodeId.ts b/webapp/packages/plugin-navigation-tree-rm/src/NavNodes/getRmProjectNodeId.ts similarity index 100% rename from webapp/packages/plugin-resource-manager/src/NavNodes/getRmProjectNodeId.ts rename to webapp/packages/plugin-navigation-tree-rm/src/NavNodes/getRmProjectNodeId.ts diff --git a/webapp/packages/plugin-resource-manager/src/NavResourceNodeService.ts b/webapp/packages/plugin-navigation-tree-rm/src/NavResourceNodeService.ts similarity index 57% rename from webapp/packages/plugin-resource-manager/src/NavResourceNodeService.ts rename to webapp/packages/plugin-navigation-tree-rm/src/NavResourceNodeService.ts index e18ca0cb7e..d83898d272 100644 --- a/webapp/packages/plugin-resource-manager/src/NavResourceNodeService.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/NavResourceNodeService.ts @@ -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 { 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); } diff --git a/webapp/packages/plugin-navigation-tree-rm/src/NavTreeRMContextMenuService.ts b/webapp/packages/plugin-navigation-tree-rm/src/NavTreeRMContextMenuService.ts index 2aa8e52e10..fe7ca665d5 100644 --- a/webapp/packages/plugin-navigation-tree-rm/src/NavTreeRMContextMenuService.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/NavTreeRMContextMenuService.ts @@ -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 diff --git a/webapp/packages/plugin-resource-manager/src/Tree/DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID.ts b/webapp/packages/plugin-navigation-tree-rm/src/Tree/DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID.ts similarity index 100% rename from webapp/packages/plugin-resource-manager/src/Tree/DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID.ts rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID.ts diff --git a/webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/NavigationNodeProjectControl.tsx b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/NavigationNodeProjectControl.tsx similarity index 98% rename from webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/NavigationNodeProjectControl.tsx rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/NavigationNodeProjectControl.tsx index 707fb66b13..c55550bf4f 100644 --- a/webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/NavigationNodeProjectControl.tsx +++ b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/NavigationNodeProjectControl.tsx @@ -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 { diff --git a/webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/ProjectsSettingsForm.tsx b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/ProjectsSettingsForm.tsx similarity index 100% rename from webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/ProjectsSettingsForm.tsx rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/ProjectsSettingsForm.tsx diff --git a/webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts similarity index 97% rename from webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts index ee1aec3ab6..c0a560552a 100644 --- a/webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectFilter.ts @@ -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, diff --git a/webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeProjectSearchCompare.ts b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectSearchCompare.ts similarity index 100% rename from webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeProjectSearchCompare.ts rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectSearchCompare.ts diff --git a/webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts similarity index 100% rename from webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectsExpandStateGetter.ts diff --git a/webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx similarity index 97% rename from webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx index de8b70d99a..5143101c87 100644 --- a/webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx +++ b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx @@ -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 { diff --git a/webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeResourceTypeFilter.ts b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeResourceTypeFilter.ts similarity index 100% rename from webapp/packages/plugin-resource-manager/src/Tree/ProjectsRenderer/navigationTreeResourceTypeFilter.ts rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/ProjectsRenderer/navigationTreeResourceTypeFilter.ts diff --git a/webapp/packages/plugin-resource-manager/src/Tree/ResourceFolderRenderer/navigationTreeResourceExpandStateGetter.ts b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceFolderRenderer/navigationTreeResourceExpandStateGetter.ts similarity index 94% rename from webapp/packages/plugin-resource-manager/src/Tree/ResourceFolderRenderer/navigationTreeResourceExpandStateGetter.ts rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceFolderRenderer/navigationTreeResourceExpandStateGetter.ts index d051b22ebf..e9822f7a30 100644 --- a/webapp/packages/plugin-resource-manager/src/Tree/ResourceFolderRenderer/navigationTreeResourceExpandStateGetter.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceFolderRenderer/navigationTreeResourceExpandStateGetter.ts @@ -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, diff --git a/webapp/packages/plugin-resource-manager/src/Tree/ResourceManagerTree.tsx b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceManagerTree.tsx similarity index 92% rename from webapp/packages/plugin-resource-manager/src/Tree/ResourceManagerTree.tsx rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceManagerTree.tsx index 0745ccb1bb..a9f86adb9f 100644 --- a/webapp/packages/plugin-resource-manager/src/Tree/ResourceManagerTree.tsx +++ b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceManagerTree.tsx @@ -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 = 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 = observer(function ResourceMa getChildren={navTreeService.getChildren} loadChildren={navTreeService.loadNestedNodes} settings={settings} + nodeInfoTransformers={[transformResourceNode]} filters={[resourceTypeFilter, projectFilter]} renderers={[projectsRendererRenderer]} expandStateGetters={[projectsExpandStateGetter, resourceExpandStateGetter]} diff --git a/webapp/packages/plugin-resource-manager/src/Tree/ResourceManagerTreeCaptureViewContext.tsx b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceManagerTreeCaptureViewContext.tsx similarity index 100% rename from webapp/packages/plugin-resource-manager/src/Tree/ResourceManagerTreeCaptureViewContext.tsx rename to webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceManagerTreeCaptureViewContext.tsx diff --git a/webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceRenderer/transformResourceNodeInfo.ts b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceRenderer/transformResourceNodeInfo.ts new file mode 100644 index 0000000000..e3ec88e8f5 --- /dev/null +++ b/webapp/packages/plugin-navigation-tree-rm/src/Tree/ResourceRenderer/transformResourceNodeInfo.ts @@ -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; + }; +} diff --git a/webapp/packages/plugin-navigation-tree-rm/src/index.ts b/webapp/packages/plugin-navigation-tree-rm/src/index.ts index fe9636e353..4a6b9741c9 100644 --- a/webapp/packages/plugin-navigation-tree-rm/src/index.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/index.ts @@ -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; diff --git a/webapp/packages/plugin-navigation-tree-rm/src/locales/en.ts b/webapp/packages/plugin-navigation-tree-rm/src/locales/en.ts index d6d1738de6..19c2d0f8b3 100644 --- a/webapp/packages/plugin-navigation-tree-rm/src/locales/en.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/locales/en.ts @@ -1 +1 @@ -export default []; +export default [['plugin_navigation_tree_rm_delete_confirmation_message', 'You\'re going to delete "{arg:name}" resource. Are you sure?']]; diff --git a/webapp/packages/plugin-navigation-tree-rm/src/locales/it.ts b/webapp/packages/plugin-navigation-tree-rm/src/locales/it.ts index d6d1738de6..19c2d0f8b3 100644 --- a/webapp/packages/plugin-navigation-tree-rm/src/locales/it.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/locales/it.ts @@ -1 +1 @@ -export default []; +export default [['plugin_navigation_tree_rm_delete_confirmation_message', 'You\'re going to delete "{arg:name}" resource. Are you sure?']]; diff --git a/webapp/packages/plugin-navigation-tree-rm/src/locales/ru.ts b/webapp/packages/plugin-navigation-tree-rm/src/locales/ru.ts index d6d1738de6..c89dafc3c0 100644 --- a/webapp/packages/plugin-navigation-tree-rm/src/locales/ru.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/locales/ru.ts @@ -1 +1 @@ -export default []; +export default [['plugin_navigation_tree_rm_delete_confirmation_message', 'Вы собираетесь удалить "{arg:name}" ресурс. Вы уверены?']]; diff --git a/webapp/packages/plugin-navigation-tree-rm/src/locales/zh.ts b/webapp/packages/plugin-navigation-tree-rm/src/locales/zh.ts index d6d1738de6..19c2d0f8b3 100644 --- a/webapp/packages/plugin-navigation-tree-rm/src/locales/zh.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/locales/zh.ts @@ -1 +1 @@ -export default []; +export default [['plugin_navigation_tree_rm_delete_confirmation_message', 'You\'re going to delete "{arg:name}" resource. Are you sure?']]; diff --git a/webapp/packages/plugin-navigation-tree-rm/src/manifest.ts b/webapp/packages/plugin-navigation-tree-rm/src/manifest.ts index 5923b9fc30..69c2a4d9d1 100644 --- a/webapp/packages/plugin-navigation-tree-rm/src/manifest.ts +++ b/webapp/packages/plugin-navigation-tree-rm/src/manifest.ts @@ -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 + ], }; diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ConnectionsRenderer/ConnectionNavNodeControl.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ConnectionsRenderer/ConnectionNavNodeControl.tsx new file mode 100644 index 0000000000..62690fb1fb --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ConnectionsRenderer/ConnectionNavNodeControl.tsx @@ -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( + 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) { + EventContext.set(event, EventStopPropagationFlag); + treeNodeContext.select(); + } + + function handleContextMenuOpen(event: React.MouseEvent) { + mouseContextMenu.handleContextMenuOpen(event); + treeNodeContext.select(); + } + + return styled( + TREE_NODE_STYLES, + NAVIGATION_NODE_CONTROL_STYLES, + )( + + + + + + + + {name} + + + {!dndPlaceholder && ( + + + + )} + , + ); + }), +); diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ConnectionsRenderer/navTreeConnectionRenderer.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ConnectionsRenderer/navTreeConnectionRenderer.tsx new file mode 100644 index 0000000000..a54e9eb53f --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ConnectionsRenderer/navTreeConnectionRenderer.tsx @@ -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)( + + + , + ); + } + + return ( + + ); +}); diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTree.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTree.tsx index 6090ce3c7f..64a676bfc4 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTree.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTree.tsx @@ -129,6 +129,7 @@ export const ElementsTree = observer(function ElementsTree({ selectionTree = false, emptyPlaceholder, navNodeFilterCompare, + nodeInfoTransformers = [], filters = [], renderers = [], expandStateGetters, @@ -172,6 +173,7 @@ export const ElementsTree = observer(function ElementsTree({ root, disabled, localState, + nodeInfoTransformers, filters: [nameFilter, ...filters, limitFilter], renderers: [...renderers, elementsTreeLimitRenderer], expandStateGetters, diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/INavTreeNodeInfo.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/INavTreeNodeInfo.ts new file mode 100644 index 0000000000..10df90dfa6 --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/INavTreeNodeInfo.ts @@ -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; +} diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationNodeComponent.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationNodeComponent.ts index 4b9b016f9c..0d65fe803c 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationNodeComponent.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationNodeComponent.ts @@ -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 | undefined; + onClick?: (event: React.MouseEvent) => void; }; export type NavTreeControlComponent = React.FC; diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NAVIGATION_NODE_CONTROL_STYLES.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NAVIGATION_NODE_CONTROL_STYLES.ts new file mode 100644 index 0000000000..8757b6f40b --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NAVIGATION_NODE_CONTROL_STYLES.ts @@ -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; + } +`; diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx index 5664da5c1c..c88ed1cedd 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx @@ -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( - 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( () => ({ saving: false, @@ -151,7 +107,9 @@ export const NavigationNodeControl: NavTreeControlComponent = observer) { - treeNodeContext.select(event.ctrlKey || event.metaKey); - } - function handleContextMenuOpen(event: React.MouseEvent) { 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, )( - {expandable && } - - - - + + + {editing ? ( - + ) : ( - {node.name} + {name} )} diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeEditor.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeEditor.tsx index e6f1517850..234e1997e2 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeEditor.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeEditor.tsx @@ -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(function NavigationNodeEditor({ node, disabled, onSave, onClose }) { - const [name, setName] = useState(node.name || ''); +export const NavigationNodeEditor = observer(function NavigationNodeEditor({ name: initialName, disabled, onSave, onClose }) { + const [name, setName] = useState(initialName); function save() { onSave(name); diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeExpand.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeExpand.tsx new file mode 100644 index 0000000000..2a4581c8c0 --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeExpand.tsx @@ -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(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)(); +}); diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeControlRenderer.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeControlRenderer.tsx index a6b7d29c15..08fc68d33c 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeControlRenderer.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeControlRenderer.tsx @@ -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( forwardRef(function NavigationNodeControlRenderer({ node, navNode, dragging, control: externalControl, style }, ref) { const elementRef = useRef(null); @@ -32,7 +46,8 @@ export const NavigationNodeControlRenderer = observer( const contextRef = useObjectRef({ context: useContext(ElementsTreeContext), }); - const Control = navNode.control || externalControl || NavigationNodeControlLoader; + const treeNodeContext = useContext(TreeNodeContext); + const navNodeInfoResource = useService(NavNodeInfoResource); const observer = useRef(null); useEffect(() => { @@ -77,6 +92,16 @@ export const NavigationNodeControlRenderer = observer( return
; } - return ; + 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) { + treeNodeContext.select(event.ctrlKey || event.metaKey); + } + + return styled(styles)( + , + ); }), ); diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeDragged.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeDragged.tsx index ebc9a7b3d2..d1239079eb 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeDragged.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeDragged.tsx @@ -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))( {/* */} - + , ); }); diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/transformNodeInfo.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/transformNodeInfo.ts new file mode 100644 index 0000000000..6ccfc4905e --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/transformNodeInfo.ts @@ -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); +} diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts index ca7c363d16..fd2271ad77 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts @@ -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; 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; 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'], diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx index 5555342140..3da6973bef 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx @@ -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} diff --git a/webapp/packages/plugin-resource-manager-scripts/package.json b/webapp/packages/plugin-resource-manager-scripts/package.json index c70d3c1d4e..3e70dc65f0 100644 --- a/webapp/packages/plugin-resource-manager-scripts/package.json +++ b/webapp/packages/plugin-resource-manager-scripts/package.json @@ -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", diff --git a/webapp/packages/plugin-resource-manager-scripts/src/ResourceManagerScripts.tsx b/webapp/packages/plugin-resource-manager-scripts/src/ResourceManagerScripts.tsx index 28986b9413..0c6168956e 100644 --- a/webapp/packages/plugin-resource-manager-scripts/src/ResourceManagerScripts.tsx +++ b/webapp/packages/plugin-resource-manager-scripts/src/ResourceManagerScripts.tsx @@ -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'; diff --git a/webapp/packages/plugin-resource-manager/src/index.ts b/webapp/packages/plugin-resource-manager/src/index.ts index 22cb91e455..e339c1810b 100644 --- a/webapp/packages/plugin-resource-manager/src/index.ts +++ b/webapp/packages/plugin-resource-manager/src/index.ts @@ -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'; diff --git a/webapp/packages/plugin-resource-manager/src/manifest.ts b/webapp/packages/plugin-resource-manager/src/manifest.ts index fc4e692f3c..95ef78c902 100644 --- a/webapp/packages/plugin-resource-manager/src/manifest.ts +++ b/webapp/packages/plugin-resource-manager/src/manifest.ts @@ -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, ], }; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/package.json b/webapp/packages/plugin-sql-editor-navigation-tab-script/package.json index 1e6a5dd69b..757db16051 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/package.json +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/package.json @@ -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", diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/PluginBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/PluginBootstrap.ts index f5a36cdd06..98810934d9 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/PluginBootstrap.ts @@ -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)); diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts index 35e980637e..85291d6de2 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts @@ -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; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSourceBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSourceBootstrap.ts index 1a6a4448d1..5bc7f65def 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSourceBootstrap.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSourceBootstrap.ts @@ -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) { @@ -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 + } } }); diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/en.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/en.ts index 68205e400e..c8f3675c19 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/en.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/en.ts @@ -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'], diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/it.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/it.ts index 68205e400e..c8f3675c19 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/it.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/it.ts @@ -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'], diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/ru.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/ru.ts index a41e210560..d079876823 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/ru.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/ru.ts @@ -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', 'Возникла ошибка при попытки сохранить скрипт'], diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/zh.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/zh.ts index 0980f9c143..a92d3bd1af 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/zh.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/locales/zh.ts @@ -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', '打开脚本出错'], diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorBootstrap.ts index b82151540d..81709f8b69 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorBootstrap.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorBootstrap.ts @@ -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(), diff --git a/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts b/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts index 946ba9600c..1410fc375f 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts @@ -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; readonly onDatabaseModelUpdate: ISyncExecutor[]>; + 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;