Skip to content

Commit

Permalink
CB-4074 transform metadata editor footer to the new context menu (#2490)
Browse files Browse the repository at this point in the history
* CB-4074 transform metadata editor footer to the new context menu

* CB-4074 use existing data context for nodes

---------

Co-authored-by: mr-anton-t <[email protected]>
  • Loading branch information
devnaumov and mr-anton-t authored Mar 25, 2024
1 parent 4daf1ef commit 1fd1ebe
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 162 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 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 { createMenu } from '@cloudbeaver/core-view';

export const MENU_OBJECT_VIEWER_FOOTER = createMenu('object-viewer-footer', 'Object viewer footer menu');
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,29 @@
*/
import { observer } from 'mobx-react-lite';

import { TableState, ToolsPanel } from '@cloudbeaver/core-blocks';
import type { TableState } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { DATA_CONTEXT_NAV_NODES, type NavNode, NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
import { resourceKeyList } from '@cloudbeaver/core-resource';
import { MenuBar } from '@cloudbeaver/core-ui';
import { useMenu } from '@cloudbeaver/core-view';

import { ObjectPropertyTableFooterItem } from './ObjectPropertyTableFooterItem';
import { ObjectPropertyTableFooterService } from './ObjectPropertyTableFooterService';
import { MENU_OBJECT_VIEWER_FOOTER } from './MENU_OBJECT_VIEWER_FOOTER';

interface Props {
nodeIds: string[];
tableState: TableState;
state: TableState;
className?: string;
}

export const ObjectPropertyTableFooter = observer<Props>(function ObjectPropertyTableFooter({ nodeIds, tableState, className }) {
const service = useService(ObjectPropertyTableFooterService);
export const ObjectPropertyTableFooter = observer<Props>(function ObjectPropertyTableFooter({ state, className }) {
const navNodeInfoResource = useService(NavNodeInfoResource);
const menu = useMenu({ menu: MENU_OBJECT_VIEWER_FOOTER });

const items = service.constructMenuWithContext(nodeIds, tableState);
const hidden = items.every(item => item.isHidden);

if (hidden) {
return null;
function getSelected() {
return navNodeInfoResource.get(resourceKeyList(state.selectedList)).filter(Boolean) as NavNode[];
}

return (
<ToolsPanel className={className}>
{items.map((topItem, i) => (
<ObjectPropertyTableFooterItem key={i} menuItem={topItem} />
))}
</ToolsPanel>
);
menu.context.set(DATA_CONTEXT_NAV_NODES, getSelected);

return <MenuBar className={className} menu={menu} />;
});

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,79 +5,75 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { TableState } from '@cloudbeaver/core-blocks';
import { injectable } from '@cloudbeaver/core-di';
import { ContextMenuService, IContextMenuItem, IMenuContext, IMenuItem } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { ENodeFeature, type NavNode, NavNodeInfoResource, NavTreeResource, NavTreeSettingsService } from '@cloudbeaver/core-navigation-tree';
import { DATA_CONTEXT_NAV_NODES, ENodeFeature, NavTreeResource, NavTreeSettingsService } from '@cloudbeaver/core-navigation-tree';
import { resourceKeyList } from '@cloudbeaver/core-resource';
import { ACTION_DELETE, ActionService, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view';

interface IObjectPropertyTableFooterContext {
nodeIds: string[];
tableState: TableState;
}
import { MENU_OBJECT_VIEWER_FOOTER } from './MENU_OBJECT_VIEWER_FOOTER';

@injectable()
export class ObjectPropertyTableFooterService {
static objectPropertyContextType = 'objectProperty';
private readonly objectPropertyTableFooterToken = 'objectPropertyTableFooter';

constructor(
private readonly contextMenuService: ContextMenuService,
private readonly navTreeResource: NavTreeResource,
private readonly navNodeInfoResource: NavNodeInfoResource,
private readonly notificationService: NotificationService,
private readonly navTreeSettingsService: NavTreeSettingsService,
) {
this.contextMenuService.addPanel(this.objectPropertyTableFooterToken);
private readonly menuService: MenuService,
private readonly actionService: ActionService,
) {}

this.registerMenuItem({
id: 'delete',
title: 'ui_delete',
tooltip: 'ui_delete',
icon: 'delete',
order: 0,
isPresent(context) {
return context.contextType === ObjectPropertyTableFooterService.objectPropertyContextType;
registerFooterActions() {
this.menuService.addCreator({
menus: [MENU_OBJECT_VIEWER_FOOTER],
isApplicable: context => {
const selected = context.tryGet(DATA_CONTEXT_NAV_NODES);
return selected !== undefined && this.navTreeSettingsService.settings.getValue('deleting');
},
isHidden: () => !this.navTreeSettingsService.settings.getValue('deleting'),
isDisabled: context => {
if (context.data.tableState.selectedList.length === 0) {
return true;
}
getItems: (_, items) => [...items, ACTION_DELETE],
});

const selectedNodes = this.getSelectedNodes(context.data.tableState.selectedList);
return !selectedNodes.some(node => node.features?.includes(ENodeFeature.canDelete)) || this.navTreeResource.isLoading();
},
onClick: async context => {
const nodes = this.getSelectedNodes(context.data.tableState.selectedList).filter(node => node.features?.includes(ENodeFeature.canDelete));
this.actionService.addHandler({
id: 'object-viewer-footer-base',
isActionApplicable: (context, action) => {
const menu = context.hasValue(DATA_CONTEXT_MENU, MENU_OBJECT_VIEWER_FOOTER);

try {
await this.navTreeResource.deleteNode(resourceKeyList(nodes.map(node => node.id)));
} catch (exception: any) {
this.notificationService.logException(exception, 'Failed to delete item');
if (!menu || !context.has(DATA_CONTEXT_NAV_NODES)) {
return false;
}

return [ACTION_DELETE].includes(action);
},
});
}
getActionInfo: (_, action) => {
if (action === ACTION_DELETE) {
return {
...action.info,
icon: 'delete',
};
}

registerMenuItem(options: IContextMenuItem<IObjectPropertyTableFooterContext>): void {
this.contextMenuService.addMenuItem<IObjectPropertyTableFooterContext>(this.objectPropertyTableFooterToken, options);
}
return action.info;
},
isDisabled: (context, action) => {
if (action === ACTION_DELETE) {
const selected = context.get(DATA_CONTEXT_NAV_NODES)();
return !selected.some(node => node.features?.includes(ENodeFeature.canDelete)) || this.navTreeResource.isLoading();
}

constructMenuWithContext(nodeIds: string[], tableState: TableState): IMenuItem[] {
const context: IMenuContext<IObjectPropertyTableFooterContext> = {
menuId: this.objectPropertyTableFooterToken,
contextType: ObjectPropertyTableFooterService.objectPropertyContextType,
data: {
nodeIds,
tableState,
return true;
},
};
return this.contextMenuService.createContextMenu(context, this.objectPropertyTableFooterToken).menuItems;
}
handler: async (context, action) => {
if (action === ACTION_DELETE) {
const selected = context.get(DATA_CONTEXT_NAV_NODES)();
const nodes = selected.filter(node => node.features?.includes(ENodeFeature.canDelete));

private getSelectedNodes(list: string[]) {
return this.navNodeInfoResource.get(resourceKeyList(list)).filter(Boolean) as NavNode[];
try {
await this.navTreeResource.deleteNode(resourceKeyList(nodes.map(node => node.id)));
} catch (exception: any) {
this.notificationService.logException(exception, 'plugin_object_viewer_delete_object_fail');
}
}
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { observer } from 'mobx-react-lite';
import { useCallback, useState } from 'react';
import styled from 'reshadow';

import { IScrollState, Link, s, useControlledScroll, useS, useStyles, useTable, useTranslate } from '@cloudbeaver/core-blocks';
import type { DBObject } from '@cloudbeaver/core-navigation-tree';
import { IScrollState, Link, s, useControlledScroll, useExecutor, useS, useStyles, useTable, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { type DBObject, NavTreeResource } from '@cloudbeaver/core-navigation-tree';
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
import { useTabLocalState } from '@cloudbeaver/core-ui';
import { isDefined, TextTools } from '@cloudbeaver/core-utils';
Expand Down Expand Up @@ -76,6 +77,7 @@ const CUSTOM_COLUMNS = [ColumnSelect, ColumnIcon];

export const Table = observer<TableProps>(function Table({ objects, hasNextPage, loadMore }) {
const styles = useS(classes);
const navTreeResource = useService(NavTreeResource);

const [tableContainer, setTableContainerRef] = useState<HTMLDivElement | null>(null);
const translate = useTranslate();
Expand All @@ -88,7 +90,6 @@ export const Table = observer<TableProps>(function Table({ objects, hasNextPage,

const baseObject = objects.slice().sort((a, b) => (b.object?.properties?.length || 0) - (a.object?.properties?.length || 0));

const nodeIds = objects.map(object => object.id);
const properties = baseObject[0]?.object?.properties ?? [];
const measuredCells = getMeasuredCells(properties, objects);

Expand All @@ -115,6 +116,15 @@ export const Table = observer<TableProps>(function Table({ objects, hasNextPage,
[loadMore],
);

useExecutor({
executor: navTreeResource.onItemDelete,
handlers: [
function handleNodeDelete(nodeId) {
tableState.unselect(nodeId);
},
],
});

if (objects.length === 0) {
return null;
}
Expand All @@ -137,7 +147,7 @@ export const Table = observer<TableProps>(function Table({ objects, hasNextPage,
</Link>
</div>
)}
<ObjectPropertyTableFooter className={s(styles, { objectPropertyTableFooter: true })} nodeIds={nodeIds} tableState={tableState} />
<ObjectPropertyTableFooter className={s(styles, { objectPropertyTableFooter: true })} state={tableState} />
</div>
</TableContext.Provider>,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@
import { Bootstrap, injectable } from '@cloudbeaver/core-di';

import { ObjectPropertiesPageService } from './ObjectPropertiesPage/ObjectPropertiesPageService';
import { ObjectPropertyTableFooterService } from './ObjectPropertiesPage/ObjectPropertyTable/ObjectPropertyTableFooterService';
import { ObjectViewerTabService } from './ObjectViewerTabService';

@injectable()
export class ObjectViewerBootstrap extends Bootstrap {
constructor(
private readonly objectViewerTabService: ObjectViewerTabService,
private readonly objectPropertiesPageService: ObjectPropertiesPageService,
private readonly objectPropertyTableFooterService: ObjectPropertyTableFooterService,
) {
super();
}

register(): void {
this.objectViewerTabService.registerTabHandler();
this.objectPropertiesPageService.registerDBObjectPage();
this.objectPropertyTableFooterService.registerFooterActions();
}

load(): void {}
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/plugin-object-viewer/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default [
['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'],
['plugin_object_viewer_delete_object_fail', 'Failed to delete object'],
];
1 change: 1 addition & 0 deletions webapp/packages/plugin-object-viewer/src/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default [
['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'],
['plugin_object_viewer_delete_object_fail', 'Failed to delete object'],
];
1 change: 1 addition & 0 deletions webapp/packages/plugin-object-viewer/src/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default [
['plugin_object_viewer_table_name', 'Название'],
['plugin_object_viewer_table_no_items', 'Нет объектов для показа'],
['plugin_object_viewer_error', 'Произошла ошибка при загрузке вкладки'],
['plugin_object_viewer_delete_object_fail', 'Не удалось удалить объект'],
];
1 change: 1 addition & 0 deletions webapp/packages/plugin-object-viewer/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default [
['plugin_object_viewer_table_name', '名称'],
['plugin_object_viewer_table_no_items', '没有要显示的项目'],
['plugin_object_viewer_error', '选项卡加载时出错'],
['plugin_object_viewer_delete_object_fail', 'Failed to delete object'],
];

0 comments on commit 1fd1ebe

Please sign in to comment.