mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
feat: CB-1216 CB-1217 CB-1239 metadata editor update
This commit is contained in:
@@ -31,16 +31,11 @@ interface INavigationNode {
|
||||
filterValue: string;
|
||||
}
|
||||
|
||||
export function useNavigationNode({ id }: NavNode): INavigationNode {
|
||||
export function useNavigationNode(node: NavNode): INavigationNode {
|
||||
const contextRef = useObjectRef({
|
||||
context: useContext(TreeContext),
|
||||
});
|
||||
const { node, isLoading } = useNode(id);
|
||||
|
||||
// TODO: hack to provide actual node information
|
||||
if (!node) {
|
||||
throw new Error('Node should exists');
|
||||
}
|
||||
const { isLoading } = useNode(node.id);
|
||||
|
||||
const children = useChildren(node.id);
|
||||
const loading = isLoading() || children.isLoading();
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ export const ConnectionSelector = observer(function ConnectionSelector() {
|
||||
const style = useStyles(styles);
|
||||
const controller = useController(ConnectionSelectorController);
|
||||
const isEnabled = usePermission(EPermission.public);
|
||||
const driver = useMapResource(DBDriverResource, null, { onLoad: resource => isEnabled && resource.loadAll() });
|
||||
const driver = useMapResource(DBDriverResource, null, { onLoad: resource => { isEnabled && resource.loadAll(); } });
|
||||
const ConnectionMenu = TopMenuItem;
|
||||
const SchemaOrCatalogMenu = TopMenuItem;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ 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/DBObjectResource';
|
||||
export * from './shared/NodesManager/NavNodeContextMenuService';
|
||||
export * from './shared/NodesManager/NavNodeInfoResource';
|
||||
export * from './shared/NodesManager/NavNodeManagerService';
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ import type { DBObject } from './EntityTypes';
|
||||
import { NavNodeInfoResource } from './NavNodeInfoResource';
|
||||
|
||||
@injectable()
|
||||
export class DBObjectService extends CachedMapResource<string, DBObject> {
|
||||
export class DBObjectResource extends CachedMapResource<string, DBObject> {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private navNodeInfoResource: NavNodeInfoResource
|
||||
@@ -195,7 +195,7 @@ export class NavNodeContextMenuService extends Bootstrap {
|
||||
onClick: async context => {
|
||||
const node = context.data.node;
|
||||
try {
|
||||
await this.navNodeManagerService.refreshTree(node.id);
|
||||
await this.navNodeManagerService.refreshTree(node.parentId);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Failed to refresh node');
|
||||
}
|
||||
@@ -227,14 +227,10 @@ export class NavNodeContextMenuService extends Bootstrap {
|
||||
|
||||
if (result !== DialogueStateResult.Rejected && result !== DialogueStateResult.Resolved) {
|
||||
if (name !== result) {
|
||||
const notification = this.notificationService.processNotification(() => ProcessSnackbar, {}, { title: 'ui_rename_processing' });
|
||||
try {
|
||||
await this.navTreeResource.changeName(node, result);
|
||||
|
||||
const message = `prev: ${name}\nnew: ${result}`;
|
||||
notification.controller.resolve(`${node.nodeType} was renamed`, message);
|
||||
} catch (exception) {
|
||||
notification.controller.reject(exception, 'Error occured while renaming');
|
||||
this.notificationService.logException(exception, 'Error occurred while renaming');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
|
||||
}
|
||||
|
||||
async changeName(node: NavNode, name: string): Promise<void> {
|
||||
await this.performUpdate(node.id, [], async () => {
|
||||
await this.performUpdate(resourceKeyList([node.id, node.parentId]), [], async () => {
|
||||
await this.graphQLService.sdk.navRenameNode({
|
||||
nodePath: node.id,
|
||||
newName: name,
|
||||
@@ -213,10 +213,11 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
|
||||
for (const id of items) {
|
||||
this.data.delete(id);
|
||||
}
|
||||
|
||||
const allKeys = resourceKeyList(items);
|
||||
this.markUpdated(allKeys);
|
||||
this.onItemDelete.execute(allKeys);
|
||||
this.navNodeInfoResource.delete(ResourceKeyUtils.exclude(allKeys, key));
|
||||
this.navNodeInfoResource.delete(allKeys);
|
||||
}
|
||||
|
||||
protected async loader(key: ResourceKey<string>): Promise<Map<string, string[]>> {
|
||||
@@ -263,7 +264,7 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
|
||||
const connectionInfo = this.connectionInfo.get(key);
|
||||
|
||||
if (!connectionInfo?.connected) {
|
||||
this.delete(nodeId);
|
||||
this.delete(resourceKeyList(this.get(nodeId) || []));
|
||||
} else {
|
||||
await this.markTreeOutdated(nodeId);
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
|
||||
import { DBObjectService } from './DBObjectService';
|
||||
import { DBObjectResource } from './DBObjectResource';
|
||||
|
||||
export function useDatabaseObjectInfo(navNodeId: string) {
|
||||
const dbObjectService = useService(DBObjectService);
|
||||
const dbObject = dbObjectService.get(navNodeId);
|
||||
const isLoading = dbObjectService.isDataLoading(navNodeId);
|
||||
const isLoaded = dbObjectService.isLoaded(navNodeId);
|
||||
const dbObjectResource = useService(DBObjectResource);
|
||||
const dbObject = dbObjectResource.get(navNodeId);
|
||||
const isLoading = dbObjectResource.isDataLoading(navNodeId);
|
||||
const isLoaded = dbObjectResource.isLoaded(navNodeId);
|
||||
|
||||
return { dbObject, isLoading, isLoaded };
|
||||
}
|
||||
|
||||
@@ -94,17 +94,18 @@ interface Props {
|
||||
message?: string;
|
||||
exception?: Error;
|
||||
inline?: boolean;
|
||||
className?: string;
|
||||
onRetry?: () => void;
|
||||
}
|
||||
|
||||
export const ExceptionMessage = observer<Props>(function ExceptionMessage({
|
||||
name, message, exception = null, inline, onRetry,
|
||||
name, message, exception = null, inline, className, onRetry,
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
const error = useErrorDetails(exception);
|
||||
|
||||
return styled(styles)(
|
||||
<error {...use({ inline })}>
|
||||
<error {...use({ inline })} className={className}>
|
||||
<error-icon><IconOrImage icon={inline ? '/icons/error_icon_sm.svg' : '/icons/error_icon.svg'} /></error-icon>
|
||||
<error-data>
|
||||
<error-name><span>{name || error.details?.name}</span></error-name>
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useState, useEffect } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import { Translate } from '@cloudbeaver/core-localization';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { Button } from '../Button';
|
||||
import { ExceptionMessage } from '../ExceptionMessage';
|
||||
@@ -49,6 +49,7 @@ interface Props {
|
||||
className?: string;
|
||||
fullSize?: boolean;
|
||||
state?: LoaderState | LoaderState[];
|
||||
style?: ComponentStyle;
|
||||
children?: () => React.ReactNode;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
@@ -72,6 +73,7 @@ export const Loader = observer<Props>(function Loader({
|
||||
loading = true,
|
||||
inlineException,
|
||||
state,
|
||||
style,
|
||||
children,
|
||||
onCancel,
|
||||
}) {
|
||||
@@ -111,7 +113,7 @@ export const Loader = observer<Props>(function Loader({
|
||||
}
|
||||
}
|
||||
|
||||
const style = useStyles(loaderStyles, overlay && overlayStyles);
|
||||
style = useStyles(loaderStyles, style, overlay && overlayStyles);
|
||||
const [isVisible, setVisible] = useState(loading);
|
||||
const spinnerURL = (secondary || overlay) ? spinnerType.secondary : spinnerType.primary;
|
||||
|
||||
@@ -128,7 +130,9 @@ export const Loader = observer<Props>(function Loader({
|
||||
}, [loading]);
|
||||
|
||||
if (exception && !loading) {
|
||||
return <ExceptionMessage exception={exception} inline={inline || inlineException} onRetry={reload} />;
|
||||
return styled(style)(
|
||||
<ExceptionMessage exception={exception} inline={inline || inlineException} onRetry={reload} />
|
||||
);
|
||||
}
|
||||
|
||||
if (children && (!loader || !loading)) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { IServiceConstructor, useService } from '@cloudbeaver/core-di';
|
||||
@@ -13,15 +14,15 @@ import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { CachedResourceIncludeArgs, CachedMapResource, CachedMapResourceGetter, ResourceKey, CachedMapResourceValue, CachedMapResourceKey, CachedMapResourceArguments, CachedMapResourceLoader, ResourceKeyList, CachedMapResourceListGetter } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { ILoadableState } from './Loader/Loader';
|
||||
import { useObjectRef } from './useObjectRef';
|
||||
import { useObservableRef } from './useObservableRef';
|
||||
|
||||
interface IActions<
|
||||
TKeyArg extends ResourceKey<CachedMapResourceKey<TResource>>,
|
||||
TResource extends CachedMapResource<any, any, any>,
|
||||
TIncludes
|
||||
> {
|
||||
isActive?: () => Promise<boolean> | boolean;
|
||||
onLoad?: (resource: TResource) => Promise<any> | any;
|
||||
isActive?: (resource: TResource) => Promise<boolean> | boolean;
|
||||
onLoad?: (resource: TResource) => Promise<boolean | void> | boolean | void;
|
||||
onData?: (
|
||||
data: CachedMapResourceLoader<
|
||||
TKeyArg,
|
||||
@@ -129,7 +130,7 @@ export function useMapResource<
|
||||
includes = keyObj.includes;
|
||||
}
|
||||
|
||||
const refObj = useObjectRef(() => ({
|
||||
const refObj = useObservableRef(() => ({
|
||||
loading: false,
|
||||
prevData: undefined as CachedMapResourceLoader<
|
||||
TKeyArg,
|
||||
@@ -137,8 +138,52 @@ export function useMapResource<
|
||||
CachedMapResourceValue<TResource>,
|
||||
TIncludes
|
||||
> | undefined,
|
||||
load: () => {},
|
||||
async load() {
|
||||
const { resource, actions, prevData, key, includes } = this;
|
||||
|
||||
const active = await actions?.isActive?.(resource);
|
||||
|
||||
if (this.loading || active === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const prevent = await actions?.onLoad?.(resource);
|
||||
|
||||
if (key === null || prevent === true) {
|
||||
setException(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const newData = await resource.load(key, includes as any);
|
||||
setException(null);
|
||||
|
||||
try {
|
||||
await actions?.onData?.(
|
||||
newData,
|
||||
resource,
|
||||
prevData
|
||||
);
|
||||
} finally {
|
||||
this.prevData = newData;
|
||||
}
|
||||
} catch (exception) {
|
||||
if (resource.getException(key) === null) {
|
||||
setException(exception);
|
||||
}
|
||||
actions?.onError?.(exception);
|
||||
if (!this.exceptionObserved) {
|
||||
notifications.logException(exception, 'Can\'t load data');
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
}), {
|
||||
loading: observable.ref,
|
||||
}, {
|
||||
exceptionObserved: false,
|
||||
resource,
|
||||
key,
|
||||
@@ -149,49 +194,6 @@ export function useMapResource<
|
||||
|
||||
const outdated = resource.isOutdated(key);
|
||||
|
||||
refObj.load = async function load() {
|
||||
const { loading, resource, actions, prevData } = refObj;
|
||||
|
||||
const active = await actions?.isActive?.();
|
||||
|
||||
if (loading || active === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
await actions?.onLoad?.(resource);
|
||||
|
||||
if (key === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newData = await resource.load(key, includes as any);
|
||||
setException(null);
|
||||
|
||||
try {
|
||||
await actions?.onData?.(
|
||||
newData,
|
||||
resource,
|
||||
prevData
|
||||
);
|
||||
} finally {
|
||||
refObj.prevData = newData;
|
||||
}
|
||||
} catch (exception) {
|
||||
if (resource.getException(key) === null) {
|
||||
setException(exception);
|
||||
}
|
||||
actions?.onError?.(exception);
|
||||
if (!refObj.exceptionObserved) {
|
||||
notifications.logException(exception, 'Can\'t load data');
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const [result] = useState<
|
||||
IMapResourceResult<TResource, TIncludes>
|
||||
| IMapResourceListResult<TResource, TIncludes>
|
||||
@@ -226,7 +228,7 @@ export function useMapResource<
|
||||
return false;
|
||||
}
|
||||
|
||||
return resource.isDataLoading(refObj.key);
|
||||
return refObj.loading || resource.isDataLoading(refObj.key);
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
+7
-1
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { SessionDataResource } from '@cloudbeaver/core-root';
|
||||
import { EPermission, PermissionsResource, SessionDataResource } from '@cloudbeaver/core-root';
|
||||
import {
|
||||
GraphQLService,
|
||||
CachedMapResource,
|
||||
@@ -30,6 +30,7 @@ export class ConnectionExecutionContextResource extends CachedMapResource<string
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private connectionInfoResource: ConnectionInfoResource,
|
||||
private permissionsResource: PermissionsResource,
|
||||
sessionDataResource: SessionDataResource
|
||||
) {
|
||||
super();
|
||||
@@ -39,6 +40,7 @@ export class ConnectionExecutionContextResource extends CachedMapResource<string
|
||||
sessionDataResource.onDataUpdate.addHandler(async () => {
|
||||
await this.load(ConnectionExecutionContextResource.keyAll);
|
||||
});
|
||||
this.permissionsResource.onDataOutdated.addHandler(this.markOutdated.bind(this));
|
||||
connectionInfoResource.onItemAdd.addHandler(this.updateConnectionContexts.bind(this));
|
||||
connectionInfoResource.onItemDelete.addHandler(this.deleteConnectionContexts.bind(this));
|
||||
|
||||
@@ -168,6 +170,10 @@ export class ConnectionExecutionContextResource extends CachedMapResource<string
|
||||
protected async loader(
|
||||
key: ResourceKey<string>
|
||||
): Promise<Map<string, IConnectionExecutionContextInfo>> {
|
||||
if (!this.permissionsResource.has(EPermission.public)) {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
const all = ResourceKeyUtils.hasMark(key, ConnectionExecutionContextResource.keyAll.mark);
|
||||
|
||||
await ResourceKeyUtils.forEachAsync(all ? ConnectionExecutionContextResource.keyAll : key, async contextId => {
|
||||
|
||||
+2
-2
@@ -75,11 +75,11 @@ export const ConnectionAccess: TabContainerPanelComponent<IConnectionFormProps>
|
||||
|| state.initialGrantedSubjects.some(subject => !state.grantedSubjects.includes(subject));
|
||||
|
||||
const users = useMapResource(UsersResource, null, {
|
||||
onLoad: resource => resource.loadAll(),
|
||||
onLoad: resource => { resource.loadAll(); },
|
||||
});
|
||||
|
||||
const roles = useMapResource(RolesResource, null, {
|
||||
onLoad: resource => resource.loadAll(),
|
||||
onLoad: resource => { resource.loadAll(); },
|
||||
});
|
||||
|
||||
const grantedUsers = useMemo(() => computed(() => users.resource.values
|
||||
|
||||
@@ -107,6 +107,9 @@ export abstract class CachedMapResource<
|
||||
getException(key: ResourceKeyList<TKey>): Array<Error | null>;
|
||||
getException(key: ResourceKey<TKey>): Array<Error | null>| Error | null;
|
||||
getException(key: ResourceKey<TKey>): Array<Error | null>| Error | null {
|
||||
if (ResourceKeyUtils.some(key, key => !this.has(key))) { // TODO: metadata isn't properly cleared while rename with opened in Metadata Editor node
|
||||
return null;
|
||||
}
|
||||
key = this.transformParam(key);
|
||||
return ResourceKeyUtils.map(key, key => this.metadata.get(key).exception);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ export interface ResourceKeyUtils {
|
||||
map: MapFnc;
|
||||
includes: <TKey>(first: ResourceKey<TKey>, second: ResourceKey<TKey>) => boolean;
|
||||
exclude: <TKey>(first: ResourceKeyList<TKey>, second: ResourceKey<TKey>) => ResourceKey<TKey>;
|
||||
join: <TKey>(first: ResourceKey<TKey>, second: ResourceKey<TKey>) => ResourceKey<TKey>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
@@ -151,6 +152,24 @@ export const ResourceKeyUtils: ResourceKeyUtils = {
|
||||
|
||||
return resourceKeyList(param.list.filter(param => param !== key), param.mark);
|
||||
},
|
||||
|
||||
join<TKey>(param: ResourceKey<TKey>, key: ResourceKey<TKey>): ResourceKey<TKey> {
|
||||
const list: TKey[] = [];
|
||||
|
||||
if (isResourceKeyList(param)) {
|
||||
list.push(...param.list);
|
||||
} else {
|
||||
list.push(param);
|
||||
}
|
||||
|
||||
if (isResourceKeyList(key)) {
|
||||
list.push(...key.list);
|
||||
} else {
|
||||
list.push(key);
|
||||
}
|
||||
|
||||
return resourceKeyList(list);
|
||||
},
|
||||
};
|
||||
|
||||
export function isResourceKeyList<T>(data: any): data is ResourceKeyList<T> {
|
||||
|
||||
@@ -77,7 +77,7 @@ export class DataViewerTabService {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = await this.navNodeManagerService.loadNode({
|
||||
const node = this.navNodeManagerService.getNode({
|
||||
nodeId: tab.handlerState.objectId,
|
||||
parentId: tab.handlerState.parentId,
|
||||
});
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useEffect } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import type { NavNodeTransformViewComponent } from '@cloudbeaver/core-app';
|
||||
import { Loader, useTab } from '@cloudbeaver/core-blocks';
|
||||
import { Loader } from '@cloudbeaver/core-blocks';
|
||||
import { useController } from '@cloudbeaver/core-di';
|
||||
import { SQLCodeEditorLoader } from '@cloudbeaver/plugin-sql-editor';
|
||||
|
||||
@@ -32,7 +33,12 @@ const styles = css`
|
||||
|
||||
export const DDLViewerTabPanel: NavNodeTransformViewComponent = observer(function DDLViewerTabPanel({ nodeId, folderId }) {
|
||||
const controller = useController(DdlViewerController, nodeId);
|
||||
useTab(folderId, () => controller.load());
|
||||
|
||||
useEffect(() => {
|
||||
controller.load();
|
||||
});
|
||||
// TODO: not triggered in switch case with lazy
|
||||
// useTab(folderId, () => controller.load());
|
||||
|
||||
if (controller.isLoading) {
|
||||
return <Loader />;
|
||||
|
||||
+15
-2
@@ -9,9 +9,10 @@
|
||||
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 { DBObjectResource, NavNodeViewService, useChildren, useNode } from '@cloudbeaver/core-app';
|
||||
import { getComputed, Loader, Tab, TabIcon, TabTitle, useStateDelay } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
interface IFolderTabRendererProps {
|
||||
@@ -45,11 +46,23 @@ interface INavNodeTabProps {
|
||||
|
||||
const NavNodeTab = observer<INavNodeTabProps>(function NavNodeTab({ nodeId, style }) {
|
||||
const nodeInfo = useNode(nodeId);
|
||||
const children = useChildren(nodeId);
|
||||
const dbObjectResource = useService(DBObjectResource);
|
||||
const childrenList = resourceKeyList(children.children || []);
|
||||
|
||||
const loading = useStateDelay(getComputed(() => (
|
||||
nodeInfo.isLoaded() && nodeInfo.isLoading()
|
||||
) || (
|
||||
children.isLoaded() && children.isLoading()
|
||||
) || (
|
||||
dbObjectResource.isLoaded(childrenList) && dbObjectResource.isDataLoading(childrenList)
|
||||
)), 300);
|
||||
|
||||
return styled(useStyles(style))(
|
||||
<Tab tabId={nodeId}>
|
||||
{nodeInfo.node?.icon && <TabIcon icon={nodeInfo.node.icon} />}
|
||||
<TabTitle>{nodeInfo.node?.name}</TabTitle>
|
||||
<tab-loader><Loader loading={loading} small /></tab-loader>
|
||||
</Tab>
|
||||
);
|
||||
});
|
||||
|
||||
+37
-36
@@ -9,8 +9,8 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { useDatabaseObjectInfo } from '@cloudbeaver/core-app';
|
||||
import { ColoredContainer, Loader, TextPlaceholder, useObjectPropertyCategories, GroupTitle, ObjectPropertyInfoFormNew, Group } from '@cloudbeaver/core-blocks';
|
||||
import { DBObjectResource } from '@cloudbeaver/core-app';
|
||||
import { ColoredContainer, Loader, TextPlaceholder, useObjectPropertyCategories, GroupTitle, ObjectPropertyInfoFormNew, Group, useMapResource } from '@cloudbeaver/core-blocks';
|
||||
import { BASE_CONTAINERS_STYLES } from '@cloudbeaver/core-blocks';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
|
||||
@@ -26,42 +26,43 @@ export const ObjectProperties = observer<Props>(function ObjectProperties({
|
||||
objectId,
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
const { dbObject, isLoading } = useDatabaseObjectInfo(objectId);
|
||||
const dbObject = useMapResource(DBObjectResource, objectId);
|
||||
const styles = useStyles(BASE_CONTAINERS_STYLES);
|
||||
const { categories, isUncategorizedExists } = useObjectPropertyCategories(dbObject?.properties ?? emptyArray);
|
||||
const properties = dbObject?.properties;
|
||||
|
||||
if (!properties && isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (!properties || properties.length === 0) {
|
||||
return <TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>;
|
||||
}
|
||||
const { categories, isUncategorizedExists } = useObjectPropertyCategories(dbObject.data?.properties ?? emptyArray);
|
||||
const properties = dbObject.data?.properties;
|
||||
|
||||
return styled(styles)(
|
||||
<ColoredContainer overflow parent gap>
|
||||
{isUncategorizedExists && (
|
||||
<Group gap large>
|
||||
<ObjectPropertyInfoFormNew
|
||||
properties={properties}
|
||||
category={null}
|
||||
small
|
||||
readOnly
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
{categories.map(category => (
|
||||
<Group key={category} gap large>
|
||||
<GroupTitle>{category}</GroupTitle>
|
||||
<ObjectPropertyInfoFormNew
|
||||
properties={properties}
|
||||
category={category}
|
||||
small
|
||||
readOnly
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
</ColoredContainer>
|
||||
<Loader state={dbObject}>{() => styled(styles)(
|
||||
<>
|
||||
{!properties || properties.length === 0 ? (
|
||||
<TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>
|
||||
) : (
|
||||
<ColoredContainer overflow parent gap>
|
||||
{isUncategorizedExists && (
|
||||
<Group gap large>
|
||||
<ObjectPropertyInfoFormNew
|
||||
properties={properties}
|
||||
category={null}
|
||||
small
|
||||
readOnly
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
{categories.map(category => (
|
||||
<Group key={category} gap large>
|
||||
<GroupTitle>{category}</GroupTitle>
|
||||
<ObjectPropertyInfoFormNew
|
||||
properties={properties}
|
||||
category={category}
|
||||
small
|
||||
readOnly
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
</ColoredContainer>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Loader>
|
||||
);
|
||||
});
|
||||
|
||||
+18
-6
@@ -8,7 +8,8 @@
|
||||
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { NavNodeInfoResource, NavNodeTransformViewComponent, useChildren } from '@cloudbeaver/core-app';
|
||||
import { DBObjectResource, NavNodeInfoResource, NavNodeTransformViewComponent, NavTreeResource } from '@cloudbeaver/core-app';
|
||||
import { Loader, useMapResource } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
@@ -29,16 +30,27 @@ export const VirtualFolderPanel: NavNodeTransformViewComponent = function Virtua
|
||||
}) {
|
||||
const nodeType = VirtualFolderUtils.getNodeType(folderId);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const children = useChildren(nodeId);
|
||||
const tree = useMapResource(NavTreeResource, nodeId);
|
||||
const key = resourceKeyList([nodeId, ...tree.data || []]);
|
||||
const dbObject = useMapResource(DBObjectResource, key, {
|
||||
async onLoad(resource: DBObjectResource) {
|
||||
await resource.loadChildren(nodeId, key);
|
||||
},
|
||||
});
|
||||
|
||||
const nodeIds = navNodeInfoResource
|
||||
.get(resourceKeyList(children.children || []))
|
||||
.get(resourceKeyList(tree.data || []))
|
||||
.filter(node => node?.nodeType === nodeType)
|
||||
.map(node => node!.id);
|
||||
|
||||
return styled(style)(
|
||||
<tab-wrapper>
|
||||
<ObjectChildrenPropertyTable nodeIds={nodeIds} />
|
||||
</tab-wrapper>
|
||||
<Loader state={[tree, dbObject]}>{() => styled(style)(
|
||||
<>
|
||||
<tab-wrapper>
|
||||
<ObjectChildrenPropertyTable nodeIds={nodeIds} />
|
||||
</tab-wrapper>
|
||||
</>
|
||||
)}
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,8 +11,9 @@ import { useEffect } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { ITab, NavNodeManagerService, NavNodeViewService, NavTreeResource } from '@cloudbeaver/core-app';
|
||||
import { ITabData, TabList, TabPanel, TabsState, useMapResource, verticalTabStyles } from '@cloudbeaver/core-blocks';
|
||||
import { ITabData, Loader, TabList, TabPanel, TabsState, TextPlaceholder, useMapResource, verticalTabStyles } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import type { IObjectViewerTabState } from '../IObjectViewerTabState';
|
||||
@@ -45,6 +46,19 @@ const styles = composes(
|
||||
TabList {
|
||||
border-right: 1px solid;
|
||||
}
|
||||
TabTitle {
|
||||
flex: 1;
|
||||
}
|
||||
tab-loader {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
ExceptionMessage {
|
||||
padding: 24px;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@@ -55,20 +69,16 @@ interface IProps {
|
||||
}
|
||||
|
||||
export const ObjectFolders = observer<IProps>(function ObjectFolders({ tab }) {
|
||||
const translate = useTranslate();
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const navNodeViewService = useService(NavNodeViewService);
|
||||
const style = useStyles(verticalTabStyles, styles);
|
||||
|
||||
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 children = useMapResource(NavTreeResource, nodeId);
|
||||
|
||||
const folders = navNodeViewService.getFolders(nodeId) || [];
|
||||
|
||||
@@ -86,20 +96,29 @@ export const ObjectFolders = observer<IProps>(function ObjectFolders({ tab }) {
|
||||
folderId = folders[0];
|
||||
}
|
||||
|
||||
return styled(useStyles(verticalTabStyles, styles))(
|
||||
<TabsState currentTabId={folderId} orientation='vertical' onChange={openFolder}>
|
||||
<vertical-tabs>
|
||||
<TabList aria-label="Object folders">
|
||||
{folders.map(folderId => (
|
||||
<FolderTabRenderer key={folderId} nodeId={nodeId} folderId={folderId} style={tabStyles} />
|
||||
))}
|
||||
</TabList>
|
||||
{folders.map(folderId => (
|
||||
<TabPanel key={folderId} tabId={folderId}>
|
||||
<FolderPanelRenderer key={folderId} nodeId={nodeId} folderId={folderId} style={tabStyles} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</vertical-tabs>
|
||||
</TabsState>
|
||||
return styled(style)(
|
||||
<Loader state={children} style={style}>{() => styled(style)(
|
||||
<>
|
||||
{folders.length > 0 ? (
|
||||
<TabsState currentTabId={folderId} orientation='vertical' lazy onChange={openFolder}>
|
||||
<vertical-tabs>
|
||||
<TabList aria-label="Object folders">
|
||||
{folders.map(folderId => (
|
||||
<FolderTabRenderer key={folderId} nodeId={nodeId} folderId={folderId} style={tabStyles} />
|
||||
))}
|
||||
</TabList>
|
||||
{folders.map(folderId => (
|
||||
<TabPanel key={folderId} tabId={folderId}>
|
||||
<FolderPanelRenderer key={folderId} nodeId={nodeId} folderId={folderId} style={tabStyles} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</vertical-tabs>
|
||||
</TabsState>
|
||||
) : (
|
||||
<TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Loader>
|
||||
);
|
||||
});
|
||||
|
||||
-10
@@ -37,16 +37,6 @@ export const ObjectPropertiesPagePanel: ObjectPagePanelComponent = observer(func
|
||||
tab,
|
||||
}) {
|
||||
const style = useStyles(viewerStyles);
|
||||
const translate = useTranslate();
|
||||
const { children, isLoading } = useChildren(tab.handlerState.objectId);
|
||||
|
||||
if (!children && isLoading()) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (!children) {
|
||||
return <TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>;
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
<wrapper>
|
||||
|
||||
+25
-16
@@ -9,12 +9,12 @@
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { useChildren } from '@cloudbeaver/core-app';
|
||||
import { Loader, TextPlaceholder } from '@cloudbeaver/core-blocks';
|
||||
import { DBObjectResource, NavTreeResource } from '@cloudbeaver/core-app';
|
||||
import { Loader, TextPlaceholder, useMapResource } from '@cloudbeaver/core-blocks';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
import { css } from '@reshadow/react';
|
||||
|
||||
import { useObjectFolder } from '../../useObjectFolder';
|
||||
import { ObjectChildrenPropertyTable } from './ObjectChildrenPropertyTable';
|
||||
|
||||
const styles = css`
|
||||
@@ -22,6 +22,9 @@ const styles = css`
|
||||
flex: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
ExceptionMessage {
|
||||
padding: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface ObjectPropertyTableProps {
|
||||
@@ -34,21 +37,27 @@ export const ObjectPropertyTable = observer<ObjectPropertyTableProps>(function O
|
||||
parentId,
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
const children = useChildren(objectId);
|
||||
const { isLoading } = useObjectFolder(objectId);
|
||||
|
||||
if ((!children.children && children.isLoading()) || isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (!children?.children || !children.children.length) {
|
||||
return <TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>;
|
||||
}
|
||||
const tree = useMapResource(NavTreeResource, objectId);
|
||||
const key = resourceKeyList([objectId, ...tree.data || []]);
|
||||
const dbObject = useMapResource(DBObjectResource, key, {
|
||||
async onLoad(resource: DBObjectResource) {
|
||||
await resource.loadChildren(objectId, key);
|
||||
},
|
||||
});
|
||||
|
||||
return styled(styles)(
|
||||
<div>
|
||||
<ObjectChildrenPropertyTable nodeIds={children.children} />
|
||||
</div>
|
||||
<Loader state={[tree, dbObject]} style={styles}>{() => styled(styles)(
|
||||
<>
|
||||
{!tree.data || tree.data.length === 0 ? (
|
||||
<TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>
|
||||
) : (
|
||||
<div>
|
||||
<ObjectChildrenPropertyTable nodeIds={tree.data} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Loader>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { useChildren, TabHandlerPanelComponent } from '@cloudbeaver/core-app';
|
||||
import { Loader, TabsBox, TabPanel, TextPlaceholder, Button } from '@cloudbeaver/core-blocks';
|
||||
import { useConnectionInfo } from '@cloudbeaver/core-connections';
|
||||
import { TabHandlerPanelComponent, NavTreeResource, NavNodeInfoResource } from '@cloudbeaver/core-app';
|
||||
import { Loader, TabsBox, TabPanel, TextPlaceholder, Button, useMapResource, useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import { ConnectionInfoResource, ConnectionsManagerService } from '@cloudbeaver/core-connections';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
@@ -35,6 +36,9 @@ const styles = composes(
|
||||
tab-outer:only-child {
|
||||
display: none;
|
||||
}
|
||||
ExceptionMessage {
|
||||
padding: 24px;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@@ -42,26 +46,72 @@ export const ObjectViewerPanel: TabHandlerPanelComponent<IObjectViewerTabState>
|
||||
tab,
|
||||
}) {
|
||||
const translate = useTranslate();
|
||||
const [connecting, setConnecting] = useState(false);
|
||||
const connection = useConnectionInfo(tab.handlerState.connectionId || '');
|
||||
const style = useStyles(styles);
|
||||
const {
|
||||
children, isOutdated, isLoading, isLoaded,
|
||||
} = useChildren(tab.handlerState.objectId);
|
||||
const dbObjectPagesService = useService(DBObjectPageService);
|
||||
const connectionsManagerService = useService(ConnectionsManagerService);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
|
||||
const state = useObservableRef(() => ({
|
||||
connecting: false,
|
||||
notFound: false,
|
||||
}), {
|
||||
connecting: observable.ref,
|
||||
notFound: observable.ref,
|
||||
}, false);
|
||||
|
||||
const connection = useMapResource(ConnectionInfoResource, tab.handlerState.connectionId || null, {
|
||||
isActive: resource => !tab.handlerState.connectionId || !resource.has(tab.handlerState.connectionId),
|
||||
});
|
||||
|
||||
const children = useMapResource(NavTreeResource, tab.handlerState.parentId, {
|
||||
onLoad: async resource => {
|
||||
if (tab.handlerState.parents.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const first = tab.handlerState.parents[0];
|
||||
await resource.load(first);
|
||||
|
||||
for (const nodeId of tab.handlerState.parents) {
|
||||
if (!navNodeInfoResource.has(nodeId)) {
|
||||
state.notFound = true;
|
||||
return true;
|
||||
}
|
||||
await resource.load(nodeId);
|
||||
}
|
||||
state.notFound = false;
|
||||
return false;
|
||||
},
|
||||
isActive: () => connection.data?.connected || false,
|
||||
});
|
||||
|
||||
const dataPreloaded = children.isLoaded() && !!children.data?.includes(tab.handlerState.objectId) && !state.notFound;
|
||||
|
||||
const node = useMapResource(navNodeInfoResource, dataPreloaded ? tab.handlerState.objectId : null, {
|
||||
onData(data) {
|
||||
tab.handlerState.tabIcon = data.icon;
|
||||
tab.handlerState.tabTitle = data.name;
|
||||
},
|
||||
});
|
||||
|
||||
const pages = dbObjectPagesService.orderedPages;
|
||||
|
||||
const handleConnect = useCallback(async () => {
|
||||
setConnecting(true);
|
||||
try {
|
||||
await connection.connect();
|
||||
} finally {
|
||||
setConnecting(false);
|
||||
if (state.connecting || !connection.data) {
|
||||
return;
|
||||
}
|
||||
}, [connection]);
|
||||
|
||||
if (connection.connectionInfo && !connection.connectionInfo.connected) {
|
||||
if (connecting || connection.isLoading()) {
|
||||
state.connecting = true;
|
||||
|
||||
try {
|
||||
await connectionsManagerService.requireConnection(connection.data.id);
|
||||
} finally {
|
||||
state.connecting = false;
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (connection.data && !connection.data.connected) {
|
||||
if (state.connecting || connection.isLoading()) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
@@ -72,37 +122,39 @@ export const ObjectViewerPanel: TabHandlerPanelComponent<IObjectViewerTabState>
|
||||
);
|
||||
}
|
||||
|
||||
if (tab.handlerState.error) {
|
||||
if (tab.handlerState.error || state.notFound) {
|
||||
return <TextPlaceholder>{translate('plugin_object_viewer_error')}</TextPlaceholder>;
|
||||
}
|
||||
|
||||
if (!isLoaded() || (!isOutdated() && isLoading())) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (!children) {
|
||||
return <TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>;
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
<TabsBox
|
||||
currentTabId={tab.handlerState.pageId}
|
||||
tabs={pages.map(page => (
|
||||
<DBObjectPageTab
|
||||
key={page.key}
|
||||
tab={tab}
|
||||
page={page}
|
||||
style={styles}
|
||||
onSelect={dbObjectPagesService.selectPage}
|
||||
/>
|
||||
))}
|
||||
style={styles}
|
||||
>
|
||||
{pages.map(page => (
|
||||
<TabPanel key={page.key} tabId={page.key} lazy>
|
||||
<DBObjectPagePanel tab={tab} page={page} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabsBox>
|
||||
<Loader state={[node, children]} style={styles}>
|
||||
{() => styled(style)(
|
||||
<>
|
||||
{node.data ? (
|
||||
<TabsBox
|
||||
currentTabId={tab.handlerState.pageId}
|
||||
tabs={pages.map(page => (
|
||||
<DBObjectPageTab
|
||||
key={page.key}
|
||||
tab={tab}
|
||||
page={page}
|
||||
style={styles}
|
||||
onSelect={dbObjectPagesService.selectPage}
|
||||
/>
|
||||
))}
|
||||
style={styles}
|
||||
>
|
||||
{pages.map(page => (
|
||||
<TabPanel key={page.key} tabId={page.key} lazy>
|
||||
<DBObjectPagePanel tab={tab} page={page} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</TabsBox>
|
||||
) : (
|
||||
<TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Loader>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -11,17 +11,15 @@ import {
|
||||
INodeNavigationData,
|
||||
ITab,
|
||||
TabHandler,
|
||||
NodeManagerUtils,
|
||||
objectCatalogProvider,
|
||||
objectSchemaProvider,
|
||||
NavNodeManagerService,
|
||||
DBObjectService
|
||||
NavNodeManagerService
|
||||
} from '@cloudbeaver/core-app';
|
||||
import { connectionProvider, ConnectionInfoResource, Connection } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import type { IAsyncContextLoader, IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
||||
import { ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
|
||||
import { ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { IObjectViewerTabContext } from './IObjectViewerTabContext';
|
||||
import type { IObjectViewerTabState } from './IObjectViewerTabState';
|
||||
@@ -37,7 +35,6 @@ export class ObjectViewerTabService {
|
||||
|
||||
constructor(
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private dbObjectService: DBObjectService,
|
||||
private dbObjectPageService: DBObjectPageService,
|
||||
private notificationService: NotificationService,
|
||||
private navigationTabsService: NavigationTabsService,
|
||||
@@ -291,51 +288,15 @@ export class ObjectViewerTabService {
|
||||
}
|
||||
}
|
||||
|
||||
for (const nodeId of tab.handlerState.parents) {
|
||||
await this.navNodeManagerService.loadTree(nodeId);
|
||||
}
|
||||
|
||||
// TODO: must be loaded by info folder?
|
||||
const node = await this.navNodeManagerService.loadNode({
|
||||
nodeId: tab.handlerState.objectId,
|
||||
parentId: tab.handlerState.parentId,
|
||||
});
|
||||
|
||||
const currentPage = this.dbObjectPageService.getPage(tab.handlerState.pageId);
|
||||
|
||||
if (currentPage) {
|
||||
await this.dbObjectPageService.selectPage(tab, currentPage);
|
||||
}
|
||||
|
||||
if (node) {
|
||||
tab.handlerState.tabIcon = node.icon;
|
||||
tab.handlerState.tabTitle = node.name;
|
||||
}
|
||||
|
||||
await this.dbObjectService.load(tab.handlerState.objectId);
|
||||
const children = await this.navNodeManagerService.loadTree(tab.handlerState.objectId);
|
||||
|
||||
if (tab.handlerState.childrenError) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const folderId = tab.handlerState.folderId;
|
||||
|
||||
if (children.length === 0 || !NodeManagerUtils.isDatabaseObject(folderId)) {
|
||||
await this.dbObjectService.loadChildren(tab.handlerState.objectId, resourceKeyList(children));
|
||||
return;
|
||||
}
|
||||
const folderChildren = await this.navNodeManagerService.loadTree(folderId);
|
||||
|
||||
await this.dbObjectService.loadChildren(folderId, resourceKeyList(folderChildren));
|
||||
} catch (exception) {
|
||||
if (tab.handlerState.childrenError) {
|
||||
return;
|
||||
}
|
||||
tab.handlerState.childrenError = true;
|
||||
this.notificationService.logException(exception, 'Object Viewer Error', 'Error in Object Viewer while folder selecting');
|
||||
}
|
||||
} catch (exception) {
|
||||
tab.handlerState.error = true;
|
||||
this.notificationService.logException(exception, 'Object Viewer Error', 'Error in Object Viewer while tab selecting');
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { NavNodeManagerService, DBObjectService } from '@cloudbeaver/core-app';
|
||||
import { NavNodeManagerService, DBObjectResource } from '@cloudbeaver/core-app';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
export function useObjectFolder(objectId: string) {
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const dbObjectService = useService(DBObjectService);
|
||||
const dbObjectResource = useService(DBObjectResource);
|
||||
|
||||
const children = navNodeManagerService.getTree(objectId) || [];
|
||||
|
||||
const isLoading = !dbObjectService.isLoaded(resourceKeyList(children))
|
||||
&& dbObjectService.isDataLoading(resourceKeyList(children));
|
||||
const isLoading = !dbObjectResource.isLoaded(resourceKeyList(children))
|
||||
&& dbObjectResource.isDataLoading(resourceKeyList(children));
|
||||
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user