mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-19 00:37:08 +00:00
256 lines
6.8 KiB
TypeScript
256 lines
6.8 KiB
TypeScript
import type { HookHub, ICmdHooks as IHooks, NsGraph, IModelService } from '@antv/xflow';
|
||
import { Deferred, ManaSyringe } from '@antv/xflow';
|
||
import type { FormInstance } from 'antd';
|
||
import { Modal, Form, Input, ConfigProvider } from 'antd';
|
||
|
||
import type { IArgsBase, ICommandHandler, IGraphCommandService } from '@antv/xflow';
|
||
import { ICommandContextProvider } from '@antv/xflow';
|
||
|
||
import { CustomCommands } from './constants';
|
||
|
||
import 'antd/es/modal/style/index.css';
|
||
|
||
// prettier-ignore
|
||
type ICommand = ICommandHandler<NsRenameNodeCmd.IArgs, NsRenameNodeCmd.IResult, NsRenameNodeCmd.ICmdHooks>;
|
||
|
||
export namespace NsRenameNodeCmd {
|
||
/** Command: 用于注册named factory */
|
||
// eslint-disable-next-line
|
||
export const command = CustomCommands.SHOW_RENAME_MODAL;
|
||
/** hook name */
|
||
// eslint-disable-next-line
|
||
export const hookKey = 'renameNode';
|
||
/** hook 参数类型 */
|
||
export interface IArgs extends IArgsBase {
|
||
nodeConfig: NsGraph.INodeConfig;
|
||
updateNodeNameService: IUpdateNodeNameService;
|
||
}
|
||
export interface IUpdateNodeNameService {
|
||
(newName: string, nodeConfig: NsGraph.INodeConfig, meta: NsGraph.IGraphMeta): Promise<{
|
||
err: string | null;
|
||
nodeName: string;
|
||
}>;
|
||
}
|
||
/** hook handler 返回类型 */
|
||
export interface IResult {
|
||
err: string | null;
|
||
preNodeName?: string;
|
||
currentNodeName?: string;
|
||
}
|
||
/** hooks 类型 */
|
||
export interface ICmdHooks extends IHooks {
|
||
renameNode: HookHub<IArgs, IResult>;
|
||
}
|
||
}
|
||
|
||
@ManaSyringe.injectable()
|
||
/** 部署画布数据 */
|
||
// prettier-ignore
|
||
export class RenameNodeCommand implements ICommand {
|
||
/** api */
|
||
@ManaSyringe.inject(ICommandContextProvider) contextProvider!: ICommand['contextProvider'];
|
||
|
||
/** 执行Cmd */
|
||
execute = async () => {
|
||
const ctx = this.contextProvider();
|
||
// const app = useXFlowApp();
|
||
const { args } = ctx.getArgs();
|
||
const hooks = ctx.getHooks();
|
||
const result = await hooks.renameNode.call(args, async (args) => {
|
||
const { nodeConfig, graphMeta, commandService, modelService, updateNodeNameService } = args;
|
||
const preNodeName = nodeConfig.label;
|
||
|
||
const getAppContext: IGetAppCtx = () => {
|
||
return {
|
||
graphMeta,
|
||
commandService,
|
||
modelService,
|
||
updateNodeNameService,
|
||
};
|
||
};
|
||
|
||
const x6Graph = await ctx.getX6Graph();
|
||
const cell = x6Graph.getCellById(nodeConfig.id);
|
||
const nodes = x6Graph.getNodes();
|
||
const edges = x6Graph.getEdges();
|
||
nodes.forEach((node) => {
|
||
if (node !== cell) {
|
||
x6Graph.removeCell(node);
|
||
}
|
||
});
|
||
edges.forEach((edge) => {
|
||
x6Graph.removeEdge(edge);
|
||
});
|
||
if (!cell || !cell.isNode()) {
|
||
throw new Error(`${nodeConfig.id} is not a valid node`);
|
||
}
|
||
/** 通过modal 获取 new name */
|
||
const newName = await showModal(nodeConfig, getAppContext);
|
||
/** 更新 node name */
|
||
if (newName) {
|
||
const cellData = cell.getData<NsGraph.INodeConfig>();
|
||
|
||
cell.setData({ ...cellData, label: newName } as NsGraph.INodeConfig);
|
||
return { err: null, preNodeName, currentNodeName: newName };
|
||
}
|
||
return { err: null, preNodeName, currentNodeName: '' };
|
||
});
|
||
|
||
ctx.setResult(result);
|
||
return this;
|
||
};
|
||
|
||
/** undo cmd */
|
||
undo = async () => {
|
||
if (this.isUndoable()) {
|
||
const ctx = this.contextProvider();
|
||
ctx.undo();
|
||
}
|
||
return this;
|
||
};
|
||
|
||
/** redo cmd */
|
||
redo = async () => {
|
||
if (!this.isUndoable()) {
|
||
await this.execute();
|
||
}
|
||
return this;
|
||
};
|
||
|
||
isUndoable(): boolean {
|
||
const ctx = this.contextProvider();
|
||
return ctx.isUndoable();
|
||
}
|
||
}
|
||
|
||
export interface IGetAppCtx {
|
||
(): {
|
||
graphMeta: NsGraph.IGraphMeta;
|
||
commandService: IGraphCommandService;
|
||
modelService: IModelService;
|
||
updateNodeNameService: NsRenameNodeCmd.IUpdateNodeNameService;
|
||
};
|
||
}
|
||
|
||
export type IModalInstance = ReturnType<typeof Modal.confirm>;
|
||
export interface IFormProps {
|
||
newNodeName: string;
|
||
}
|
||
|
||
const layout = {
|
||
labelCol: { span: 5 },
|
||
wrapperCol: { span: 19 },
|
||
};
|
||
|
||
function showModal(node: NsGraph.INodeConfig, getAppContext: IGetAppCtx) {
|
||
/** showModal 返回一个Promise */
|
||
const defer = new Deferred<string | void>();
|
||
|
||
/** modal确认保存逻辑 */
|
||
class ModalCache {
|
||
static modal: IModalInstance;
|
||
static form: FormInstance<IFormProps>;
|
||
}
|
||
|
||
/** modal确认保存逻辑 */
|
||
const onOk = async () => {
|
||
const { form, modal } = ModalCache;
|
||
const appContext = getAppContext();
|
||
const { updateNodeNameService, graphMeta } = appContext;
|
||
try {
|
||
modal.update({ okButtonProps: { loading: true } });
|
||
await form.validateFields();
|
||
const values = await form.getFieldsValue();
|
||
const newName: string = values.newNodeName;
|
||
/** 执行 backend service */
|
||
if (updateNodeNameService) {
|
||
const { err, nodeName } = await updateNodeNameService(newName, node, graphMeta);
|
||
if (err) {
|
||
throw new Error(err);
|
||
}
|
||
defer.resolve(nodeName);
|
||
}
|
||
/** 更新成功后,关闭modal */
|
||
onHide();
|
||
} catch (error) {
|
||
console.error(error);
|
||
/** 如果resolve空字符串则不更新 */
|
||
modal.update({ okButtonProps: { loading: false } });
|
||
}
|
||
};
|
||
|
||
/** modal销毁逻辑 */
|
||
const onHide = () => {
|
||
modal.destroy();
|
||
ModalCache.form = null as any;
|
||
ModalCache.modal = null as any;
|
||
container.destroy();
|
||
};
|
||
|
||
/** modal内容 */
|
||
const ModalContent = () => {
|
||
const [form] = Form.useForm<IFormProps>();
|
||
/** 缓存form实例 */
|
||
ModalCache.form = form;
|
||
|
||
return (
|
||
<div>
|
||
<ConfigProvider>
|
||
<Form form={form} {...layout} initialValues={{ newNodeName: node.label }}>
|
||
<Form.Item
|
||
name="newNodeName"
|
||
label="节点名"
|
||
rules={[
|
||
{ required: true, message: '请输入新节点名' },
|
||
{ min: 3, message: '节点名不能少于3个字符' },
|
||
]}
|
||
>
|
||
<Input />
|
||
</Form.Item>
|
||
</Form>
|
||
</ConfigProvider>
|
||
</div>
|
||
);
|
||
};
|
||
/** 创建modal dom容器 */
|
||
const container = createContainer();
|
||
/** 创建modal */
|
||
const modal = Modal.confirm({
|
||
title: '重命名',
|
||
content: <ModalContent />,
|
||
getContainer: () => {
|
||
return container.element;
|
||
},
|
||
okButtonProps: {
|
||
onClick: (e) => {
|
||
e.stopPropagation();
|
||
onOk();
|
||
},
|
||
},
|
||
onCancel: () => {
|
||
onHide();
|
||
},
|
||
afterClose: () => {
|
||
onHide();
|
||
},
|
||
});
|
||
|
||
/** 缓存modal实例 */
|
||
ModalCache.modal = modal;
|
||
|
||
/** showModal 返回一个Promise,用于await */
|
||
return defer.promise;
|
||
}
|
||
|
||
const createContainer = () => {
|
||
const div = document.createElement('div');
|
||
div.classList.add('xflow-modal-container');
|
||
window.document.body.append(div);
|
||
return {
|
||
element: div,
|
||
destroy: () => {
|
||
window.document.body.removeChild(div);
|
||
},
|
||
};
|
||
};
|