feat(core-app): CB-2297 customize node delete confirmation

This commit is contained in:
Wroud
2022-07-12 00:55:07 +03:00
parent 64f779f9d7
commit 665c98cdcd
6 changed files with 118 additions and 16 deletions
+1
View File
@@ -23,6 +23,7 @@ export * from './shared/NodesManager/NavNodeContextMenuService';
export * from './shared/NodesManager/NavNodeInfoResource';
export * from './shared/NodesManager/NavNodeManagerService';
export * from './shared/NodesManager/NavTreeResource';
export * from './shared/NodesManager/nodeDeleteContext';
export * from './shared/NodesManager/NavNodeExtensionsService';
export * from './shared/NodesManager/NodeLink';
export * from './shared/NodesManager/NodeManagerUtils';
@@ -11,7 +11,9 @@ import { untracked } from 'mobx';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { CommonDialogService, ConfirmationDialogDelete, DialogueStateResult, RenameDialog } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { ExecutorInterrupter } from '@cloudbeaver/core-executor';
import { LocalizationService } from '@cloudbeaver/core-localization';
import { ResourceKeyUtils } from '@cloudbeaver/core-sdk';
import { ActionService, ACTION_DELETE, ACTION_OPEN, ACTION_REFRESH, ACTION_RENAME, DATA_CONTEXT_MENU_NESTED, menuExtractActions, MenuSeparatorItem, MenuService } from '@cloudbeaver/core-view';
import { CoreSettingsService } from '../../CoreSettingsService';
@@ -20,9 +22,10 @@ import { DATA_CONTEXT_NAV_NODE } from './DATA_CONTEXT_NAV_NODE';
import { ENodeFeature } from './ENodeFeature';
import type { NavNode } from './EntityTypes';
import type { INodeActions } from './INodeActions';
import { getNodeDisplayName } from './NavNodeInfoResource';
import { getNodeDisplayName, NavNodeInfoResource } from './NavNodeInfoResource';
import { NavNodeManagerService } from './NavNodeManagerService';
import { NavTreeResource } from './NavTreeResource';
import { nodeDeleteContext } from './nodeDeleteContext';
export interface INodeMenuData {
node: NavNode;
@@ -39,12 +42,41 @@ export class NavNodeContextMenuService extends Bootstrap {
private readonly actionService: ActionService,
private readonly menuService: MenuService,
private readonly coreSettingsService: CoreSettingsService,
private readonly localizationService: LocalizationService
private readonly localizationService: LocalizationService,
private readonly navNodeInfoResource: NavNodeInfoResource
) {
super();
}
register(): void {
this.navTreeResource.beforeNodeDelete.addPostHandler(async (data, contexts) => {
if (ExecutorInterrupter.isInterrupted(contexts)) {
return;
}
const deleteContext = contexts.getContext(nodeDeleteContext);
if (deleteContext.confirmed) {
return;
}
const nodes = ResourceKeyUtils
.mapArray(data, nodeId => this.navNodeInfoResource.get(nodeId))
.filter<NavNode>(Boolean as any)
.map(node => node.name)
.join();
const result = await this.commonDialogService.open(ConfirmationDialogDelete, {
title: 'ui_data_delete_confirmation',
message: this.localizationService.translate('app_navigationTree_node_delete_confirmation', undefined, { name: nodes }),
confirmActionText: 'ui_delete',
});
if (result === DialogueStateResult.Rejected) {
ExecutorInterrupter.interrupt(contexts);
}
});
this.actionService.addHandler({
id: 'nav-node-base-handler',
isActionApplicable: (context, action): boolean => {
@@ -130,16 +162,6 @@ export class NavNodeContextMenuService extends Bootstrap {
break;
}
case ACTION_DELETE: {
const result = await this.commonDialogService.open(ConfirmationDialogDelete, {
title: 'ui_data_delete_confirmation',
message: this.localizationService.translate('app_navigationTree_node_delete_confirmation', undefined, { name }),
confirmActionText: 'ui_delete',
});
if (result === DialogueStateResult.Rejected) {
return;
}
try {
await this.navTreeResource.deleteNode(node.id);
} catch (exception: any) {
@@ -11,7 +11,7 @@ import { action, computed, makeObservable, runInAction } from 'mobx';
import { UserInfoResource } from '@cloudbeaver/core-authentication';
import { Connection, ConnectionInfoResource } from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor';
import { EPermission, SessionPermissionsResource, SessionDataResource } from '@cloudbeaver/core-root';
import {
GraphQLService,
@@ -45,6 +45,7 @@ interface INodeMetadata extends ICachedMapResourceMetadata {
@injectable()
export class NavTreeResource extends CachedMapResource<string, string[]> {
readonly beforeNodeDelete: IExecutor<ResourceKey<string>>;
readonly onNodeRefresh: IExecutor<string>;
protected metadata: MetadataMap<string, INodeMetadata>;
@@ -63,6 +64,8 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
) {
super();
this.beforeNodeDelete = new Executor();
makeObservable<this, 'setNavObject' | 'connectionRemoveHandler'>(this, {
childrenLimit: computed,
setDetails: action,
@@ -164,6 +167,12 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
}
async deleteNode(key: ResourceKey<string>): Promise<void> {
const contexts = await this.beforeNodeDelete.execute(key);
if (ExecutorInterrupter.isInterrupted(contexts)) {
return;
}
const nodePaths = isResourceKeyList(key) ? key.list : [key];
await this.performUpdate(key, [], async () => {
@@ -0,0 +1,27 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 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 { IExecutionContextProvider } from '@cloudbeaver/core-executor';
import type { ResourceKey } from '@cloudbeaver/core-sdk';
export interface INodeDeleteContext {
confirmed: boolean;
confirm(): void;
}
export function nodeDeleteContext(
contexts: IExecutionContextProvider<ResourceKey<string>>,
data: ResourceKey<string>
): INodeDeleteContext {
return {
confirmed: false,
confirm() {
this.confirmed = true;
},
};
}
@@ -46,6 +46,7 @@ export interface ResourceKeyUtils {
every: <TKey>(key: ResourceKey<TKey>, predicate: (key: TKey, index: number) => boolean) => boolean;
map: MapFnc;
toArray: <TKey>(key: ResourceKey<TKey>) => TKey[];
filter: <TKey>(key: ResourceKey<TKey>, filter: (key: TKey) => boolean) => TKey[];
mapKey: <TKey, TValue>(key: ResourceKey<TKey>, selector: (key: TKey, index: number) => TValue) => ResourceKey<TValue>;
mapArray: <TKey, TValue>(key: ResourceKey<TKey>, selector: (key: TKey, index: number) => TValue) => TValue[];
includes: <TKey>(first: ResourceKey<TKey>, second: ResourceKey<TKey>) => boolean;
@@ -135,6 +136,9 @@ export const ResourceKeyUtils: ResourceKeyUtils = {
return [key];
}
},
filter<TKey>(key: ResourceKey<TKey>, filter: (key: TKey) => boolean): TKey[] {
return this.toArray(key).filter(filter);
},
mapKey<TKey, TValue>(key: ResourceKey<TKey>, selector: (key: TKey, index: number) => TValue): ResourceKey<TValue> {
if (isResourceKeyList(key)) {
@@ -7,13 +7,13 @@
*/
import { DATA_CONTEXT_ELEMENTS_TREE, ENodeFeature, ENodeMoveType, getNodesFromContext, IElementsTree, INodeMoveData, MENU_ELEMENTS_TREE_TOOLS, NavNode, NavNodeInfoResource, NavNodeManagerService, navNodeMoveContext, NavTreeResource, NAV_NODE_TYPE_FOLDER, NAV_NODE_TYPE_ROOT, ROOT_NODE_PATH } from '@cloudbeaver/core-app';
import { DATA_CONTEXT_ELEMENTS_TREE, ENodeFeature, ENodeMoveType, getNodesFromContext, IElementsTree, INodeMoveData, MENU_ELEMENTS_TREE_TOOLS, NavNode, NavNodeInfoResource, NavNodeManagerService, navNodeMoveContext, NavTreeResource, NAV_NODE_TYPE_FOLDER, NAV_NODE_TYPE_ROOT, nodeDeleteContext, ROOT_NODE_PATH } from '@cloudbeaver/core-app';
import { UserInfoResource } from '@cloudbeaver/core-authentication';
import { ConnectionFolderResource, ConnectionInfoResource, CONNECTION_FOLDER_NAME_VALIDATION } from '@cloudbeaver/core-connections';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { CommonDialogService, DialogueStateResult, RenameDialog } from '@cloudbeaver/core-dialogs';
import { CommonDialogService, ConfirmationDialogDelete, DialogueStateResult, RenameDialog } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { ExecutorInterrupter, IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { LocalizationService } from '@cloudbeaver/core-localization';
import { CachedMapAllKey, ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
import { ActionService, ACTION_NEW_FOLDER, DATA_CONTEXT_MENU, IAction, IDataContextProvider, MenuService } from '@cloudbeaver/core-view';
@@ -44,6 +44,45 @@ export class ConnectionFoldersBootstrap extends Bootstrap {
this.navNodeInfoResource.onItemDelete.addHandler(this.syncWithNavTree.bind(this));
this.navNodeManagerService.onMove.addHandler(this.moveConnectionToFolder.bind(this));
this.navTreeResource.beforeNodeDelete.addHandler(async (data, contexts) => {
if (ExecutorInterrupter.isInterrupted(contexts)) {
return;
}
const deleteContext = contexts.getContext(nodeDeleteContext);
if (deleteContext.confirmed) {
return;
}
await this.connectionFolderResource.load(CachedMapAllKey);
const nodes = ResourceKeyUtils
.filter(
data,
nodeId => this.connectionFolderResource.fromNodeId(nodeId) !== undefined
)
.map(nodeId => this.navNodeInfoResource.get(nodeId))
.filter<NavNode>(Boolean as any)
.map(node => node.name)
.join();
if (!nodes) {
return;
}
const result = await this.commonDialogService.open(ConfirmationDialogDelete, {
title: 'ui_data_delete_confirmation',
message: this.localizationService.translate('connections_public_connection_folder_delete_confirmation', undefined, { name: nodes }),
confirmActionText: 'ui_delete',
});
if (result === DialogueStateResult.Rejected) {
ExecutorInterrupter.interrupt(contexts);
} else {
deleteContext.confirm();
}
});
this.actionService.addHandler({
id: 'tree-tools-menu-folders-handler',
isActionApplicable: (context, action) => {