mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-2479 feat: display tabs for active projects
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string, any>;
|
||||
tabList?: string[];
|
||||
enabledBaseActions?: boolean;
|
||||
autoSelect?: boolean;
|
||||
className?: string;
|
||||
onChange?: (tab: ITabData<any>) => void;
|
||||
style?: ComponentStyle;
|
||||
}>;
|
||||
|
||||
@@ -59,8 +62,10 @@ export const TabsBox = forwardRef<HTMLDivElement, TabsBoxProps>(function TabsBox
|
||||
localState,
|
||||
tabList,
|
||||
enabledBaseActions,
|
||||
autoSelect,
|
||||
children,
|
||||
className,
|
||||
onChange,
|
||||
style,
|
||||
}, ref) {
|
||||
return styled(styles, useStyles(style))(
|
||||
@@ -68,7 +73,9 @@ export const TabsBox = forwardRef<HTMLDivElement, TabsBoxProps>(function TabsBox
|
||||
currentTabId={currentTabId}
|
||||
localState={localState}
|
||||
tabList={tabList}
|
||||
autoSelect={autoSelect}
|
||||
enabledBaseActions={enabledBaseActions}
|
||||
onChange={onChange}
|
||||
>
|
||||
<tabs-box ref={ref} as="div" className={className} tabIndex={tabIndex}>
|
||||
{tabs && <tabs>{tabs}</tabs>}
|
||||
|
||||
@@ -53,19 +53,19 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
|
||||
...rest
|
||||
}: Props<T>): 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<T = Record<string, any>>({
|
||||
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<T = Record<string, any>>({
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentTabId !== undefined) {
|
||||
if (currentTabId !== undefined && currentTabId !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -145,7 +144,7 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
|
||||
tabId: state.selectedId!,
|
||||
props,
|
||||
});
|
||||
}, [state.selectedId]);
|
||||
}, [currentTabId, state.selectedId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.selectedId) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
export interface ITabOptions<T = any> {
|
||||
id?: string;
|
||||
projectId: string | null;
|
||||
handlerId: string;
|
||||
handlerState: T;
|
||||
}
|
||||
@@ -15,5 +16,6 @@ export interface ITabOptions<T = any> {
|
||||
export interface ITab<T = any> extends ITabOptions<T> {
|
||||
id: string;
|
||||
userId: string;
|
||||
projectId: string | null;
|
||||
restored: boolean;
|
||||
}
|
||||
|
||||
+7
-1
@@ -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<Props>(function NavigationTabsBar({ cl
|
||||
await navigation.restoreTabs();
|
||||
}
|
||||
|
||||
function handleTabChange(tab: ITabData<any>) {
|
||||
handleSelect(tab.tabId);
|
||||
}
|
||||
|
||||
useExecutor({
|
||||
executor: userInfoResource.onDataUpdate,
|
||||
postHandlers: [unloadTabs, restoreTabs],
|
||||
@@ -90,7 +94,9 @@ export const NavigationTabsBar = observer<Props>(function NavigationTabsBar({ cl
|
||||
tabList={navigation.tabIdList}
|
||||
style={styles}
|
||||
tabIndex={0}
|
||||
autoSelect
|
||||
enabledBaseActions
|
||||
onChange={handleTabChange}
|
||||
>
|
||||
{navigation.tabIdList.map(tabId => (
|
||||
<TabPanel key={tabId} tabId={tabId} lazy>
|
||||
|
||||
+55
-16
@@ -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<ITab> {
|
||||
state = new Map<string, TabsState>();
|
||||
|
||||
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<ITab> {
|
||||
this.state.set(userId, {
|
||||
tabs: [],
|
||||
history: [],
|
||||
currentId: '',
|
||||
currentId: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,6 +91,7 @@ export class NavigationTabsService extends View<ITab> {
|
||||
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<ITab> {
|
||||
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<ITab> {
|
||||
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<ITab> {
|
||||
);
|
||||
|
||||
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<ITab> {
|
||||
}
|
||||
}
|
||||
|
||||
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<ITab> {
|
||||
}
|
||||
|
||||
getView = (): IActiveView<ITab> | null => {
|
||||
if (!this.currentTabId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tab = this.getTab(this.currentTabId);
|
||||
|
||||
if (!tab) {
|
||||
@@ -336,17 +368,24 @@ export class NavigationTabsService extends View<ITab> {
|
||||
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);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export class TabNavigationContext implements ITabNavigationContext {
|
||||
) { }
|
||||
|
||||
openNewTab<T = any>(options: ITabOptions<T>): ITab<T> {
|
||||
this._tab = observable({
|
||||
this._tab = observable<ITab<T>>({
|
||||
id: uuid(),
|
||||
userId: this.userInfoResource.getId(),
|
||||
restored: true,
|
||||
|
||||
@@ -99,6 +99,7 @@ export class ObjectViewerTabService {
|
||||
const initTab = (): ITab<IObjectViewerTabState> | null => {
|
||||
if (!tabInfo.tab && isSupported()) {
|
||||
tabInfo.openNewTab<IObjectViewerTabState>({
|
||||
projectId: nodeInfo.projectId ?? null,
|
||||
handlerId: objectViewerTabHandlerKey,
|
||||
handlerState: {
|
||||
projectId: nodeInfo.projectId,
|
||||
|
||||
@@ -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<ISqlEditorTabState>): void {
|
||||
const dataSource = this.sqlDataSourceService.get(tab.handlerState.editorId);
|
||||
|
||||
dataSource?.setExecutionContext(undefined);
|
||||
tab.projectId = null;
|
||||
}
|
||||
|
||||
private async handleConnectionDelete(key: ResourceKey<IConnectionInfoParams>) {
|
||||
@@ -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<ISqlEditorTabState>) {
|
||||
|
||||
Reference in New Issue
Block a user