CB-2479 feat: display tabs for active projects

This commit is contained in:
Wroud
2022-09-18 16:43:21 +03:00
parent f2fdeef73b
commit a206f8bbe2
9 changed files with 111 additions and 42 deletions
@@ -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>}
+13 -14
View File
@@ -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;
}
@@ -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>
@@ -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>) {