diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts index 61541420be..88869486a2 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts @@ -10,6 +10,7 @@ import { makeObservable, observable } from 'mobx'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { IExecutor, Executor, IExecutionContextProvider, ISyncContextLoader } from '@cloudbeaver/core-executor'; +import { ProjectsNavNodeService } from '@cloudbeaver/core-projects'; import { resourceKeyList } from '@cloudbeaver/core-sdk'; import { NavigationService } from '@cloudbeaver/core-ui'; import type { IDataContextProvider } from '@cloudbeaver/core-view'; @@ -114,6 +115,7 @@ export class NavNodeManagerService extends Bootstrap { constructor( readonly navTree: NavTreeResource, readonly navNodeInfoResource: NavNodeInfoResource, + private readonly projectsNavNodeService: ProjectsNavNodeService, navigationService: NavigationService ) { super(); @@ -327,7 +329,7 @@ export class NavNodeManagerService extends Bootstrap { data ) => { let nodeId = data.nodeId; - const projectId = data.projectId; + let projectId = data.projectId; let parentId = data.parentId; let folderId = ''; let name: string | undefined; @@ -355,6 +357,10 @@ export class NavNodeManagerService extends Bootstrap { if (data.folderId) { folderId = data.folderId; } + + if (!projectId) { + projectId = this.projectsNavNodeService.getProject(node.id)?.id; + } } } diff --git a/webapp/packages/core-ui/src/Tabs/TabsBox/TabsBox.tsx b/webapp/packages/core-ui/src/Tabs/TabsBox/TabsBox.tsx index fdd1e668cd..9e8ca84179 100644 --- a/webapp/packages/core-ui/src/Tabs/TabsBox/TabsBox.tsx +++ b/webapp/packages/core-ui/src/Tabs/TabsBox/TabsBox.tsx @@ -12,6 +12,7 @@ import styled, { css } from 'reshadow'; import { useStyles, ComponentStyle } from '@cloudbeaver/core-theming'; import type { MetadataMap } from '@cloudbeaver/core-utils'; +import type { ITabData } from '../TabsContainer/ITabsContainer'; import { TabsState } from '../TabsState'; const styles = css` @@ -42,13 +43,15 @@ const styles = css` `; type TabsBoxProps = PropsWithChildren<{ - currentTabId: string; + currentTabId: string | null; tabs?: ReactNode; tabIndex?: number; localState?: MetadataMap; tabList?: string[]; enabledBaseActions?: boolean; + autoSelect?: boolean; className?: string; + onChange?: (tab: ITabData) => void; style?: ComponentStyle; }>; @@ -59,8 +62,10 @@ export const TabsBox = forwardRef(function TabsBox localState, tabList, enabledBaseActions, + autoSelect, children, className, + onChange, style, }, ref) { return styled(styles, useStyles(style))( @@ -68,7 +73,9 @@ export const TabsBox = forwardRef(function TabsBox currentTabId={currentTabId} localState={localState} tabList={tabList} + autoSelect={autoSelect} enabledBaseActions={enabledBaseActions} + onChange={onChange} > {tabs && {tabs}} diff --git a/webapp/packages/core-ui/src/Tabs/TabsState.tsx b/webapp/packages/core-ui/src/Tabs/TabsState.tsx index 6d9a3fe924..6064832125 100644 --- a/webapp/packages/core-ui/src/Tabs/TabsState.tsx +++ b/webapp/packages/core-ui/src/Tabs/TabsState.tsx @@ -53,19 +53,19 @@ export const TabsState = observer(function TabsState>({ ...rest }: Props): React.ReactElement | null { const props = rest as any as T; + let displayed: string[] = []; + + if (container) { + displayed = container.getIdList(props); + } else if (tabList) { + displayed = tabList; + } + if ( !selectedId && (currentTabId === undefined || currentTabId === null) && autoSelect ) { - let displayed: string[] = []; - - if (container) { - displayed = container.getIdList(props); - } else if (tabList) { - displayed = tabList; - } - if (displayed.length > 0) { selectedId = displayed[0]; } @@ -96,16 +96,15 @@ export const TabsState = observer(function TabsState>({ tabList, }); - if (currentTabId !== undefined) { + if (currentTabId !== undefined && currentTabId !== null) { state.selectedId = currentTabId; dynamic.selectedId = currentTabId; } - if (dynamic.container && dynamic.selectedId && selectedId) { - const displayed = dynamic.container.getIdList(props); + if (displayed.length > 0 && dynamic.selectedId && selectedId) { const tabExists = displayed.includes(dynamic.selectedId); - if (displayed.length > 0 && !tabExists) { + if (!tabExists) { if (displayed.includes(selectedId)) { state.selectedId = selectedId; } else { @@ -137,7 +136,7 @@ export const TabsState = observer(function TabsState>({ }, []); useEffect(() => { - if (currentTabId !== undefined) { + if (currentTabId !== undefined && currentTabId !== null) { return; } @@ -145,7 +144,7 @@ export const TabsState = observer(function TabsState>({ tabId: state.selectedId!, props, }); - }, [state.selectedId]); + }, [currentTabId, state.selectedId]); useEffect(() => { if (state.selectedId) { diff --git a/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/ITab.ts b/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/ITab.ts index 56fae5b47a..40f45088c6 100644 --- a/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/ITab.ts +++ b/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/ITab.ts @@ -8,6 +8,7 @@ export interface ITabOptions { id?: string; + projectId: string | null; handlerId: string; handlerState: T; } @@ -15,5 +16,6 @@ export interface ITabOptions { export interface ITab extends ITabOptions { id: string; userId: string; + projectId: string | null; restored: boolean; } diff --git a/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsBar/NavigationTabsBar.tsx b/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsBar/NavigationTabsBar.tsx index 0c5b10666f..1e15f1c3ae 100644 --- a/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsBar/NavigationTabsBar.tsx +++ b/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsBar/NavigationTabsBar.tsx @@ -15,7 +15,7 @@ import { TextPlaceholder, useExecutor } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; -import { TabsBox, TabPanel, BASE_TAB_STYLES } from '@cloudbeaver/core-ui'; +import { TabsBox, TabPanel, BASE_TAB_STYLES, ITabData } from '@cloudbeaver/core-ui'; import { CaptureView } from '@cloudbeaver/core-view'; import { NavigationTabsService } from '../NavigationTabsService'; @@ -62,6 +62,10 @@ export const NavigationTabsBar = observer(function NavigationTabsBar({ cl await navigation.restoreTabs(); } + function handleTabChange(tab: ITabData) { + handleSelect(tab.tabId); + } + useExecutor({ executor: userInfoResource.onDataUpdate, postHandlers: [unloadTabs, restoreTabs], @@ -90,7 +94,9 @@ export const NavigationTabsBar = observer(function NavigationTabsBar({ cl tabList={navigation.tabIdList} style={styles} tabIndex={0} + autoSelect enabledBaseActions + onChange={handleTabChange} > {navigation.tabIdList.map(tabId => ( diff --git a/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsService.ts b/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsService.ts index 4e03faa17f..07fcb450e4 100644 --- a/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsService.ts +++ b/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/NavigationTabsService.ts @@ -13,6 +13,7 @@ import { AppAuthService, UserInfoResource } from '@cloudbeaver/core-authenticati import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; +import { ProjectsService } from '@cloudbeaver/core-projects'; import { ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; import { LocalStorageSaveService } from '@cloudbeaver/core-settings'; import { isArraysEqual } from '@cloudbeaver/core-utils'; @@ -25,7 +26,7 @@ import { TabNavigationContext, ITabNavigationContext } from './TabNavigationCont interface TabsState { tabs: string[]; history: string[]; - currentId: string; + currentId: string | null; } const NAVIGATION_TABS_BASE_KEY = 'navigation_tabs'; @@ -37,16 +38,33 @@ export class NavigationTabsService extends View { state = new Map(); get currentTab(): ITab | undefined { - return this.getTab(this.currentTabId); + if (this.currentTabId) { + return this.getTab(this.currentTabId); + } + return undefined; } - get currentTabId(): string { - return this.userTabsState.currentId; + get currentTabId(): string | null { + if ( + this.userTabsState.currentId !== null + && this.tabIdList.includes(this.userTabsState.currentId) + ) { + return this.userTabsState.currentId; + } + + return null; } get tabIdList(): string[] { return Array.from(this.tabsMap.values()) - .filter(tab => tab.restored && tab.userId === this.userInfoResource.getId()) + .filter(tab => ( + tab.restored + && tab.userId === this.userInfoResource.getId() + && ( + tab.projectId === null + || this.projectsService.activeProjectIds.includes(tab.projectId) + ) + )) .map(tab => tab.id); } @@ -57,7 +75,7 @@ export class NavigationTabsService extends View { this.state.set(userId, { tabs: [], history: [], - currentId: '', + currentId: null, }); } @@ -73,6 +91,7 @@ export class NavigationTabsService extends View { private readonly notificationService: NotificationService, private readonly autoSaveService: LocalStorageSaveService, private readonly userInfoResource: UserInfoResource, + private readonly projectsService: ProjectsService, private readonly administrationScreenService: AdministrationScreenService, private readonly appAuthService: AppAuthService ) { @@ -112,6 +131,7 @@ export class NavigationTabsService extends View { typeof value.id === 'string' && typeof value.handlerId === 'string' && typeof value.userId === 'string' + && ['object', 'string'].includes(typeof value.projectId) ) { value.restored = false; } else { @@ -128,7 +148,7 @@ export class NavigationTabsService extends View { map => { for (const [key, value] of Array.from(map.entries())) { if ( - typeof value.currentId !== 'string' + !['object', 'string'].includes(typeof value.currentId) || !Array.isArray(value.history) || !Array.isArray(value.tabs) ) { @@ -140,6 +160,7 @@ export class NavigationTabsService extends View { ); this.userInfoResource.onDataUpdate.addHandler(this.unloadTabs.bind(this)); + this.projectsService.onActiveProjectChange.addHandler(this.resetHistory.bind(this)); } openTab(tab: ITab, isSelected?: boolean): void { @@ -151,14 +172,21 @@ export class NavigationTabsService extends View { } } - selectTab(tabId: string, skipHandlers?: boolean): void { - if (tabId === '') { - this.userTabsState.currentId = ''; - } - if (!this.userTabsState.tabs.includes(tabId)) { + selectTab(tabId: string | null, skipHandlers?: boolean): void { + if (tabId === null) { + this.userTabsState.currentId = null; return; } + + if ( + !this.userTabsState.tabs.includes(tabId) + || !this.tabIdList.includes(tabId) + ) { + return; + } + const tab = this.tabsMap.get(tabId); + if (!tab) { return; } @@ -235,6 +263,10 @@ export class NavigationTabsService extends View { } getView = (): IActiveView | null => { + if (!this.currentTabId) { + return null; + } + const tab = this.getTab(this.currentTabId); if (!tab) { @@ -336,17 +368,24 @@ export class NavigationTabsService extends View { runInAction(() => { this.closeTabSilent(resourceKeyList(removedTabs), true); - const tab = this.tabsMap.get(this.userTabsState.currentId); + if (this.userTabsState.currentId) { + const tab = this.tabsMap.get(this.userTabsState.currentId); - if (tab) { - this.selectTab(this.userTabsState.currentId); - this.onTabSelect.execute(tab); + if (tab) { + this.selectTab(this.userTabsState.currentId); + this.onTabSelect.execute(tab); + } } this.onInit.execute(true); }); } + private resetHistory() { + this.userTabsState.history = []; + this.userTabsState.currentId = null; + } + private async callHandlerCallback(tab: ITab, selector: (handler: TabHandler) => TabHandlerEvent | undefined) { const handler = this.handlers.get(tab.handlerId); diff --git a/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/TabNavigationContext.ts b/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/TabNavigationContext.ts index c0a0974f23..a3a869bf9b 100644 --- a/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/TabNavigationContext.ts +++ b/webapp/packages/plugin-navigation-tabs/src/NavigationTabs/TabNavigationContext.ts @@ -45,7 +45,7 @@ export class TabNavigationContext implements ITabNavigationContext { ) { } openNewTab(options: ITabOptions): ITab { - this._tab = observable({ + this._tab = observable>({ id: uuid(), userId: this.userInfoResource.getId(), restored: true, diff --git a/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts b/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts index 3bd84188c7..a9f5f66f8f 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts +++ b/webapp/packages/plugin-object-viewer/src/ObjectViewerTabService.ts @@ -99,6 +99,7 @@ export class ObjectViewerTabService { const initTab = (): ITab | null => { if (!tabInfo.tab && isSupported()) { tabInfo.openNewTab({ + projectId: nodeInfo.projectId ?? null, handlerId: objectViewerTabHandlerKey, handlerState: { projectId: nodeInfo.projectId, 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 8514e78176..9e10881bd8 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts @@ -115,19 +115,21 @@ export class SqlEditorTabService extends Bootstrap { source, ); - this.sqlDataSourceService.create(handlerState, dataSourceKey, { name, script }); + const datasource = this.sqlDataSourceService.create(handlerState, dataSourceKey, { name, script }); return { id: editorId, + projectId: datasource.executionContext?.projectId ?? null, handlerId: sqlEditorTabHandlerKey, handlerState, }; } - resetConnectionInfo(state: ISqlEditorTabState): void { - const dataSource = this.sqlDataSourceService.get(state.editorId); + resetConnectionInfo(tab: ITab): void { + const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId); dataSource?.setExecutionContext(undefined); + tab.projectId = null; } private async handleConnectionDelete(key: ResourceKey) { @@ -149,7 +151,7 @@ export class SqlEditorTabService extends Bootstrap { ); if (this.connectionInfoResource.includes(key, contextConnection)) { - this.resetConnectionInfo(tab.handlerState); + this.resetConnectionInfo(tab); } } } @@ -221,11 +223,12 @@ export class SqlEditorTabService extends Bootstrap { ); if (!this.connectionInfoResource.has(contextConnection)) { - this.resetConnectionInfo(tab.handlerState); + this.resetConnectionInfo(tab); } } } else { dataSource.setExecutionContext({ ...executionContext.context }); + tab.projectId = executionContext.context.projectId; } } } @@ -252,7 +255,7 @@ export class SqlEditorTabService extends Bootstrap { ResourceKeyUtils.includes(key, dataSource.executionContext!.id) && !this.connectionInfoResource.has(contextConnection) ) { - this.resetConnectionInfo(tab.handlerState); + this.resetConnectionInfo(tab); } } } @@ -296,7 +299,7 @@ export class SqlEditorTabService extends Bootstrap { ); if (!this.connectionInfoResource.has(contextConnection)) { - this.resetConnectionInfo(tab.handlerState); + this.resetConnectionInfo(tab); } } @@ -342,7 +345,13 @@ export class SqlEditorTabService extends Bootstrap { catalogId?: string, schemaId?: string ) { - return await this.sqlEditorService.setConnection(tab.handlerState, connectionKey, catalogId, schemaId); + const state = await this.sqlEditorService.setConnection(tab.handlerState, connectionKey, catalogId, schemaId); + + if (state) { + tab.projectId = connectionKey.projectId; + } + + return state; } private async setObjectCatalogId(containerId: string, tab: ITab) {