diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts index 61d192e856..cc43403d89 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeInfoResource.ts @@ -16,7 +16,8 @@ import { isResourceKeyList, NavNodeInfoFragment, ResourceKeyUtils, - ICachedMapResourceMetadata + ICachedMapResourceMetadata, + ResourceKeyList } from '@cloudbeaver/core-sdk'; import { MetadataMap } from '@cloudbeaver/core-utils'; @@ -60,6 +61,19 @@ export class NavNodeInfoResource extends CachedMapResource { }); } + setParent(key: string, parentId: string): void; + setParent(key: ResourceKeyList, parentId: string): void; + setParent(key: ResourceKey, parentId: string): void; + setParent(key: ResourceKey, parentId: string): void { + ResourceKeyUtils.forEach(key, key => { + const node = this.get(key); + + if (node) { + node.parentId = parentId; + } + }); + } + protected async loader(key: ResourceKey): Promise> { if (isResourceKeyList(key)) { const values: NavNode[] = []; diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts index df4b8496e7..5f77ac6194 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts @@ -140,14 +140,19 @@ export class NavTreeResource extends CachedMapResource { } async changeName(node: NavNode, name: string): Promise { - await this.performUpdate(resourceKeyList([node.id, node.parentId]), [], async () => { - await this.graphQLService.sdk.navRenameNode({ - nodePath: node.id, - newName: name, - }); + await this.performUpdate(node.parentId, [], async () => { + this.markDataLoading(node.id); + try { + await this.graphQLService.sdk.navRenameNode({ + nodePath: node.id, + newName: name, + }); + } finally { + this.markDataLoaded(node.id); + } }); - await this.refreshTree(node.parentId); + await this.markOutdated(node.parentId); } deleteInNode(key: string, value: string[]): void; @@ -274,47 +279,88 @@ export class NavTreeResource extends CachedMapResource { } private async connectionUpdateHandler(key: ResourceKey) { + const outdatedTrees: string[] = []; + const outdatedFolders: string[] = []; const closedConnections: string[] = []; - await ResourceKeyUtils.forEachAsync(key, async key => { - const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(key); + ResourceKeyUtils.forEach(key, key => { + const connectionInfo = this.connectionInfo.get(key); - if (this.has(nodeId)) { - const connectionInfo = this.connectionInfo.get(key); + if (!connectionInfo?.nodePath) { + return; + } - if (!connectionInfo?.connected) { - closedConnections.push(nodeId); - } else { - await this.markOutdated(nodeId); + if (!this.has(connectionInfo.nodePath)) { + return; + } + + if (!connectionInfo.connected) { + closedConnections.push(connectionInfo.nodePath); + + const folder = /* connectionInfo.folder || */ ROOT_NODE_PATH; + + if (!outdatedFolders.includes(folder)) { + outdatedFolders.push(folder); } + } else { + outdatedTrees.push(connectionInfo.nodePath); } }); if (closedConnections.length > 0) { const key = resourceKeyList(closedConnections); this.set(key, closedConnections.map(() => [])); - await this.markOutdated(ROOT_NODE_PATH); + } + + if (outdatedTrees.length > 0 || outdatedFolders.length > 0) { + const key = resourceKeyList([...outdatedTrees, ...outdatedFolders]); + await this.markOutdated(key); } } private connectionRemoveHandler(key: ResourceKey) { - runInAction(() => { - ResourceKeyUtils.forEach(key, key => { - const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(key); + ResourceKeyUtils.forEach(key, key => { + const connectionInfo = this.connectionInfo.get(key); - const node = this.navNodeInfoResource.get(nodeId); + if (!connectionInfo?.nodePath) { + return; + } - if (node) { - this.deleteInNode(node.parentId, [nodeId]); - } - }); + const folder = /* connectionInfo.folder || */ ROOT_NODE_PATH; + + if (connectionInfo) { + this.deleteInNode(folder, [connectionInfo.nodePath]); + } }); } private async connectionCreateHandler(connection: Connection) { - const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(connection.id); - await this.markOutdated(ROOT_NODE_PATH); - await this.markTreeOutdated(nodeId); + if (!connection.nodePath) { + return; + } + const folder = /* connection.folder || */ ROOT_NODE_PATH; + + const children = this.get(folder); + + if (!children) { + return; + } + + const connectionNode = await this.navNodeInfoResource.load(connection.nodePath); + this.navNodeInfoResource.setParent(connection.nodePath, folder); + + let insertIndex = 0; + + const nodes = this.navNodeInfoResource.get(resourceKeyList(children)); + + for (const node of nodes) { + if (!node?.folder && node?.name?.localeCompare(connectionNode.name!) === 1) { + break; + } + insertIndex++; + } + + children.splice(insertIndex, 0, connection.nodePath); } private setNavObject(data: NavNodeChildrenQuery | NavNodeChildrenQuery[]) { diff --git a/webapp/packages/core-blocks/src/useDataResource.ts b/webapp/packages/core-blocks/src/useDataResource.ts index fc0e389044..fb05a65727 100644 --- a/webapp/packages/core-blocks/src/useDataResource.ts +++ b/webapp/packages/core-blocks/src/useDataResource.ts @@ -12,6 +12,7 @@ import { IServiceConstructor, useService } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { CachedDataResource, CachedResourceData, CachedResourceParam, isResourceKeyList, ResourceKey } from '@cloudbeaver/core-sdk'; +import { getComputed } from './getComputed'; import type { ILoadableState } from './Loader/Loader'; import { useObjectRef } from './useObjectRef'; @@ -47,7 +48,7 @@ export function useDataResource< const key = keyObj && typeof keyObj === 'object' && 'includes' in keyObj ? keyObj.key : keyObj; const includes = keyObj && typeof keyObj === 'object' && 'includes' in keyObj ? keyObj.includes : []; - const outdated = resource.isOutdated(key); + const outdated = getComputed(() => resource.isOutdated(key) && !resource.isDataLoading(key)); const refObj = useObjectRef(() => ({ loading: false, diff --git a/webapp/packages/core-blocks/src/useMapResource.ts b/webapp/packages/core-blocks/src/useMapResource.ts index 678ad4fb68..e94eccab2a 100644 --- a/webapp/packages/core-blocks/src/useMapResource.ts +++ b/webapp/packages/core-blocks/src/useMapResource.ts @@ -13,6 +13,7 @@ import { IServiceConstructor, useService } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { CachedResourceIncludeArgs, CachedMapResource, CachedMapResourceGetter, ResourceKey, CachedMapResourceValue, CachedMapResourceKey, CachedMapResourceArguments, CachedMapResourceLoader, ResourceKeyList, CachedMapResourceListGetter } from '@cloudbeaver/core-sdk'; +import { getComputed } from './getComputed'; import type { ILoadableState } from './Loader/Loader'; import { useObservableRef } from './useObservableRef'; @@ -192,7 +193,7 @@ export function useMapResource< actions, }); - const outdated = resource.isOutdated(key); + const outdated = getComputed(() => resource.isOutdated(key) && !resource.isDataLoading(key)); const [result] = useState< IMapResourceResult diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts index d556d24860..37112944de 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { action, computed, makeObservable } from 'mobx'; +import { action, computed, makeObservable, runInAction } from 'mobx'; import { Executor, IExecutor } from '@cloudbeaver/core-executor'; import { MetadataMap } from '@cloudbeaver/core-utils'; @@ -237,9 +237,15 @@ export abstract class CachedMapResource< delete(key: ResourceKey): void; delete(key: ResourceKey): void { key = this.transformParam(key); - ResourceKeyUtils.forEach(key, key => this.data.delete(key)); - this.markUpdated(key); - this.onItemDelete.execute(key); + + this.onItemDelete + .execute(key) + .finally(() => { + runInAction(() => { + ResourceKeyUtils.forEach(key, key => this.data.delete(key)); + this.markUpdated(key); + }); + }); } clear(): void { diff --git a/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts b/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts index bed8deeca7..2e6e74c5be 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts +++ b/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts @@ -64,7 +64,7 @@ export class ObjectViewerTabService { this.connectionInfo.onItemAdd.addHandler(this.updateConnectionTabs.bind(this)); this.connectionInfo.onItemDelete.addHandler(this.closeConnectionTabs.bind(this)); this.navNodeManagerService.navNodeInfoResource.onItemAdd.addHandler(this.updateTabs.bind(this)); - this.navNodeManagerService.navNodeInfoResource.onItemDelete.addHandler(this.removeTabs.bind(this)); + this.navNodeManagerService.navTree.onItemDelete.addHandler(this.removeTabs.bind(this)); } isPageActive(tab: ITab, page: ObjectPage): boolean {