mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
feat(core-app): CB-2297 customize node delete confirmation
This commit is contained in:
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user