refactor: CB-1094 metadata editor

This commit is contained in:
Wroud
2021-09-03 10:16:05 +03:00
parent a074b8c31d
commit d58367b9c3
46 changed files with 603 additions and 688 deletions
@@ -29,11 +29,9 @@ export const NavigationNodeElement = observer<Props>(function NavigationNodeElem
for (const renderer of context.tree.renderers) {
const CustomRenderer = renderer(nodeId);
if (CustomRenderer === undefined) {
continue;
if (CustomRenderer) {
return <CustomRenderer nodeId={nodeId} component={NavigationNodeElement} />;
}
return <CustomRenderer nodeId={nodeId} component={NavigationNodeElement} />;
}
}
+3 -1
View File
@@ -7,11 +7,13 @@ export * from './TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerServic
export * from './TopNavBar/TopNavBarService';
export * from './TopNavBar/AdministrationTopAppBarBootstrapService';
export * from './TopNavBar/shared/topMenuStyles';
export * from './shared/NodesManager/ConnectionDialogsService';
export * from './shared/NodesManager/extensions/IObjectCatalogProvider';
export * from './shared/NodesManager/extensions/IObjectCatalogSetter';
export * from './shared/NodesManager/extensions/IObjectSchemaProvider';
export * from './shared/NodesManager/extensions/IObjectSchemaSetter';
export * from './shared/NodesManager/NavNodeView/IFolderTransform';
export * from './shared/NodesManager/NavNodeView/NavNodeViewService';
export * from './shared/NodesManager/ConnectionDialogsService';
export * from './shared/NodesManager/DBObjectService';
export * from './shared/NodesManager/NavNodeInfoResource';
export * from './shared/NodesManager/NavNodeManagerService';
@@ -80,7 +80,7 @@ export const NavigationTabsBar = observer(function NavigationTabsBar() {
tabIndex={0}
>
{navigation.tabIdList.map(tabId => (
<TabPanel key={tabId} tabId={tabId}>
<TabPanel key={tabId} tabId={tabId} lazy>
<TabHandlerPanel tabId={tabId} />
</TabPanel>
))}
@@ -0,0 +1,25 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 type { ComponentStyle } from '@cloudbeaver/core-theming';
export type NavNodeTransformViewComponent = React.FC<{
nodeId: string;
folderId: string;
style?: ComponentStyle;
}>;
export type NavNodeTransformView = (nodeId: string, folderId: string) => NavNodeTransformViewComponent | undefined;
export type NavNodeFolderTransformFn = (nodeId: string, children: string[] | undefined) => string[] | undefined;
export interface INavNodeFolderTransform {
tab: NavNodeTransformView;
panel: NavNodeTransformView;
transformer: NavNodeFolderTransformFn;
}
@@ -0,0 +1,48 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { injectable } from '@cloudbeaver/core-di';
import { NavTreeResource } from '../NavTreeResource';
import type { NavNodeTransformView, INavNodeFolderTransform, NavNodeFolderTransformFn } from './IFolderTransform';
@injectable()
export class NavNodeViewService {
get tabs(): NavNodeTransformView[] {
return this.transformers.map(transform => transform.tab);
}
get panels(): NavNodeTransformView[] {
return this.transformers.map(transform => transform.panel);
}
get transformations(): NavNodeFolderTransformFn[] {
return this.transformers.map(transform => transform.transformer);
}
private transformers: INavNodeFolderTransform[];
constructor(
private navTreeResource: NavTreeResource
) {
this.transformers = [];
}
getFolders(nodeId: string): string[] | undefined {
const children = this.navTreeResource.get(nodeId);
return this.transformations.reduce(
(children, transform) => transform(nodeId, children),
children
);
}
addTransform(transform: INavNodeFolderTransform): void {
this.transformers.push(transform);
}
}
@@ -107,9 +107,10 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
}, []);
useEffect(() => {
if (currentTabId !== undefined) {
return;
}
// prevent from initial render handlers execution
// if (currentTabId !== undefined) {
// return;
// }
openExecutor.execute({
tabId: state.selectedId!,
@@ -21,6 +21,7 @@ import {
NavigationTreeService,
NavNodeManagerService,
DBObjectService,
NavNodeViewService,
NavNodeExtensionsService,
NavNodeInfoResource,
NavTreeResource,
@@ -193,6 +194,7 @@ export const coreManifest: PluginManifest = {
NavigationTreeContextMenuService,
NavigationTreeService,
NavNodeManagerService,
NavNodeViewService,
NavNodeExtensionsService,
NavNodeInfoResource,
NavTreeResource,
@@ -8,9 +8,9 @@
import { injectable } from '@cloudbeaver/core-di';
import { TaskScheduler } from '@cloudbeaver/core-executor';
import { ResourceKeyUtils } from '@cloudbeaver/core-sdk';
import { MetadataMap } from '@cloudbeaver/core-utils';
import { ResourceKeyUtils } from '../../../core-sdk/src';
import { ConnectionExecutionContext } from './ConnectionExecutionContext';
import { ConnectionExecutionContextResource } from './ConnectionExecutionContextResource';
@@ -0,0 +1,26 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import type { NavNodeTransformViewComponent } from '@cloudbeaver/core-app';
import { Tab, TabIcon, TabTitle } from '@cloudbeaver/core-blocks';
import { useStyles } from '@cloudbeaver/core-theming';
export const DDLViewerTab: NavNodeTransformViewComponent = observer(function DDLViewerTab({
folderId,
style,
}) {
return styled(useStyles(style))(
<Tab tabId={folderId}>
<TabIcon icon="/icons/DDL.svg" />
<TabTitle>DDL</TabTitle>
</Tab>
);
});
@@ -9,7 +9,8 @@
import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { Loader } from '@cloudbeaver/core-blocks';
import type { NavNodeTransformViewComponent } from '@cloudbeaver/core-app';
import { Loader, useTab } from '@cloudbeaver/core-blocks';
import { useController } from '@cloudbeaver/core-di';
import { SQLCodeEditorLoader } from '@cloudbeaver/plugin-sql-editor';
@@ -18,22 +19,20 @@ import { DdlViewerController } from './DdlViewerController';
const styles = css`
wrapper {
flex: 1;
display: flex;
overflow: auto;
composes: theme-typography--body1 from global;
}
SQLCodeEditorLoader {
height: 100%;
flex: 1;
overflow: auto;
}
`;
export interface DdlViewerTabPanelProps {
nodeId: string;
}
export const ddlViewer = (nodeId: string) => (<DdlViewerTabPanel nodeId={nodeId} />);
const DdlViewerTabPanel = observer<DdlViewerTabPanelProps>(function DdlViewerTabPanel({ nodeId }) {
export const DDLViewerTabPanel: NavNodeTransformViewComponent = observer(function DDLViewerTabPanel({ nodeId, folderId }) {
const controller = useController(DdlViewerController, nodeId);
useTab(folderId, () => controller.load());
if (controller.isLoading) {
return <Loader />;
@@ -41,16 +40,14 @@ const DdlViewerTabPanel = observer<DdlViewerTabPanelProps>(function DdlViewerTab
return styled(styles)(
<wrapper>
{controller.metadata && (
<SQLCodeEditorLoader
bindings={{
autoCursor: false,
}}
value={controller.metadata}
dialect={controller.dialect}
readonly
/>
)}
<SQLCodeEditorLoader
bindings={{
autoCursor: false,
}}
value={controller.metadata}
dialect={controller.dialect}
readonly
/>
</wrapper>
);
});
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { observable, when, makeObservable } from 'mobx';
import { observable, makeObservable } from 'mobx';
import { NodeManagerUtils, NavNodeManagerService } from '@cloudbeaver/core-app';
import { IDestructibleController, IInitializableController, injectable } from '@cloudbeaver/core-di';
@@ -37,12 +37,12 @@ export class DdlViewerController implements IInitializableController, IDestructi
});
}
init(nodeId: string) {
init(nodeId: string): void {
this.nodeId = nodeId;
when(
() => !!this.ddlViewerService.getMetadata(nodeId),
() => this.showMetadata(nodeId)
);
}
async load(): Promise<void> {
await this.showMetadata(this.nodeId);
}
destruct(): void {
@@ -51,7 +51,7 @@ export class DdlViewerController implements IInitializableController, IDestructi
private async showMetadata(nodeId: string): Promise<void> {
try {
this.metadata = await this.ddlViewerService.getMetadata(nodeId)!;
this.metadata = await this.ddlViewerService.loadDdlMetadata(nodeId)!;
await this.loadDialect(nodeId);
} catch (error) {
this.notificationService.logException(error, 'Failed to load DDL');
@@ -6,26 +6,47 @@
* you may not use this file except in compliance with the License.
*/
import { EObjectFeature, NavNodeInfoResource, NavNodeViewService } from '@cloudbeaver/core-app';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ITabBuilder, ObjectFoldersService } from '@cloudbeaver/plugin-object-viewer';
import { DdlViewerTabService } from './DdlViewerTabService';
import { DDLViewerTab } from './DdlViewer/DDLViewerTab';
import { DDLViewerTabPanel } from './DdlViewer/DDLViewerTabPanel';
const navNodeDDLId = 'object-viewer://ddl';
@injectable()
export class DdlViewerBootstrap extends Bootstrap {
constructor(
private objectFoldersService: ObjectFoldersService,
private ddlViewerTabService: DdlViewerTabService
private readonly navNodeViewService: NavNodeViewService,
private readonly navNodeInfoResource: NavNodeInfoResource
) {
super();
}
register(): void {
const tabBuilder: ITabBuilder = {
build: nodeId => this.ddlViewerTabService.buildTab(nodeId),
};
this.navNodeViewService.addTransform({
tab: (nodeId, folderId) => {
if (folderId.startsWith(navNodeDDLId)) {
return DDLViewerTab;
}
return undefined;
},
panel: (nodeId, folderId) => {
if (folderId.startsWith(navNodeDDLId)) {
return DDLViewerTabPanel;
}
return undefined;
},
transformer: (nodeId, children) => {
const node = this.navNodeInfoResource.get(nodeId);
this.objectFoldersService.registerTabConstructor(tabBuilder);
if (!node?.objectFeatures?.includes(EObjectFeature.script)) {
return children;
}
return [...children || [], navNodeDDLId];
},
});
}
load(): void {}
@@ -1,65 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 {
EObjectFeature, NavNodeManagerService, INodeNavigationData
} from '@cloudbeaver/core-app';
import type { ITab } from '@cloudbeaver/core-blocks';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { ObjectViewerTabService } from '@cloudbeaver/plugin-object-viewer';
import { ddlViewer } from './DdlViewer/DdlViewer';
import { DdlViewerService } from './DdlViewerService';
const ddlViewerTabId = 'ddl-viewer';
@injectable()
export class DdlViewerTabService {
constructor(
private navNodeManagerService: NavNodeManagerService,
private objectViewerTabService: ObjectViewerTabService,
private notificationService: NotificationService,
private ddlViewerService: DdlViewerService
) {
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
}
buildTab(nodeId: string): ITab | null {
const node = this.navNodeManagerService.getNode(nodeId);
const isDdlRequired = node?.objectFeatures.includes(EObjectFeature.script);
if (!isDdlRequired) {
return null;
}
const ddlTab: ITab = {
tabId: ddlViewerTabId,
title: 'DDL',
icon: '/icons/DDL.svg',
onActivate: () => this.activateDDLTab(nodeId, node?.parentId as string),
panel: () => ddlViewer(nodeId),
};
return ddlTab;
}
private async navigationHandler(data: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>) {
try {
const tabContext = await contexts.getContext(this.objectViewerTabService.objectViewerTabContext);
if (tabContext.nodeInfo.folderId === ddlViewerTabId) {
this.ddlViewerService.loadDdlMetadata(tabContext.nodeInfo.nodeId);
}
} catch (exception) {
this.notificationService.logException(exception, 'Object Viewer Error', 'Error in Object Viewer while processing action with ddl-viewer');
}
}
private activateDDLTab(nodeId: string, parentId: string) {
this.navNodeManagerService.navToNode(nodeId, parentId, ddlViewerTabId);
}
}
@@ -10,7 +10,6 @@ import type { PluginManifest } from '@cloudbeaver/core-di';
import { DdlViewerBootstrap } from './DdlViewerBootstrap';
import { DdlViewerService } from './DdlViewerService';
import { DdlViewerTabService } from './DdlViewerTabService';
export const manifest: PluginManifest = {
info: {
@@ -19,7 +18,6 @@ export const manifest: PluginManifest = {
providers: [
DdlViewerBootstrap,
DdlViewerTabService,
DdlViewerService,
],
};
@@ -0,0 +1,54 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import { NavNodeViewService, useNode } from '@cloudbeaver/core-app';
import { useService } from '@cloudbeaver/core-di';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { ObjectPropertyTable } from './ObjectPropertyTable/ObjectPropertyTable';
interface IFolderPanelRendererProps {
nodeId: string;
folderId: string;
style?: ComponentStyle;
}
export const FolderPanelRenderer = observer<IFolderPanelRendererProps>(function FolderPanelRenderer({
nodeId,
folderId,
style,
}) {
const navNodeViewService = useService(NavNodeViewService);
for (const panel of navNodeViewService.panels) {
const Panel = panel(nodeId, folderId);
if (Panel) {
return <Panel nodeId={nodeId} folderId={folderId} style={style} />;
}
}
return <NavNodePanel nodeId={folderId} style={style} />;
});
interface INavNodePanelProps {
nodeId: string;
style?: ComponentStyle;
}
const NavNodePanel = observer<INavNodePanelProps>(function NavNodeTab({ nodeId }) {
const nodeInfo = useNode(nodeId);
if (!nodeInfo.node) {
return null;
}
return <ObjectPropertyTable objectId={nodeId} parentId={nodeInfo.node.parentId} />;
});
@@ -0,0 +1,55 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { NavNodeViewService, useNode } from '@cloudbeaver/core-app';
import { Tab, TabIcon, TabTitle } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
interface IFolderTabRendererProps {
nodeId: string;
folderId: string;
style?: ComponentStyle;
}
export const FolderTabRenderer = observer<IFolderTabRendererProps>(function FolderTabRenderer({
nodeId,
folderId,
style,
}) {
const navNodeViewService = useService(NavNodeViewService);
for (const tab of navNodeViewService.tabs) {
const Tab = tab(nodeId, folderId);
if (Tab) {
return <Tab nodeId={nodeId} folderId={folderId} style={style} />;
}
}
return <NavNodeTab nodeId={folderId} style={style} />;
});
interface INavNodeTabProps {
nodeId: string;
style?: ComponentStyle;
}
const NavNodeTab = observer<INavNodeTabProps>(function NavNodeTab({ nodeId, style }) {
const nodeInfo = useNode(nodeId);
return styled(useStyles(style))(
<Tab tabId={nodeId}>
{nodeInfo.node?.icon && <TabIcon icon={nodeInfo.node.icon} />}
<TabTitle>{nodeInfo.node?.name}</TabTitle>
</Tab>
);
});
@@ -0,0 +1,17 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 type { NavNodeTransformViewComponent } from '@cloudbeaver/core-app';
import { ObjectProperties } from './ObjectProperties';
export const NavNodeMetadataPanel: NavNodeTransformViewComponent = function NavNodeMetadataPanel({
nodeId,
}) {
return <ObjectProperties objectId={nodeId} />;
};
@@ -0,0 +1,38 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { NavNodeTransformViewComponent, useNode } from '@cloudbeaver/core-app';
import { Tab, TabIcon, TabTitle } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { useStyles } from '@cloudbeaver/core-theming';
export const NavNodeMetadataTab: NavNodeTransformViewComponent = observer(function NavNodeMetadataTab({
folderId,
nodeId,
style,
}) {
const translate = useTranslate();
const styles = useStyles(style);
const nodeInfo = useNode(nodeId);
if (!nodeInfo.node) {
return null;
}
const icon = 'platform:/plugin/org.jkiss.dbeaver.model/icons/tree/info.png';
return styled(styles)(
<Tab tabId={folderId}>
<TabIcon icon={icon} />
<TabTitle>{translate('plugin_object_viewer_object_info_tab')}</TabTitle>
</Tab>
);
});
@@ -0,0 +1,57 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { ENodeFeature, NavNodeInfoResource, NavNodeViewService } from '@cloudbeaver/core-app';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { NavNodeMetadataPanel } from './NavNodeMetadataPanel';
import { NavNodeMetadataTab } from './NavNodeMetadataTab';
const navNodeMetadataId = 'object-viewer://metadata';
@injectable()
export class NavNodeMetadataViewBootstrap extends Bootstrap {
constructor(
private readonly navNodeViewService: NavNodeViewService,
private readonly navNodeInfoResource: NavNodeInfoResource
) {
super();
}
register(): void {
this.navNodeViewService.addTransform({
tab: (nodeId, folderId) => {
if (folderId.startsWith(navNodeMetadataId)) {
return NavNodeMetadataTab;
}
return undefined;
},
panel: (nodeId, folderId) => {
if (folderId.startsWith(navNodeMetadataId)) {
return NavNodeMetadataPanel;
}
return undefined;
},
transformer: (nodeId, children) => {
const node = this.navNodeInfoResource.get(nodeId);
const isDatabaseObject = (
node?.features?.includes(ENodeFeature.item)
|| node?.features?.includes(ENodeFeature.container)
);
if (!isDatabaseObject) {
return children;
}
return [navNodeMetadataId, ...children || []];
},
});
}
load(): void { }
}
@@ -10,8 +10,8 @@ import { observer } from 'mobx-react-lite';
import { FieldCheckboxNew, InputFieldNew } from '@cloudbeaver/core-blocks';
import type { ObjectPropertyProps } from '../formControls';
import { additionalProps, filterProperty, getValue, matchType } from '../helpers';
import type { ObjectPropertyProps } from '../../formControls';
import { additionalProps, filterProperty, getValue, matchType } from '../../helpers';
export const ObjectProperty = observer<ObjectPropertyProps>(function ObjectProperty({
objectProperty,
@@ -0,0 +1,44 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 styled, { css } from 'reshadow';
import { NavNodeInfoResource, NavNodeTransformViewComponent, useChildren } from '@cloudbeaver/core-app';
import { useService } from '@cloudbeaver/core-di';
import { resourceKeyList } from '@cloudbeaver/core-sdk';
import { ObjectChildrenPropertyTable } from '../../ObjectPropertyTable/ObjectChildrenPropertyTable';
import { VirtualFolderUtils } from './VirtualFolderUtils';
const style = css`
tab-wrapper {
position: relative;
width: 100%;
flex: 1 0 auto;
}
`;
export const VirtualFolderPanel: NavNodeTransformViewComponent = function VirtualFolderPanel({
folderId,
nodeId,
}) {
const nodeType = VirtualFolderUtils.getNodeType(folderId);
const navNodeInfoResource = useService(NavNodeInfoResource);
const children = useChildren(nodeId);
const nodeIds = navNodeInfoResource
.get(resourceKeyList(children.children || []))
.filter(node => node?.nodeType === nodeType)
.map(node => node!.id);
return styled(style)(
<tab-wrapper>
<ObjectChildrenPropertyTable nodeIds={nodeIds} />
</tab-wrapper>
);
};
@@ -0,0 +1,31 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 styled from 'reshadow';
import type { NavNodeTransformViewComponent } from '@cloudbeaver/core-app';
import { Tab, TabIcon, TabTitle } from '@cloudbeaver/core-blocks';
import { useStyles } from '@cloudbeaver/core-theming';
import { VirtualFolderUtils } from './VirtualFolderUtils';
export const VirtualFolderTab: NavNodeTransformViewComponent = function VirtualFolderTab({
folderId,
style,
}) {
const nodeType = VirtualFolderUtils.getNodeType(folderId);
const styles = useStyles(style);
const icon = 'platform:/plugin/org.jkiss.dbeaver.model/icons/tree/folder.png';
return styled(styles)(
<Tab tabId={folderId}>
<TabIcon icon={icon} />
<TabTitle>{nodeType}</TabTitle>
</Tab>
);
};
@@ -0,0 +1,23 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export const VirtualFolderUtils = {
prefix: 'object-viewer://virtual-folder/',
getFolderId(nodeType: string): string {
return this.prefix + nodeType;
},
getNodeType(folderId: string): string {
return folderId.replace(this.prefix, '');
},
isVirtualFolder(folderId: string): boolean {
return folderId.startsWith(this.prefix);
},
};
@@ -0,0 +1,69 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { NavNodeInfoResource, NavNodeViewService } from '@cloudbeaver/core-app';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { VirtualFolderPanel } from './VirtualFolderPanel';
import { VirtualFolderTab } from './VirtualFolderTab';
import { VirtualFolderUtils } from './VirtualFolderUtils';
@injectable()
export class VirtualFolderViewBootstrap extends Bootstrap {
constructor(
private readonly navNodeViewService: NavNodeViewService,
private readonly navNodeInfoResource: NavNodeInfoResource
) {
super();
}
register(): void {
this.navNodeViewService.addTransform({
tab: (nodeId, folderId) => {
if (VirtualFolderUtils.isVirtualFolder(folderId)) {
return VirtualFolderTab;
}
return undefined;
},
panel: (nodeId, folderId) => {
if (VirtualFolderUtils.isVirtualFolder(folderId)) {
return VirtualFolderPanel;
}
return undefined;
},
transformer: (nodeId, children) => {
if (!children) {
return children;
}
const virtualFolders: string[] = [];
const nextChildren = [];
for (const child of children) {
const node = this.navNodeInfoResource.get(child);
if (!node || node?.folder) {
nextChildren.push(child);
} else if (node?.nodeType) {
const virtualId = VirtualFolderUtils.getFolderId(node.nodeType);
if (!virtualFolders.includes(virtualId)) {
virtualFolders.push(virtualId);
}
}
}
nextChildren.push(...virtualFolders);
return nextChildren;
},
});
}
load(): void { }
}
@@ -7,15 +7,17 @@
*/
import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import styled, { css } from 'reshadow';
import type { ITab } from '@cloudbeaver/core-app';
import { Tab, TabIcon, TabList, TabPanel, TabsState, TabTitle, verticalTabStyles } from '@cloudbeaver/core-blocks';
import { useController } from '@cloudbeaver/core-di';
import { ITab, NavNodeManagerService, NavNodeViewService, NavTreeResource } from '@cloudbeaver/core-app';
import { ITabData, TabList, TabPanel, TabsState, useMapResource, verticalTabStyles } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { useStyles, composes } from '@cloudbeaver/core-theming';
import type { IObjectViewerTabState } from '../IObjectViewerTabState';
import { ObjectFoldersController } from './ObjectFoldersController';
import { FolderPanelRenderer } from './FolderPanelRenderer';
import { FolderTabRenderer } from './FolderTabRenderer';
const styles = composes(
css`
@@ -46,34 +48,55 @@ const styles = composes(
`
);
const tabStyles = [verticalTabStyles, styles];
interface IProps {
tab: ITab<IObjectViewerTabState>;
}
export const ObjectFolders = observer<IProps>(function ObjectFolders({ tab }) {
const controller = useController(ObjectFoldersController, tab);
const navNodeManagerService = useService(NavNodeManagerService);
const navNodeViewService = useService(NavNodeViewService);
const tabContainer = controller.getTabContainer();
const nodeId = tab.handlerState.objectId;
const parentId = tab.handlerState.parentId;
let folderId = tab.handlerState.folderId;
useMapResource(NavTreeResource, nodeId, {
onLoad: async resource => {
for (const nodeId of tab.handlerState.parents) {
await resource.load(nodeId);
}
},
});
const folders = navNodeViewService.getFolders(nodeId) || [];
function openFolder(tabData: ITabData) {
navNodeManagerService.navToNode(nodeId, parentId, tabData.tabId);
}
useEffect(() => {
if (!folders.includes(folderId) && folders.length > 0) {
navNodeManagerService.navToNode(nodeId, parentId, folders[0]);
}
});
if (!folders.includes(folderId) && folders.length > 0) {
folderId = folders[0];
}
return styled(useStyles(verticalTabStyles, styles))(
<TabsState currentTabId={tabContainer.currentTabId} orientation='vertical'>
<TabsState currentTabId={folderId} orientation='vertical' onChange={openFolder}>
<vertical-tabs>
<TabList aria-label="Object folders">
{tabContainer.tabs.map(tab => (
<Tab
key={tab.tabId}
tabId={tab.tabId}
onOpen={tab.onActivate}
onClose={tab.onClose}
>
{tab.icon && <TabIcon icon={tab.icon} />}
<TabTitle>{tab.title}</TabTitle>
</Tab>
{folders.map(folderId => (
<FolderTabRenderer key={folderId} nodeId={nodeId} folderId={folderId} style={tabStyles} />
))}
</TabList>
{tabContainer.tabs.map(tab => (
<TabPanel key={tab.tabId} tabId={tab.tabId}>
<tab.panel />
{folders.map(folderId => (
<TabPanel key={folderId} tabId={folderId}>
<FolderPanelRenderer key={folderId} nodeId={nodeId} folderId={folderId} style={tabStyles} />
</TabPanel>
))}
</vertical-tabs>
@@ -1,64 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { autorun } from 'mobx';
import { ITab, NavigationTabsService } from '@cloudbeaver/core-app';
import { IDestructibleController, IInitializableController, injectable } from '@cloudbeaver/core-di';
import type { IObjectViewerTabState } from '../IObjectViewerTabState';
import { ObjectFoldersService } from './ObjectFoldersService';
import type { ObjectFoldersTabContainer } from './ObjectFoldersTabsContainer/ObjectFoldersTabContainer';
@injectable()
export class ObjectFoldersController implements IInitializableController, IDestructibleController {
private tabContainer!: ObjectFoldersTabContainer;
private navigationTab!: ITab<IObjectViewerTabState>;
private disposer!: ReturnType<typeof autorun>;
constructor(
private objectFoldersService: ObjectFoldersService,
private navigationTabsService: NavigationTabsService
) { }
getTabContainer(): ObjectFoldersTabContainer {
return this.tabContainer;
}
init(tab: ITab<IObjectViewerTabState>): void {
this.navigationTab = tab;
this.tabContainer = this.objectFoldersService.createTabsContainer(tab.handlerState.objectId);
this.disposer = autorun(() => {
const currentFolderId = tab.handlerState.folderId || null;
if (this.navigationTabsService.currentTabId === tab.id) {
this.activateTab(currentFolderId);
}
});
}
private activateTab(folderId: string | null): void {
try {
this.tabContainer.activateTab(folderId);
} catch {
// no tab with tabId
this.activateFirstTab();
}
}
private activateFirstTab() {
const firstTab = this.tabContainer.tabs[0];
this.tabContainer.activateTab(firstTab?.tabId || null);
}
destruct(): void {
this.disposer();
this.objectFoldersService.destroyTabContainer(this.navigationTab.handlerState.objectId);
}
}
@@ -1,91 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { action, makeObservable } from 'mobx';
import { TabEntity } from '@cloudbeaver/core-app';
import type { ITab } from '@cloudbeaver/core-blocks';
import { Entity, injectable, RootContainerService } from '@cloudbeaver/core-di';
import { ObjectFoldersTabService } from './ObjectFoldersTab/ObjectFoldersTabService';
import { ObjectFoldersTabContainer } from './ObjectFoldersTabsContainer/ObjectFoldersTabContainer';
import { ObjectInfoTabService } from './ObjectInfoTab/ObjectInfoTabService';
import { VirtualFolderTabService } from './VirtualFolderTab/VirtualFolderTabService';
export interface ITabBuilder {
build: (objectId: string) => ITab | ITab[] | null;
}
@injectable()
export class ObjectFoldersService {
private tabBuilders: ITabBuilder[] = [];
// todo temporary solution while only ObjectFolders supports entities
// later it will be integrated in common lifecycle of nested entities
private tabContainersStorage = new Entity();
constructor(
private objectInfoTabService: ObjectInfoTabService,
private virtualFolderTabService: VirtualFolderTabService,
private objectFoldersTabService: ObjectFoldersTabService,
private rootContainerService: RootContainerService
) {
makeObservable(this, {
createTabsContainer: action,
});
this.rootContainerService.registerEntityInRootContainer(this.tabContainersStorage);
}
createTabsContainer(nodeId: string): ObjectFoldersTabContainer {
const tabContainer = new ObjectFoldersTabContainer(nodeId);
this.tabContainersStorage.addChild(tabContainer);
const infoTab = this.objectInfoTabService.createTabEntity(nodeId);
if (infoTab) {
tabContainer.addTabEntity(infoTab);
}
this.objectFoldersTabService.createTabEntities(nodeId)
.forEach(tabEntity => tabContainer.addTabEntity(tabEntity));
this.virtualFolderTabService.createTabEntities(nodeId)
.forEach(tabEntity => tabContainer.addTabEntity(tabEntity));
const tabs = this.getTabs(nodeId);
tabs.forEach(tab => {
const tabEntity = new TabEntity(tab);
tabContainer.addTabEntity(tabEntity);
});
return tabContainer;
}
destroyTabContainer(nodeId: string) {
this.tabContainersStorage.removeChild(nodeId);
}
/**
* return tabs that should be created once for node id
* @param nodeId
*/
getTabs(nodeId: string): ITab[] {
const tabList: ITab[] = [];
this.tabBuilders.forEach(builder => {
const tab = builder.build(nodeId);
if (tab) {
const tabs = Array.isArray(tab) ? tab : [tab];
tabList.push(...tabs);
}
});
return tabList;
}
registerTabConstructor(tabBuilder: ITabBuilder) {
this.tabBuilders.push(tabBuilder);
}
}
@@ -1,34 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 type { ReactElement } from 'react';
import type { NavNode } from '@cloudbeaver/core-app';
import type { ITab } from '@cloudbeaver/core-blocks';
import { objectPropertyTablePanel } from '../ObjectPropertyTable/ObjectPropertyTable';
export class ObjectFolderTabModel implements ITab {
readonly tabId: string;
get title(): string {
return this.node.name || '';
}
get icon(): string | undefined {
return this.node.icon;
}
panel: () => ReactElement | null;
constructor(private node: NavNode,
public onActivate: () => void) {
this.tabId = node.id;
this.panel = () => objectPropertyTablePanel(node.parentId, node.id);
}
}
@@ -1,45 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { NavNodeManagerService, TabEntity } from '@cloudbeaver/core-app';
import type { ITab } from '@cloudbeaver/core-blocks';
import { injectable } from '@cloudbeaver/core-di';
import { ObjectFolderTabModel } from './ObjectFolderTabModel';
@injectable()
export class ObjectFoldersTabService {
constructor(private navNodeManagerService: NavNodeManagerService) {
}
createTabEntities(nodeId: string): TabEntity[] {
return this.tabBuilder(nodeId).map(tab => new TabEntity(tab));
}
private tabBuilder(nodeId: string): ITab[] {
const children = this.navNodeManagerService.getTree(nodeId) || [];
const folderTabs = children.filter(nodeId => {
const node = this.navNodeManagerService.getNode(nodeId);
return node ? node.folder : false;
});
const tabList: ITab[] = folderTabs.map(nodeId => {
const node = this.navNodeManagerService.getNode(nodeId);
if (!node) {
throw Error(`node not found: ${nodeId}`);
}
return new ObjectFolderTabModel(
node,
() => this.navNodeManagerService.navToNode(node.id, node.parentId)
);
});
return tabList;
}
}
@@ -1,13 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export class NodePropertiesMixin {
constructor(
public readonly nodeId: string
) {}
}
@@ -1,19 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { TabContainerEntity } from '@cloudbeaver/core-app';
import { NodePropertiesMixin } from './NodePropertiesMixin';
export class ObjectFoldersTabContainer extends TabContainerEntity {
constructor(nodeId: string) {
super([], nodeId);
const nodePropertiesMixin = new NodePropertiesMixin(nodeId);
this.addMixin(NodePropertiesMixin, nodePropertiesMixin);
}
}
@@ -1,21 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import { useService } from '@cloudbeaver/core-di';
import { NodePropertiesMixin } from '../ObjectFoldersTabsContainer/NodePropertiesMixin';
import { ObjectProperties } from './ObjectProperties';
export const ObjectInfoTab = observer(function ObjectInfoTab() {
const nodePropertiesMixin = useService(NodePropertiesMixin);
return (
<ObjectProperties objectId={nodePropertiesMixin.nodeId} />
);
});
@@ -1,23 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 type { ITab } from '@cloudbeaver/core-blocks';
import { injectable } from '@cloudbeaver/core-di';
import { ObjectInfoTab } from './ObjectInfoTab';
@injectable()
export class ObjectInfoTabModel implements ITab {
readonly tabId = 'infoTab';
title = 'Info';
icon = 'platform:/plugin/org.jkiss.dbeaver.model/icons/tree/info.png';
panel = ObjectInfoTab;
constructor(public onActivate: () => void) { }
}
@@ -1,37 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { TabEntity, NavNodeManagerService, ENodeFeature } from '@cloudbeaver/core-app';
import { injectable } from '@cloudbeaver/core-di';
import { ObjectInfoTabModel } from './ObjectInfoTabModel';
@injectable()
export class ObjectInfoTabService {
constructor(
private navNodeManagerService: NavNodeManagerService
) {}
createTabEntity(navNodeId: string): TabEntity | null {
const node = this.navNodeManagerService.getNode(navNodeId);
const isDatabaseObject = node?.features?.includes(ENodeFeature.item)
|| node?.features?.includes(ENodeFeature.container);
if (!isDatabaseObject) {
return null;
}
return new TabEntity(new ObjectInfoTabModel(
() => {
const node = this.navNodeManagerService.getNode(navNodeId);
if (node) {
this.navNodeManagerService.navToNode(node.id, node.parentId, 'infoTab');
}
}
));
}
}
@@ -57,11 +57,6 @@ export class ObjectPropertiesPageService {
try {
const tabContext = await contexts.getContext(this.objectViewerTabService.objectViewerTabContext);
if (tabContext.tab && !tabContext.tab.handlerState.folderId) {
tabContext.tab.handlerState.folderId = 'infoTab';
}
tabContext.trySwitchPage(this.page);
} catch (exception) {
this.notificationService.logException(exception, 'Object Viewer Error', 'Error in Object Viewer while processing action with database node');
@@ -1,38 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { Loader } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { ObjectChildrenPropertyTable } from '../ObjectPropertyTable/ObjectChildrenPropertyTable';
import { VirtualFolderTabMixin } from './VirtualFolderTabMixin';
const style = css`
tab-wrapper {
position: relative;
width: 100%;
flex: 1 0 auto;
}
`;
export const VirtualFolderTab = observer(function VirtualFolderTab() {
const folderMixin = useService(VirtualFolderTabMixin);
if (!folderMixin.isActivated) {
return <Loader />;
}
return styled(style)(
<tab-wrapper>
<ObjectChildrenPropertyTable nodeIds={folderMixin.getChildrenId()} />
</tab-wrapper>
);
});
@@ -1,15 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export class VirtualFolderTabData {
constructor(
public readonly nodeId: string,
public readonly folderName: string,
public readonly childrenIds: string[]
) {}
}
@@ -1,48 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observable, makeObservable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { VirtualFolderTabData } from './VirtualFolderTabData';
@injectable()
export class VirtualFolderTabMixin {
isActivated = false;
constructor(
private virtualFolderTabData: VirtualFolderTabData,
) {
makeObservable(this, {
isActivated: observable,
});
}
getChildrenId() {
return this.virtualFolderTabData.childrenIds;
}
getTitle(): string {
return this.virtualFolderTabData.folderName;
}
async onActivate() {
if (this.isActivated) {
return;
}
try {
// for (const nodeId of this.getChildrenId()) {
// await this.dbObjectService.load(nodeId);
// }
} finally {
this.isActivated = true;
}
}
}
@@ -1,30 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 type { ITab } from '@cloudbeaver/core-blocks';
import { Entity, injectable } from '@cloudbeaver/core-di';
import { VirtualFolderTab } from './VirtualFolderTab';
import { VirtualFolderTabMixin } from './VirtualFolderTabMixin';
@injectable()
export class VirtualFolderTabModel implements ITab {
readonly tabId: string;
title: string;
icon = 'platform:/plugin/org.jkiss.dbeaver.model/icons/tree/folder.png';
panel = VirtualFolderTab;
onActivate = () => this.virtualFolderTabMixin.onActivate();
constructor(private tabEntity: Entity,
private virtualFolderTabMixin: VirtualFolderTabMixin) {
this.tabId = this.tabEntity.id;
this.title = this.virtualFolderTabMixin.getTitle();
}
}
@@ -1,64 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { NavNodeManagerService, NavNode, TabEntity } from '@cloudbeaver/core-app';
import { injectable, MixinProvider } from '@cloudbeaver/core-di';
import { VirtualFolderTabData } from './VirtualFolderTabData';
import { VirtualFolderTabMixin } from './VirtualFolderTabMixin';
import { VirtualFolderTabModel } from './VirtualFolderTabModel';
@injectable()
export class VirtualFolderTabService {
constructor(private navNodeManagerService: NavNodeManagerService) { }
createTabEntities(nodeId: string): TabEntity[] {
const children = this.navNodeManagerService.getTree(nodeId) || [];
const notFolderNodes = children.reduce<NavNode[]>(
(nodes, nodeId) => {
const node = this.navNodeManagerService.getNode(nodeId);
if (node && !node.folder) {
nodes.push(node);
}
return nodes;
},
[]
);
const virtualFolders = notFolderNodes.reduce<Map<string, VirtualFolderTabData>>(
(map, node) => {
const folderName = node?.nodeType || 'unknown';
let tabData = map.get(folderName);
if (!tabData) {
tabData = new VirtualFolderTabData(nodeId, folderName, []);
map.set(folderName, tabData);
}
tabData.childrenIds.push(node.id);
return map;
},
new Map()
);
return Array.from(virtualFolders.values())
.map(tabData => this.createEntity(tabData));
}
private createEntity(tabData: VirtualFolderTabData): TabEntity {
const mixins: Array<MixinProvider<any>> = [
{
token: VirtualFolderTabData,
value: tabData,
},
VirtualFolderTabMixin,
];
return new TabEntity(VirtualFolderTabModel, mixins);
}
}
@@ -5,6 +5,5 @@ export default manifest;
// All Services and Components that is provided by this plugin should be exported here
export * from './ObjectPage/DBObjectPageService';
export * from './ObjectPage/ObjectPage';
export * from './ObjectPropertiesPage/ObjectFoldersService';
export * from './ObjectViewerTabService';
export * from './IObjectViewerTabState';
@@ -1,5 +1,6 @@
export default [
['plugin_object_viewer_properties_tab', 'Properties'],
['plugin_object_viewer_object_info_tab', 'Info'],
['plugin_object_viewer_table_name', 'Name'],
['plugin_object_viewer_table_no_items', 'There are no items to show'],
['plugin_object_viewer_error', 'Error occurred while tab loading'],
@@ -1,5 +1,6 @@
export default [
['plugin_object_viewer_properties_tab', 'Proprietà'],
['plugin_object_viewer_object_info_tab', 'Informazioni'],
['plugin_object_viewer_table_name', 'Nome'],
['plugin_object_viewer_table_no_items', 'Non ci sono elementi da mostrare'],
['plugin_object_viewer_error', 'Errore durante il caricamento della tab'],
@@ -1,5 +1,6 @@
export default [
['plugin_object_viewer_properties_tab', 'Свойства'],
['plugin_object_viewer_object_info_tab', 'Информация'],
['plugin_object_viewer_table_name', 'Название'],
['plugin_object_viewer_table_no_items', 'Нет объектов для показа'],
['plugin_object_viewer_error', 'Произошла ошибка при загрузке вкладки'],
@@ -10,11 +10,9 @@ import type { PluginManifest } from '@cloudbeaver/core-di';
import { LocaleService } from './LocaleService';
import { DBObjectPageService } from './ObjectPage/DBObjectPageService';
import { ObjectFoldersService } from './ObjectPropertiesPage/ObjectFoldersService';
import { ObjectFoldersTabService } from './ObjectPropertiesPage/ObjectFoldersTab/ObjectFoldersTabService';
import { ObjectInfoTabService } from './ObjectPropertiesPage/ObjectInfoTab/ObjectInfoTabService';
import { NavNodeMetadataViewBootstrap } from './ObjectPropertiesPage/NavNodeView/NavNodeMetadata/NavNodeMetadataViewBootstrap';
import { VirtualFolderViewBootstrap } from './ObjectPropertiesPage/NavNodeView/VirtualFolder/VirtualFolderViewBootstrap';
import { ObjectPropertiesPageService } from './ObjectPropertiesPage/ObjectPropertiesPageService';
import { VirtualFolderTabService } from './ObjectPropertiesPage/VirtualFolderTab/VirtualFolderTabService';
import { ObjectViewerBootstrap } from './ObjectViewerBootstrap';
import { ObjectViewerTabService } from './ObjectViewerTabService';
@@ -23,12 +21,10 @@ export const manifest: PluginManifest = {
providers: [
ObjectViewerBootstrap,
NavNodeMetadataViewBootstrap,
VirtualFolderViewBootstrap,
ObjectPropertiesPageService,
ObjectFoldersService,
ObjectViewerTabService,
ObjectInfoTabService,
ObjectFoldersTabService,
VirtualFolderTabService,
DBObjectPageService,
LocaleService,
],