diff --git a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts index 4f88482437..cfb3ad4ecb 100644 --- a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts +++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts @@ -186,10 +186,10 @@ export class ConnectionExecutionContextResource extends CachedMapResource - this.values.filter(context => { - const connection = this.connectionInfoResource.get(key); - return context.connectionId === key.connectionId && context.projectId === key.projectId && !connection?.connected; - }), + this.values.filter( + context => + context.connectionId === key.connectionId && context.projectId === key.projectId && !this.connectionInfoResource.isConnected(key), + ), ), ).map(context => context.id), ), diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index 6e6ec39cd0..2d1fcd1a99 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -15,6 +15,7 @@ import { CachedMapAllKey, CachedMapResource, type CachedResourceIncludeArgs, + type ICachedResourceMetadata, isResourceAlias, type ResourceKey, resourceKeyList, @@ -76,8 +77,12 @@ export const DEFAULT_NAVIGATOR_VIEW_SETTINGS: NavigatorSettingsInput = { showUtilityObjects: false, }; +export interface IConnectionInfoMetadata extends ICachedResourceMetadata { + connecting?: boolean; +} + @injectable() -export class ConnectionInfoResource extends CachedMapResource { +export class ConnectionInfoResource extends CachedMapResource { readonly onConnectionCreate: ISyncExecutor; readonly onConnectionClose: ISyncExecutor; @@ -237,6 +242,13 @@ export class ConnectionInfoResource extends CachedMapResource): boolean; + isConnecting(key: ResourceKey): boolean; + isConnecting(key: ResourceKey): boolean { + return [this.metadata.get(key)].flat().some(connection => connection?.connecting ?? false); + } + isConnected(key: IConnectionInfoParams): boolean; isConnected(key: ResourceKeyList): boolean; isConnected(key: ResourceKey): boolean; @@ -390,13 +402,19 @@ export class ConnectionInfoResource extends CachedMapResource { - const { connection } = await this.graphQLService.sdk.initConnection({ - ...config, - ...this.getDefaultIncludes(), - ...this.getIncludesMap(key), - }); - this.set(createConnectionParam(connection), connection); - this.onDataOutdated.execute(key); + const metadata = this.metadata.get(key); + metadata.connecting = true; + try { + const { connection } = await this.graphQLService.sdk.initConnection({ + ...config, + ...this.getDefaultIncludes(), + ...this.getIncludesMap(key), + }); + this.set(createConnectionParam(connection), connection); + this.onDataOutdated.execute(key); + } finally { + metadata.connecting = false; + } }); return this.get(key)!; @@ -444,8 +462,10 @@ export class ConnectionInfoResource extends CachedMapResource { + this.set(createConnectionParam(connection), connection); + this.onDataOutdated.execute(key); + }); }); return this.get(key)!; diff --git a/webapp/packages/core-connections/src/ConnectionToolsResource.ts b/webapp/packages/core-connections/src/ConnectionToolsResource.ts index 64d191fb6c..c25aff55e7 100644 --- a/webapp/packages/core-connections/src/ConnectionToolsResource.ts +++ b/webapp/packages/core-connections/src/ConnectionToolsResource.ts @@ -21,7 +21,7 @@ export type ConnectionTools = DatabaseConnectionToolsFragment; export class ConnectionToolsResource extends CachedMapResource { constructor( private readonly graphQLService: GraphQLService, - connectionInfoResource: ConnectionInfoResource, + private readonly connectionInfoResource: ConnectionInfoResource, ) { super(); @@ -44,6 +44,10 @@ export class ConnectionToolsResource extends CachedMapResource { constructor( private readonly graphQLService: GraphQLService, + private readonly navTreeResource: NavTreeResource, private readonly connectionInfoResource: ConnectionInfoResource, appAuthService: AppAuthService, ) { super(); appAuthService.requireAuthentication(this); + this.preloadResource(navTreeResource, key => { + if (isResourceAlias(key)) { + return ''; + } + return ResourceKeyUtils.mapKey( + key, + key => this.connectionInfoResource.get(createConnectionParam(key.projectId, key.connectionId))?.nodePath || '', + ); + }); + this.navTreeResource.onDataOutdated.addHandler(key => { + ResourceKeyUtils.forEach(key, key => { + if (isResourceAlias(key)) { + return; + } + const connection = this.connectionInfoResource.getConnectionForNode(key); + + if (!connection) { + return; + } + + this.markOutdated(resourceKeyList(this.keys.filter(key => key.projectId === connection.projectId && key.connectionId === connection.id))); + }); + }); this.preloadResource(connectionInfoResource, () => ConnectionInfoActiveProjectKey); this.before( ExecutorInterrupter.interrupter(key => { @@ -61,7 +86,7 @@ export class ContainerResource extends CachedMapResource ResourceKeyUtils.forEach(key, key => { - if (!this.connectionInfoResource.get(key)?.connected) { + if (!this.connectionInfoResource.isConnected(key)) { this.delete({ projectId: key.projectId, connectionId: key.connectionId }); } }), diff --git a/webapp/packages/core-connections/src/extensions/IObjectLoaderProvider.ts b/webapp/packages/core-connections/src/extensions/IObjectLoaderProvider.ts new file mode 100644 index 0000000000..8a52b197e6 --- /dev/null +++ b/webapp/packages/core-connections/src/extensions/IObjectLoaderProvider.ts @@ -0,0 +1,21 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 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 { createExtension, type IExtension, isExtension } from '@cloudbeaver/core-extensions'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; + +const objectLoaderProviderSymbol = Symbol('@extension/ObjectLoaderProvider'); + +export type IObjectLoaderProvider = (context: T) => ILoadableState[]; + +export function objectLoaderProvider(provider: IObjectLoaderProvider) { + return createExtension(provider, objectLoaderProviderSymbol); +} + +export function isObjectLoaderProvider(obj: IExtension): obj is IObjectLoaderProvider & IExtension { + return isExtension(obj, objectLoaderProviderSymbol); +} diff --git a/webapp/packages/core-connections/src/index.ts b/webapp/packages/core-connections/src/index.ts index 7ead664749..83f8e3d3c7 100644 --- a/webapp/packages/core-connections/src/index.ts +++ b/webapp/packages/core-connections/src/index.ts @@ -16,6 +16,7 @@ export * from './extensions/IObjectCatalogProvider.js'; export * from './extensions/IObjectCatalogSetter.js'; export * from './extensions/IObjectSchemaProvider.js'; export * from './extensions/IObjectSchemaSetter.js'; +export * from './extensions/IObjectLoaderProvider.js'; export * from './extensions/IExecutionContextProvider.js'; export * from './NavTree/ConnectionNavNodeService.js'; export * from './NavTree/NavNodeExtensionsService.js'; diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index 827774f152..e51307429e 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -15,6 +15,7 @@ export default [ ['ui_stepper_next', 'Next'], ['ui_stepper_finish', 'Finish'], ['ui_load_more', 'Load more'], + ['ui_processing_connecting', 'Connecting...'], ['ui_processing_loading', 'Loading...'], ['ui_processing_cancel', 'Cancel'], ['ui_processing_canceling', 'Cancelling...'], diff --git a/webapp/packages/core-localization/src/locales/fr.ts b/webapp/packages/core-localization/src/locales/fr.ts index 6c5cc7c945..c03d644e7f 100644 --- a/webapp/packages/core-localization/src/locales/fr.ts +++ b/webapp/packages/core-localization/src/locales/fr.ts @@ -15,6 +15,7 @@ export default [ ['ui_stepper_next', 'Suivant'], ['ui_stepper_finish', 'Terminer'], ['ui_load_more', 'Charger plus'], + ['ui_processing_connecting', 'Connexion en cours...'], ['ui_processing_loading', 'Chargement...'], ['ui_processing_cancel', 'Annuler'], ['ui_processing_canceling', 'Annulation...'], diff --git a/webapp/packages/core-localization/src/locales/it.ts b/webapp/packages/core-localization/src/locales/it.ts index e773928607..240a2adc28 100644 --- a/webapp/packages/core-localization/src/locales/it.ts +++ b/webapp/packages/core-localization/src/locales/it.ts @@ -15,6 +15,7 @@ export default [ ['ui_stepper_next', 'Avanti'], ['ui_stepper_finish', 'Termina'], ['ui_load_more', 'Load more'], + ['ui_processing_connecting', 'Collegamento...'], ['ui_processing_loading', 'Caricamento...'], ['ui_processing_cancel', 'Annulla'], ['ui_processing_canceling', 'Annullamento...'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index 0e820e2f6f..92cdc87d45 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -13,6 +13,7 @@ export default [ ['ui_dark_theme', 'Темная'], ['ui_stepper_back', 'Назад'], ['ui_load_more', 'Загрузить ещё'], + ['ui_processing_connecting', 'Подключение...'], ['ui_processing_loading', 'Загрузка...'], ['ui_processing_cancel', 'Отменить'], ['ui_processing_canceling', 'Отмена...'], diff --git a/webapp/packages/core-localization/src/locales/zh.ts b/webapp/packages/core-localization/src/locales/zh.ts index fd09809a6b..d24c082e92 100644 --- a/webapp/packages/core-localization/src/locales/zh.ts +++ b/webapp/packages/core-localization/src/locales/zh.ts @@ -15,6 +15,7 @@ export default [ ['ui_stepper_next', '下一步'], ['ui_stepper_finish', '完成'], ['ui_load_more', 'Load more'], + ['ui_processing_connecting', '连接中...'], ['ui_processing_loading', '加载中...'], ['ui_processing_cancel', '取消'], ['ui_processing_canceling', '取消中...'], diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts index 4254026317..fcccdafb0c 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts @@ -150,14 +150,16 @@ export class NavTreeResource extends CachedMapResource { - await this.graphQLService.sdk.navRefreshNode({ - nodePath: navNodeId, - }); + this.performUpdate(navNodeId, [], async () => { + await this.graphQLService.sdk.navRefreshNode({ + nodePath: navNodeId, + }); - if (!silent) { - this.markTreeOutdated(navNodeId); - } - await this.onNodeRefresh.execute(navNodeId); + if (!silent) { + this.markOutdated(navNodeId); + } + await this.onNodeRefresh.execute(navNodeId); + }); } markTreeOutdated(navNodeId: ResourceKeySimple): void { diff --git a/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx b/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx index 8f4d595767..be50f14c97 100644 --- a/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx @@ -31,7 +31,7 @@ export const ContextMenu = observer( const menu = useRef(); - useAutoLoad({ name: `${ContextMenu.name}(${menuData.menu.id})` }, menuData.loaders, !lazy, menuVisible); + useAutoLoad({ name: `${ContextMenu.name}(${menuData.menu.id})` }, menuData.loaders, !lazy, menuVisible, true); const handlers = useObjectRef( () => ({ diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx index b5648564d4..a62a86d2a4 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx @@ -58,7 +58,7 @@ export const MenuBar = observer( const mergedRef = useMergeRefs(ref, refNav); const styles = useS(style); const items = menu.items; - useAutoLoad(MenuBar, menu.loaders); + useAutoLoad(MenuBar, menu.loaders, true, false, true); if (!items.length) { return null; @@ -67,7 +67,7 @@ export const MenuBar = observer( return (
- + {items.map(item => ( ))} diff --git a/webapp/packages/core-ui/src/ContextMenu/SubMenuElement.tsx b/webapp/packages/core-ui/src/ContextMenu/SubMenuElement.tsx index eb81c29de3..836fc997f9 100644 --- a/webapp/packages/core-ui/src/ContextMenu/SubMenuElement.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/SubMenuElement.tsx @@ -43,7 +43,7 @@ export const SubMenuElement = observer( const handler = subMenuData.handler; const hidden = getComputed(() => handler?.isHidden?.(subMenuData.context)); - useAutoLoad(SubMenuElement, subMenuData.loaders, !hidden, visible); + useAutoLoad(SubMenuElement, subMenuData.loaders, !hidden, visible, true); const handlers = useObjectRef( () => ({ diff --git a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialogFooter.tsx b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialogFooter.tsx index 0a19c4154d..4cf3dcccce 100644 --- a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialogFooter.tsx +++ b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialogFooter.tsx @@ -27,7 +27,7 @@ export const ConnectionDialogFooter = observer(function ConnectionDialogF {translate('ui_stepper_back')}
); diff --git a/webapp/packages/plugin-connection-template/src/locales/en.ts b/webapp/packages/plugin-connection-template/src/locales/en.ts index cdd315dbc1..baf6d26cd4 100644 --- a/webapp/packages/plugin-connection-template/src/locales/en.ts +++ b/webapp/packages/plugin-connection-template/src/locales/en.ts @@ -6,7 +6,6 @@ * you may not use this file except in compliance with the License. */ export default [ - ['plugin_connection_template_connecting', 'Connecting...'], ['plugin_connection_template_connecting_message', 'Connecting to database...'], ['plugin_connection_template_connect_success', 'Connection is established'], ['plugin_connection_template_action_connection_template_label', 'From a Template'], diff --git a/webapp/packages/plugin-connection-template/src/locales/fr.ts b/webapp/packages/plugin-connection-template/src/locales/fr.ts index e8009b5044..2d4df92056 100644 --- a/webapp/packages/plugin-connection-template/src/locales/fr.ts +++ b/webapp/packages/plugin-connection-template/src/locales/fr.ts @@ -6,7 +6,6 @@ * you may not use this file except in compliance with the License. */ export default [ - ['plugin_connection_template_connecting', 'Connexion en cours...'], ['plugin_connection_template_connecting_message', 'Connexion à la base de données en cours...'], ['plugin_connection_template_connect_success', 'Connexion établie'], ['plugin_connection_template_action_connection_template_label', "À partir d'un modèle"], diff --git a/webapp/packages/plugin-connection-template/src/locales/it.ts b/webapp/packages/plugin-connection-template/src/locales/it.ts index 4b29b98428..61d0e8c04a 100644 --- a/webapp/packages/plugin-connection-template/src/locales/it.ts +++ b/webapp/packages/plugin-connection-template/src/locales/it.ts @@ -6,7 +6,6 @@ * you may not use this file except in compliance with the License. */ export default [ - ['plugin_connection_template_connecting', 'Collegamento...'], ['plugin_connection_template_connecting_message', 'Collegamento al database...'], ['plugin_connection_template_connect_success', 'Connection is established'], ['plugin_connection_template_action_connection_template_label', 'Dal Template'], diff --git a/webapp/packages/plugin-connection-template/src/locales/ru.ts b/webapp/packages/plugin-connection-template/src/locales/ru.ts index 7c4300da47..fae323ebd7 100644 --- a/webapp/packages/plugin-connection-template/src/locales/ru.ts +++ b/webapp/packages/plugin-connection-template/src/locales/ru.ts @@ -6,7 +6,6 @@ * you may not use this file except in compliance with the License. */ export default [ - ['plugin_connection_template_connecting', 'Подключение...'], ['plugin_connection_template_connecting_message', 'Подключение к базе...'], ['plugin_connection_template_connect_success', 'Подключение установлено'], ['plugin_connection_template_action_connection_template_label', 'Из шаблона'], diff --git a/webapp/packages/plugin-connection-template/src/locales/zh.ts b/webapp/packages/plugin-connection-template/src/locales/zh.ts index 6b4d9ee3ce..7c344c2adb 100644 --- a/webapp/packages/plugin-connection-template/src/locales/zh.ts +++ b/webapp/packages/plugin-connection-template/src/locales/zh.ts @@ -6,7 +6,6 @@ * you may not use this file except in compliance with the License. */ export default [ - ['plugin_connection_template_connecting', '连接中...'], ['plugin_connection_template_connecting_message', '连接数据库...'], ['plugin_connection_template_connect_success', '已建立连接'], ['plugin_connection_template_action_connection_template_label', '从模板创建'], diff --git a/webapp/packages/plugin-connections/src/ConnectionShield.tsx b/webapp/packages/plugin-connections/src/ConnectionShield.tsx index e22dfa5a40..8185e07aeb 100644 --- a/webapp/packages/plugin-connections/src/ConnectionShield.tsx +++ b/webapp/packages/plugin-connections/src/ConnectionShield.tsx @@ -6,9 +6,9 @@ * you may not use this file except in compliance with the License. */ import { observer } from 'mobx-react-lite'; -import { type PropsWithChildren, useState } from 'react'; +import { type PropsWithChildren } from 'react'; -import { Button, Loader, TextPlaceholder, useResource, useTranslate } from '@cloudbeaver/core-blocks'; +import { Button, getComputed, Loader, TextPlaceholder, useResource, useTranslate } from '@cloudbeaver/core-blocks'; import { ConnectionInfoResource, ConnectionsManagerService, type IConnectionInfoParams } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; @@ -23,28 +23,23 @@ export const ConnectionShield = observer connectionKey && connection.resource.isConnecting(connectionKey)); async function handleConnect() { if (connecting || !connection.data || !connectionKey) { return; } - setConnecting(true); - try { await connectionsManagerService.requireConnection(connectionKey); } catch (exception: any) { notificationService.logException(exception); - } finally { - setConnecting(false); } } - if (connection.data && !connection.data.connected) { - if (connecting || connection.isLoading()) { - return ; + if (getComputed(() => connection.data && !connection.data.connected)) { + if (connecting) { + return ; } return ( diff --git a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts index 52f365d7e4..c696ef956b 100644 --- a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts +++ b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts @@ -98,6 +98,7 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap { return [ ...this.appAuthService.loaders, + ...this.connectionSchemaManagerService.currentObjectLoaders, getCachedMapResourceLoaderState(this.containerResource, () => ({ ...activeConnectionKey, catalogId: this.connectionSchemaManagerService.activeObjectCatalogId, diff --git a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerService.ts b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerService.ts index cfb378c4bf..49078b088d 100644 --- a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerService.ts +++ b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerService.ts @@ -18,6 +18,7 @@ import { type IExecutionContextProvider, type IObjectCatalogProvider, type IObjectCatalogSetter, + type IObjectLoaderProvider, type IObjectSchemaProvider, type IObjectSchemaSetter, isConnectionProvider, @@ -25,6 +26,7 @@ import { isExecutionContextProvider, isObjectCatalogProvider, isObjectCatalogSetter, + isObjectLoaderProvider, isObjectSchemaProvider, isObjectSchemaSetter, type IStructContainers, @@ -44,6 +46,7 @@ import { isProjectSetterState, } from '@cloudbeaver/core-projects'; import { CachedMapAllKey } from '@cloudbeaver/core-resource'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; import { type ITab, NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs'; export interface IConnectionInfo { @@ -61,6 +64,7 @@ interface IActiveItem { getCurrentSchemaId?: IObjectSchemaProvider; getCurrentCatalogId?: IObjectCatalogProvider; getCurrentExecutionContext?: IExecutionContextProvider; + getCurrentLoader?: IObjectLoaderProvider; changeConnectionId?: IConnectionSetter; changeProjectId?: IProjectSetter; changeCatalogId?: IObjectCatalogSetter; @@ -132,6 +136,14 @@ export class ConnectionSchemaManagerService { return this.activeItem.getCurrentExecutionContext(this.activeItem.context); } + get currentObjectLoaders(): ILoadableState[] { + if (!this.activeItem?.getCurrentLoader) { + return []; + } + + return this.activeItem.getCurrentLoader(this.activeItem.context); + } + get currentObjectSchemaId(): string | undefined { if (this.pendingSchemaId !== null) { return this.pendingSchemaId; @@ -271,6 +283,7 @@ export class ConnectionSchemaManagerService { currentObjectCatalogId: computed, activeObjectCatalogId: computed, currentObjectSchemaId: computed, + currentObjectLoaders: computed, isConnectionChangeable: computed, isObjectCatalogChangeable: computed, isObjectSchemaChangeable: computed, @@ -456,6 +469,9 @@ export class ConnectionSchemaManagerService { .on(isExecutionContextProvider, extension => { item.getCurrentExecutionContext = extension; }) + .on(isObjectLoaderProvider, extension => { + item.getCurrentLoader = extension; + }) .on(isProjectSetter, extension => { item.changeProjectId = extension; diff --git a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSelector/ConnectionIcon.tsx b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSelector/ConnectionIcon.tsx index 3b3b960619..8926de2203 100644 --- a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSelector/ConnectionIcon.tsx +++ b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSelector/ConnectionIcon.tsx @@ -10,6 +10,7 @@ import { observer } from 'mobx-react-lite'; import { ConnectionImageWithMask, ConnectionImageWithMaskSvgStyles, + getComputed, s, SContext, type StyleRegistry, @@ -47,9 +48,16 @@ export const ConnectionIcon = observer(function ConnectionI return null; } - const driver = drivers.resource.get(connection.data.driverId); + const connected = getComputed(() => connection.data?.connected ?? false); + const driverIcon = getComputed(() => { + if (!connection.data?.driverId) { + return null; + } - if (!driver?.icon) { + return drivers.resource.get(connection.data.driverId)?.icon; + }); + + if (!driverIcon) { return null; } @@ -58,8 +66,8 @@ export const ConnectionIcon = observer(function ConnectionI tab.handlerState.tabTitle = data.name; }); }, - active: !connection.isLoading() && connection.data?.connected, + active: getComputed(() => !!connection.tryGetData?.connected), }); const pages = dbObjectPagesService.orderedPages; @@ -64,7 +64,7 @@ export const ObjectViewerPanel: TabHandlerPanelComponent return ( - {node.data ? ( + {node.tryGetData ? ( ) { + // this method must be synchronous with nav-tree update + private removeTabs(key: ResourceKey) { const tabs: string[] = []; - await this.connectionInfoResource.load(ConnectionInfoActiveProjectKey); - ResourceKeyUtils.forEach(key, key => { const tab = this.navigationTabsService.findTab(isObjectViewerTab(tab => tab.handlerState.objectId === key)); @@ -282,9 +281,7 @@ export class ObjectViewerTabService { private getNavNode({ handlerState }: ITab) { if (handlerState.connectionKey) { - const connection = this.connectionInfoResource.get(handlerState.connectionKey); - - if (!connection?.connected) { + if (!this.connectionInfoResource.isConnected(handlerState.connectionKey)) { return; } } diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts index be9c8fdc51..b570968252 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts @@ -24,6 +24,7 @@ import { type IConnectionInfoParams, objectCatalogProvider, objectCatalogSetter, + objectLoaderProvider, objectSchemaProvider, objectSchemaSetter, } from '@cloudbeaver/core-connections'; @@ -33,7 +34,7 @@ import { NotificationService } from '@cloudbeaver/core-events'; import { Executor, ExecutorInterrupter, type IExecutionContextProvider } from '@cloudbeaver/core-executor'; import { NavNodeInfoResource, NodeManagerUtils, objectNavNodeProvider } from '@cloudbeaver/core-navigation-tree'; import { projectProvider, projectSetter, projectSetterState } from '@cloudbeaver/core-projects'; -import { resourceKeyList, type ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource'; +import { getCachedMapResourceLoaderState, resourceKeyList, type ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource'; import type { NavNodeInfoFragment } from '@cloudbeaver/core-sdk'; import { isArraysEqual } from '@cloudbeaver/core-utils'; import { type ITab, type ITabOptions, NavigationTabsService, TabHandler } from '@cloudbeaver/plugin-navigation-tabs'; @@ -95,6 +96,7 @@ export class SqlEditorTabService extends Bootstrap { projectProvider(this.getProjectId.bind(this)), connectionProvider(this.getConnectionId.bind(this)), objectCatalogProvider(this.getObjectCatalogId.bind(this)), + objectLoaderProvider(this.getObjectLoader.bind(this)), objectSchemaProvider(this.getObjectSchemaId.bind(this)), executionContextProvider(this.getExecutionContext.bind(this)), projectSetter(this.setProjectId.bind(this)), @@ -182,9 +184,7 @@ export class SqlEditorTabService extends Bootstrap { const { projectId, connectionId, defaultCatalog, defaultSchema } = executionContext; const connectionKey = createConnectionParam(projectId, connectionId); - const connection = this.connectionInfoResource.get(connectionKey); - - if (!connection?.connected) { + if (!this.connectionInfoResource.isConnected(connectionKey)) { return; } @@ -209,8 +209,6 @@ export class SqlEditorTabService extends Bootstrap { const parents = this.navNodeInfoResource.getParents(nodeId); - untracked(() => this.navNodeInfoResource.load(nodeId!)); - return { nodeId, path: parents, @@ -327,6 +325,34 @@ export class SqlEditorTabService extends Bootstrap { return createConnectionParam(context.projectId, context.connectionId); } + private getObjectLoader(tab: ITab) { + const executionContextComputed = computed(() => this.sqlDataSourceService.get(tab.handlerState.editorId)?.executionContext); + + const connectionKeyComputed = computed(() => { + const executionContext = executionContextComputed.get(); + + if (!executionContext) { + return null; + } + + return createConnectionParam(executionContext.projectId, executionContext.connectionId); + }); + + return [ + getCachedMapResourceLoaderState(this.connectionInfoResource, () => connectionKeyComputed.get()), + getCachedMapResourceLoaderState(this.connectionExecutionContextResource, () => executionContextComputed.get()?.id || null), + getCachedMapResourceLoaderState(this.containerResource, () => connectionKeyComputed.get()), + // TODO: maybe we need it for this.getNavNode to work properly, but it's seems working without it + // getCachedMapResourceLoaderState(this.navNodeInfoResource, () => { + // if (this.containerResource.isLoadable(connectionKey)) { + // return null; + // } + // console.log('node:', this.getNavNode(tab)?.nodeId || null); + // return this.getNavNode(tab)?.nodeId || null; + // }), + ]; + } + private getObjectCatalogId(tab: ITab) { const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); const context = this.connectionExecutionContextResource.get(dataSource?.executionContext?.id ?? ''); diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorOverlay.tsx b/webapp/packages/plugin-sql-editor/src/SqlEditorOverlay.tsx index cc75c0331e..4d176e880f 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorOverlay.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorOverlay.tsx @@ -94,7 +94,7 @@ export const SqlEditorOverlay = observer(function SqlEditorOverlay({ stat }, [connected, initExecutionContext]); return ( - + {connection.tryGetData?.name} diff --git a/webapp/packages/plugin-top-app-bar/src/TopNavBar/AppStateMenu/AppStateMenu.module.css b/webapp/packages/plugin-top-app-bar/src/TopNavBar/AppStateMenu/AppStateMenu.module.css index 760eef637a..380f8422e0 100644 --- a/webapp/packages/plugin-top-app-bar/src/TopNavBar/AppStateMenu/AppStateMenu.module.css +++ b/webapp/packages/plugin-top-app-bar/src/TopNavBar/AppStateMenu/AppStateMenu.module.css @@ -14,3 +14,7 @@ display: none; } } + +.appStateMenu > .loader { + height: 100%; +} diff --git a/webapp/packages/plugin-top-app-bar/src/TopNavBar/AppStateMenu/AppStateMenu.tsx b/webapp/packages/plugin-top-app-bar/src/TopNavBar/AppStateMenu/AppStateMenu.tsx index 6189b0fc14..b60c5f329d 100644 --- a/webapp/packages/plugin-top-app-bar/src/TopNavBar/AppStateMenu/AppStateMenu.tsx +++ b/webapp/packages/plugin-top-app-bar/src/TopNavBar/AppStateMenu/AppStateMenu.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react-lite'; import { AppAuthService } from '@cloudbeaver/core-authentication'; -import { s, SContext, type StyleRegistry, useS } from '@cloudbeaver/core-blocks'; +import { Loader, s, SContext, type StyleRegistry, useS } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { MenuBar, MenuBarItemStyles, MenuBarStyles } from '@cloudbeaver/core-ui'; import { useMenu } from '@cloudbeaver/core-view'; @@ -46,7 +46,9 @@ export const AppStateMenu = observer(function AppStateMenu() { return (
- + + +
); diff --git a/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenu.tsx b/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenu.tsx index 80a04b5efe..582b638cce 100644 --- a/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenu.tsx +++ b/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenu.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react-lite'; import { AppAuthService } from '@cloudbeaver/core-authentication'; -import { s, useS } from '@cloudbeaver/core-blocks'; +import { Loader, s, useS } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { MenuBar } from '@cloudbeaver/core-ui'; import { useMenu } from '@cloudbeaver/core-view'; @@ -27,7 +27,9 @@ export const MainMenu = observer(function MainMenu() { return (
- + + +
); }); diff --git a/webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuWrapper.module.css b/webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuWrapper.module.css index 5b363f1b79..1600ffc0c9 100644 --- a/webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuWrapper.module.css +++ b/webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/TopMenuWrapper.module.css @@ -8,4 +8,8 @@ .menuWrapper { display: flex; height: 100%; + + &.loader { + height: 100%; + } }