import {existsSync, readFile, readFileSync, writeFileSync} from 'fs-extra'; import path, {join} from 'path'; import {exists} from "fs"; let currentUuid: string = ''; let currentName: string = ''; let scriptPath: string = 'db://assets/script/' let savePath: string = scriptPath + 'fsm/' /** * @zh 如果希望兼容 3.3 之前的版本可以使用下方的代码 * @en You can add the code below if you want compatibility with versions prior to 3.3 */ // Editor.Panel.define = Editor.Panel.define || function(options: any) { return options } module.exports = Editor.Panel.define({ listeners: { show() { }, hide() { }, }, template: readFileSync(join(__dirname, '../../../static/template/default/index.html'), 'utf-8'), style: readFileSync(join(__dirname, '../../../static/style/default/index.css'), 'utf-8'), $: { iframe: '#iframe', }, methods: { async import() { let self = this let path = await Editor.Message.request('asset-db', 'query-path', savePath); if (!path) { return } //Editor.log(defaultPath); Editor.Dialog.select({ title: "提示 : 聚焦资源管理器中fsm目录下的状态机代码可以快速导入状态机", path: path, filters: [ {name: 'fsm', extensions: ["ts"]} ], }).then(async (args) => { if (args?.filePaths && args.filePaths.length > 0) { let fspath = args.filePaths[0]; let uuid = await Editor.Message.request('asset-db', 'query-uuid', fspath); checkAndLoadFsm(self.$.iframe, uuid, fspath, () => { Editor.Dialog.error("导入的文件不是状态机代码"); }); } }); }, async checkSameName(args: any) { let fileName = args.className + ".ts"; let filePath = await Editor.Message.request('asset-db', 'query-path', savePath + fileName); let isFileExists = existsSync(filePath as string); this.$.iframe.contentWindow.postMessage({ message: "export-callback", data: {isFileExists, className: args.className} }, '*'); }, panelClose() { currentName = ''; currentUuid = ''; }, reset() { currentName = ''; currentUuid = ''; }, save(args: any) { let self = this let preClassName = null; if (args.isExport) { preClassName = args.className; } else { if (currentName && currentUuid) { preClassName = currentName.split(".")[0]; } else { //todo 加锁 self.$.iframe.contentWindow.postMessage({ message: "save-callback", data: {error: true} }, '*'); return; } } //Editor.log("isExport",args.isExport); let modelAsObj = args.model; let nodeDataArray = modelAsObj.nodeDataArray; let linkDataArray = modelAsObj.linkDataArray; let keyToNodeDataArray = []; let initial = ""; let events = []; let callbacks: any = {}; let stateNameInterface = []; let uniqueStateNameArray = []; let eventNameInterface = []; let uniqueEventNameArray = []; let eventNameConfig = []; let stateNameConfig = []; let callbacksConfig = []; //let callbacksInterface = []; let triggersConfig = []; let uniqueCallbacksArray = []; let callbacksImplementConfig = []; for (let i = 0, l = nodeDataArray.length; i < l; i++) { keyToNodeDataArray[nodeDataArray[i].key] = nodeDataArray[i]; if (nodeDataArray[i].isInit) { initial = nodeDataArray[i].text; } let stateName = nodeDataArray[i].text; let localPreCallbacks = JSON.parse(nodeDataArray[i].callbacks).enter; if (!!localPreCallbacks.length) { let localPreCallbacksDecorators = localPreCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator); callbacks[`onenter${stateName}`] = localPreCallbacksDecorators; } let localPostCallbacks = JSON.parse(nodeDataArray[i].callbacks).leave; if (!!localPostCallbacks.length) { let localPostCallbacksDecorators = localPostCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator); callbacks[`onleave${stateName}`] = localPostCallbacksDecorators; } //stateNameInterface.push(`${stateName}:string`); //stateNameConfig.push(`${stateName}:"${stateName}"`); uniqueStateNameArray[stateName] = true; for (let i = 0, l = localPreCallbacks.length; i < l; i++) { let callbackName = localPreCallbacks[i]; uniqueCallbacksArray[callbackName] = true; //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`); } for (let i = 0, l = localPostCallbacks.length; i < l; i++) { let callbackName = localPostCallbacks[i]; uniqueCallbacksArray[callbackName] = true; //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`); } } for (let i = 0, l = linkDataArray.length; i < l; i++) { let name = linkDataArray[i].text || "event"; let from = keyToNodeDataArray[linkDataArray[i].from].text; let to = keyToNodeDataArray[linkDataArray[i].to].text; events.push({name, from, to}); let eventName = name; let localPreCallbacks = JSON.parse(linkDataArray[i].callbacks).before; if (!!localPreCallbacks.length) { let localPreCallbacksDecorators = localPreCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator); let callbacksIdxName = `onbefore${eventName}`; if (callbacks[callbacksIdxName] === undefined) { callbacks[callbacksIdxName] = {}; } callbacks[callbacksIdxName][from] = localPreCallbacksDecorators; } let localPostCallbacks = JSON.parse(linkDataArray[i].callbacks).after; if (!!localPostCallbacks.length) { let localPostCallbacksDecorators = localPostCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator); //callbacks[`onafter${eventName}`] = localPostCallbacksDecorators; let callbacksIdxName = `onafter${eventName}`; if (callbacks[callbacksIdxName] === undefined) { callbacks[callbacksIdxName] = {}; } callbacks[callbacksIdxName][from] = localPostCallbacksDecorators; } //eventNameInterface.push(`${eventName}:string`); //eventNameConfig.push(`${eventName}:"${eventName}"`); //triggersConfig.push(`public ${eventName}(...args:any[]): void {this.fsm["${eventName}"](...args);}`); uniqueEventNameArray[eventName] = true; for (let i = 0, l = localPreCallbacks.length; i < l; i++) { let callbackName = localPreCallbacks[i]; uniqueCallbacksArray[callbackName] = true; //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`); } for (let i = 0, l = localPostCallbacks.length; i < l; i++) { let callbackName = localPostCallbacks[i]; uniqueCallbacksArray[callbackName] = true; //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`); } } let globalCallbacksText = modelAsObj.globalCallbacksText let globalCallbacksObj = JSON.parse(globalCallbacksText); let globalEnterCallbacks = globalCallbacksObj.enter; let globalLeaveCallbacks = globalCallbacksObj.leave; let globalBeforeCallbacks = globalCallbacksObj.before; let globalAfterCallbacks = globalCallbacksObj.after; if (!!globalEnterCallbacks.length) { let globalEnterCallbacksDecorators = globalEnterCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator); callbacks[`onenterstate`] = globalEnterCallbacksDecorators; } if (!!globalLeaveCallbacks.length) { let globalLeaveCallbacksDecorators = globalLeaveCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator); callbacks[`onleavestate`] = globalLeaveCallbacksDecorators; } if (!!globalBeforeCallbacks.length) { let globalBeforeCallbacksDecorators = globalBeforeCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator); callbacks[`onbeforeevent`] = globalBeforeCallbacksDecorators; } if (!!globalAfterCallbacks.length) { let globalAfterCallbacksDecorators = globalAfterCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator); callbacks[`onafterevent`] = globalAfterCallbacksDecorators; } for (let i = 0, l = globalEnterCallbacks.length; i < l; i++) { let callbackName = globalEnterCallbacks[i]; uniqueCallbacksArray[callbackName] = true; //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`); } for (let i = 0, l = globalLeaveCallbacks.length; i < l; i++) { let callbackName = globalLeaveCallbacks[i]; uniqueCallbacksArray[callbackName] = true; //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`); } for (let i = 0, l = globalBeforeCallbacks.length; i < l; i++) { let callbackName = globalBeforeCallbacks[i]; uniqueCallbacksArray[callbackName] = true; //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`); } for (let i = 0, l = globalAfterCallbacks.length; i < l; i++) { let callbackName = globalAfterCallbacks[i]; uniqueCallbacksArray[callbackName] = true; //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`); } let uniqueCallbacksIdxArray = Object.keys(uniqueCallbacksArray); for (let i = 0, l = uniqueCallbacksIdxArray.length; i < l; i++) { let callbackName = uniqueCallbacksIdxArray[i]; callbacksConfig.push(`public abstract ${callbackName}(eventName: string, from: string, to: string, ...args: any[]) : void`); //callbacksInterface.push(`${callbackName}(eventName:string,from:string,to:string,...args:any[]) : void`); //callbacksImplementConfig.push(`protected ${callbackName} = (eventName:string,from:string,to:string,...args:any[]): void => {}`); } let uniqueEventNameIdxArray = Object.keys(uniqueEventNameArray); for (let i = 0, l = uniqueEventNameIdxArray.length; i < l; i++) { let eventName = uniqueEventNameIdxArray[i]; triggersConfig.push(`public ${eventName}(...args: any[]): void { this.fsm['${eventName}'](...args); }`); eventNameInterface.push(`${eventName}: string`); eventNameConfig.push(`${eventName}: '${eventName}'`); } let uniqueStateNameIdxArray = Object.keys(uniqueStateNameArray); for (let i = 0, l = uniqueStateNameIdxArray.length; i < l; i++) { let stateName = uniqueStateNameIdxArray[i]; stateNameInterface.push(`${stateName}: string`); stateNameConfig.push(`${stateName}: '${stateName}'`); } let fsmConfig = { initial, events, callbacks } //let dist = {}; //dist.flowForFsm = fsmConfig; //dist.flowForGoJs = JSON.parse(myDiagram.model.toJson()); //console.log(dist); //editKey = null; //selectLink = false; //editor.setText(JSON.stringify(dist)); //editor.setMode("code"); //editor.expandAll(); var fsmConfigDecorator = JSON.stringify(fsmConfig).replace(/"#/g, "").replace(/#"/g, "") .replace(/"/g, "'").replace(/{'/g, "{").replace(/':/g, ": ").replace(/,'/g, ",") //var flowForFsm = new File([fsmConfigDecorator], "flowForFsm.json", {type: "text/plain;charset=utf-8"}); //saveAs(flowForFsm); //var flowForGoJs = new File([myDiagram.model.toJson()], "flowForGoJs.json", {type: "text/plain;charset=utf-8"}); //saveAs(flowForGoJs); // if(exportType === "exportGoJs"){ // //var flowForGoJsFile = new File([myDiagram.model.toJson()], "flow-for-gojs.json", {type: "text/plain;charset=utf-8"}); // var flowForGoJsFile = new File([JSON.stringify(modelAsObj)], "FlowForGojs.json", {type: "text/plain;charset=utf-8"}); // saveAs(flowForGoJsFile); // return; // }else if(exportType === "all"){ // //var flowForGoJsFile = new File([myDiagram.model.toJson()], "flow-for-gojs.json", {type: "text/plain;charset=utf-8"}); // var flowForGoJsFile = new File([JSON.stringify(modelAsObj)], "FlowForGojs.json", {type: "text/plain;charset=utf-8"}); // saveAs(flowForGoJsFile); // } //let className = currentName.split(".")[0]; //console.log(fsmConfigDecorator); let tsString = tsTemplate.replace(/#fsmConfig#/g, fsmConfigDecorator) .replace(/#className#/g, preClassName) .replace(/#gojsConfig#/g, "###" + JSON.stringify(modelAsObj) + "###") .replace(/#stateNameConfig#/g, stateNameConfig.join(",\n ") + ",") .replace(/#eventNameConfig#/g, eventNameConfig.join(",\n ") + ",") .replace(/#stateNameInterface#/g, stateNameInterface.join(";\n ") + ";\n") .replace(/#eventNameInterface#/g, eventNameInterface.join(";\n ") + ";\n") .replace(/#triggersConfig#/g, triggersConfig.join("\n ") + "\n") if (callbacksConfig.length > 0) { tsString = tsString.replace(/#callbacksConfig#/g, callbacksConfig.join("\n ") + "\n") } else { tsString = tsString.replace(/#callbacksConfig#/g, "") // 仅去掉标签 } //.replace(/#callbacksImplementConfig#/g,callbacksImplementConfig.join(";\n ") + ";\n") //.replace(/#callbacksInterface#/g,callbacksInterface.join("\n ") + ";\n"); //console.log(tsString); //todo 验证fsm和状态机库的存在性 done // let isFsmFolderExists = Editor.assetdb.existsByPath(savePath); // Editor.log(isFsmFolderExists); // if (!isFsmFolderExists) { // Editor.Ipc.sendToMain("asset-db:create-asset", savePath, function (err, result) { // //todo 验证状态机库的存在性 // }) // } else { checkAndCreateFsmFolder(() => { checkAndMoveStateMachineLib(async () => { //todo 验证文件存在性 let path = await Editor.Message.request('asset-db', 'query-path', savePath + preClassName + '.ts'); writeFileSync(path as string, tsString); Editor.Message.request("asset-db", 'refresh-asset', savePath + preClassName + '.ts').then((results) => { Editor.Dialog.info(preClassName + '.ts' + " 保存成功!", { buttons: ["好的,我知道了"], title: "提示", detail: preClassName + '.ts' + " 将会被保存于路径 " + savePath }).then((result) => { self.$.iframe.contentWindow.postMessage({message: "save-callback", data: {}}, '*'); }); }); }); }, tsString); }, // modify 下划线 async selectionSelected(type: string, uuid: string) { //Editor.log(e); //Editor.log("args",args); //Editor.log(uuidArray[0]); if (type !== 'asset') { //Editor.log("before return"); return; } else { let fspath = await Editor.Message.request('asset-db', 'query-path', uuid); if (fspath && uuid) { checkAndLoadFsm(this.$.iframe, uuid, fspath, () => { //Editor.Dialog.error("导入的文件不是状态机代码"); }); } } }, }, ready() { let self = this as any window.addEventListener('message', (event) => { // 处理消息 if (self[event.data.message]) self[event.data.message](event.data.args, event.data.callback) // 你可以在这里根据需要处理接收到的消息 }); }, beforeClose() { }, close() { }, }); let checkAndCreateFsmFolder = async function (nextCb: Function, tsString: string) { let isFsmFolderExists = await Editor.Message.request('asset-db', 'query-path', savePath); //Editor.log("FsmFolder", isFsmFolderExists); if (!isFsmFolderExists) { Editor.Message.request("asset-db", "create-asset", savePath, tsString).then((results) => { if (results && nextCb) { nextCb(); } }) } else { if (nextCb) { nextCb(); } } } let checkAndMoveStateMachineLib = async (nextCb: Function) => { let isStateMachineLibExists = await Editor.Message.request('asset-db', 'query-path', savePath + "StateMachine.ts"); let originalStateMachineLib = await Editor.Message.request('asset-db', 'query-path', "extensions://visual-fsm/panel/StateMachine.ts"); //Editor.log("StateMachineLib", isStateMachineLibExists); if (!isStateMachineLibExists) { writeFileSync(isStateMachineLibExists as string, readFileSync(originalStateMachineLib as string)); Editor.Message.request("asset-db", "refresh-asset", savePath + "StateMachine.ts").then((results) => { if (nextCb) { nextCb(); } }); } else { if (nextCb) { nextCb(); } } } let fsmCallbacksDecorator = function (rawCallback: any) { return `#this.${rawCallback}#` //return `#target.${rawCallback}#` } let filterForEmptyString = function (rawCallback: any) { if (rawCallback === null || rawCallback.length === 0) { return false; } return true; } let checkAndLoadFsm = function (iframe: any, uuid: any, filePath: any, failcb: Function) { //选中后判断是否是状态机代码 if (filePath) { let postfix = path.extname(filePath); let name = path.basename(filePath); //Editor.log(postfix); //Editor.log(name); if (postfix === '.ts') { //是代码 //判断是否是状态机代码 readFile(filePath, 'utf-8', function (err, data) { let gojsResultRegArray = /###(.*)###/.exec(data); if (gojsResultRegArray !== null && gojsResultRegArray.length >= 2) { let gojsResult = gojsResultRegArray[1]; //是状态机代码 //进入编辑模式 修改后需要提醒是导出还是修改保存,还是取消 currentUuid = uuid; currentName = name; let reEditFsmParam = { name: currentName, model: gojsResult }; //todo // 添加回调锁,控制异步 iframe.contentWindow.postMessage({message: "re-edit-fsm", data: reEditFsmParam}, '*'); } else { //Editor.log("fail"); if (failcb !== undefined) { failcb(); } } }) } else { //Editor.log("fail"); if (failcb !== undefined) { failcb(); } } } else { //Editor.log("fail"); if (failcb !== undefined) { failcb(); } } } let tsTemplate = ` //#gojsConfig# interface StateNameInterface { #stateNameInterface# } interface EventNameInterface { #eventNameInterface# } import StateMachine from './StateMachine'; export abstract class #className# extends cc.Component { public fsm: any; public fsmTrigger (eventName:string, ...args: any[]) { this.fsm[eventName](...args); } public fsmIs(stateName: string):boolean { return this.fsm.is(stateName); } public fsmCan(eventName: string):boolean { return this.fsm.can(eventName); } public fsmCannot(eventName: string):boolean { return this.fsm.cannot(eventName); } public fsmCurrent():string { return this.fsm.current; } public fsmStartUp() { this.fsm = StateMachine.create(#fsmConfig#, this); } public readonly stateName: StateNameInterface = { #stateNameConfig# }; public readonly eventName: EventNameInterface = { #eventNameConfig# }; #triggersConfig# #callbacksConfig# } `;