diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts index f331961473b..85102531c48 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts @@ -87,7 +87,7 @@ export class NavTreeResource extends CachedMapResource CachedMapAllKey, () => CachedMapAllKey, ); - this.projectInfoResource.onDataOutdated.addHandler(() => this.markTreeOutdated(resourceKeyList(this.keys))); + this.projectInfoResource.onDataOutdated.addHandler(data => { + if (isResourceAlias(data)) { + return; + } else { + this.markTreeOutdated(data); + } + }); this.sessionDataResource.onDataOutdated.addHandler(() => this.markTreeOutdated(resourceKeyList(this.keys))); this.userInfoResource.onUserChange.addHandler( action(() => { @@ -551,7 +557,7 @@ export class NavTreeResource extends CachedMapResource node.uri)); + children.splice(offset, limit ?? children.length, ...data.navNodeChildren.map(node => node.uri)); - if (data.navNodeChildren.length < limit) { + if (limit !== undefined && data.navNodeChildren.length < limit) { children.splice(offset + data.navNodeChildren.length, children.length - offset - data.navNodeChildren.length); } @@ -603,7 +609,36 @@ export class NavTreeResource extends CachedMapResource { + async loadAllChildren(nodeId: string): Promise { + if (!this.appAuthService.authenticated || this.isLoading(nodeId)) { + return; + } + + const pageKey = CachedResourceOffsetPageKey(0, 0).setParent(CachedResourceOffsetPageTargetKey(nodeId)); + const hasPage = this.offsetPagination.getPageInfo(pageKey) !== undefined; + + if (hasPage && !this.isOutdated(nodeId)) { + return; + } + + this.markLoading(nodeId, true); + try { + const navNodeChildren = await this.loadNodeChildren(nodeId, 0, undefined); + const uris = navNodeChildren.navNodeChildren.map(node => node.uri); + + runInAction(() => { + this.setNavObject(navNodeChildren, 0, undefined); + this.offsetPagination.setPage(CachedResourceOffsetPageKey(0, uris.length).setParent(CachedResourceOffsetPageTargetKey(nodeId)), uris, false); + this.markLoaded(nodeId); + this.markUpdated(nodeId); + this.markUpdated(resourceKeyList(uris)); + }); + } finally { + this.markLoading(nodeId, false); + } + } + + private async loadNodeChildren(parentPath: string, offset: number, limit: number | undefined): Promise { const metadata = this.metadata.get(parentPath); const { navNodeChildren, navNodeInfo } = await this.graphQLService.sdk.navNodeChildren({ parentPath, 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 5c686dbf564..7be2333b80f 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts @@ -233,8 +233,9 @@ export function useElementsTree(options: IOptions): IElementsTree { const pageInfo = navTreeResource.offsetPagination.getPageInfo( CachedResourceOffsetPageKey(0, 0).setParent(CachedResourceOffsetPageTargetKey(nodeId)), ); + const hasMorePages = pageInfo && pageInfo.end === undefined; - if (pageInfo) { + if (hasMorePages) { const lastOffset = getNextPageOffset(pageInfo); for (let offset = 0; offset < lastOffset; offset += navTreeResource.childrenLimit) { await navTreeResource.load( diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTreeService.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTreeService.ts index bea99767855..ed64e3805fd 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTreeService.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTreeService.ts @@ -1,6 +1,6 @@ /* * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2025 DBeaver Corp and others + * Copyright (C) 2020-2026 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. @@ -16,7 +16,14 @@ import { } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { Executor, type IExecutor, type ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; -import { EObjectFeature, NavNodeInfoResource, NavNodeManagerService, NavTreeResource, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree'; +import { + EObjectFeature, + NavNodeInfoResource, + NavNodeManagerService, + NavTreeResource, + ProjectsNavNodeService, + ROOT_NODE_PATH, +} from '@cloudbeaver/core-navigation-tree'; import { CACHED_RESOURCE_DEFAULT_PAGE_OFFSET, CachedResourceOffsetPageKey, @@ -46,6 +53,7 @@ interface INavigationNodeShowData { NavNodeExtensionsService, NavNodeInfoResource, NavTreeResource, + ProjectsNavNodeService, ]) export class NavigationTreeService extends View { readonly treeState: MetadataMap; @@ -59,6 +67,7 @@ export class NavigationTreeService extends View { private readonly navNodeExtensionsService: NavNodeExtensionsService, private readonly navNodeInfoResource: NavNodeInfoResource, private readonly navTreeResource: NavTreeResource, + private readonly projectsNavNodeService: ProjectsNavNodeService, ) { super(); @@ -131,11 +140,15 @@ export class NavigationTreeService extends View { return false; } - await this.navTreeResource.load( - CachedResourceOffsetPageKey(CACHED_RESOURCE_DEFAULT_PAGE_OFFSET, this.navTreeResource.childrenLimit).setParent( - CachedResourceOffsetPageTargetKey(id), - ), - ); + if (this.isMainNode(id)) { + await this.navTreeResource.loadAllChildren(id); + } else { + await this.navTreeResource.load( + CachedResourceOffsetPageKey(CACHED_RESOURCE_DEFAULT_PAGE_OFFSET, this.navTreeResource.childrenLimit).setParent( + CachedResourceOffsetPageTargetKey(id), + ), + ); + } return true; } @@ -194,6 +207,18 @@ export class NavigationTreeService extends View { }); } + // Main nodes - root, project, file system (dbvfs), scripts or datasets (rm) + private isMainNode(id: string): boolean { + if (id === ROOT_NODE_PATH) { + return true; + } + const node = this.navNodeInfoResource.get(id); + if (!node) { + return false; + } + return node.parentId === ROOT_NODE_PATH || this.projectsNavNodeService.projectTypes.includes(node.nodeType ?? ''); + } + private isConnectionNode(navNodeId: string) { const node = this.navNodeManagerService.getNode(navNodeId); return node?.objectFeatures.includes(EObjectFeature.dataSource);