index.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. import {existsSync, readFile, readFileSync, writeFileSync} from 'fs-extra';
  2. import path, {join} from 'path';
  3. import {exists} from "fs";
  4. let currentUuid: string = '';
  5. let currentName: string = '';
  6. let scriptPath: string = 'db://assets/script/'
  7. let savePath: string = scriptPath + 'fsm/'
  8. /**
  9. * @zh 如果希望兼容 3.3 之前的版本可以使用下方的代码
  10. * @en You can add the code below if you want compatibility with versions prior to 3.3
  11. */
  12. // Editor.Panel.define = Editor.Panel.define || function(options: any) { return options }
  13. module.exports = Editor.Panel.define({
  14. listeners: {
  15. show() {
  16. },
  17. hide() {
  18. },
  19. },
  20. template: readFileSync(join(__dirname, '../../../static/template/default/index.html'), 'utf-8'),
  21. style: readFileSync(join(__dirname, '../../../static/style/default/index.css'), 'utf-8'),
  22. $: {
  23. iframe: '#iframe',
  24. },
  25. methods: {
  26. async import() {
  27. let self = this
  28. let path = await Editor.Message.request('asset-db', 'query-path', savePath);
  29. if (!path) {
  30. return
  31. }
  32. //Editor.log(defaultPath);
  33. Editor.Dialog.select({
  34. title: "提示 : 聚焦资源管理器中fsm目录下的状态机代码可以快速导入状态机",
  35. path: path,
  36. filters: [
  37. {name: 'fsm', extensions: ["ts"]}
  38. ],
  39. }).then(async (args) => {
  40. if (args?.filePaths && args.filePaths.length > 0) {
  41. let fspath = args.filePaths[0];
  42. let uuid = await Editor.Message.request('asset-db', 'query-uuid', fspath);
  43. checkAndLoadFsm(self.$.iframe, uuid, fspath, () => {
  44. Editor.Dialog.error("导入的文件不是状态机代码");
  45. });
  46. }
  47. });
  48. },
  49. async checkSameName(args: any) {
  50. let fileName = args.className + ".ts";
  51. let filePath = await Editor.Message.request('asset-db', 'query-path', savePath + fileName);
  52. let isFileExists = existsSync(filePath as string);
  53. this.$.iframe.contentWindow.postMessage({
  54. message: "export-callback",
  55. data: {isFileExists, className: args.className}
  56. }, '*');
  57. },
  58. panelClose() {
  59. currentName = '';
  60. currentUuid = '';
  61. },
  62. reset() {
  63. currentName = '';
  64. currentUuid = '';
  65. },
  66. save(args: any) {
  67. let self = this
  68. let preClassName = null;
  69. if (args.isExport) {
  70. preClassName = args.className;
  71. } else {
  72. if (currentName && currentUuid) {
  73. preClassName = currentName.split(".")[0];
  74. } else {
  75. //todo 加锁
  76. self.$.iframe.contentWindow.postMessage({
  77. message: "save-callback",
  78. data: {error: true}
  79. }, '*');
  80. return;
  81. }
  82. }
  83. //Editor.log("isExport",args.isExport);
  84. let modelAsObj = args.model;
  85. let nodeDataArray = modelAsObj.nodeDataArray;
  86. let linkDataArray = modelAsObj.linkDataArray;
  87. let keyToNodeDataArray = [];
  88. let initial = "";
  89. let events = [];
  90. let callbacks: any = {};
  91. let stateNameInterface = [];
  92. let uniqueStateNameArray = [];
  93. let eventNameInterface = [];
  94. let uniqueEventNameArray = [];
  95. let eventNameConfig = [];
  96. let stateNameConfig = [];
  97. let callbacksConfig = [];
  98. //let callbacksInterface = [];
  99. let triggersConfig = [];
  100. let uniqueCallbacksArray = [];
  101. let callbacksImplementConfig = [];
  102. for (let i = 0, l = nodeDataArray.length; i < l; i++) {
  103. keyToNodeDataArray[nodeDataArray[i].key] = nodeDataArray[i];
  104. if (nodeDataArray[i].isInit) {
  105. initial = nodeDataArray[i].text;
  106. }
  107. let stateName = nodeDataArray[i].text;
  108. let localPreCallbacks = JSON.parse(nodeDataArray[i].callbacks).enter;
  109. if (!!localPreCallbacks.length) {
  110. let localPreCallbacksDecorators = localPreCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator);
  111. callbacks[`onenter${stateName}`] = localPreCallbacksDecorators;
  112. }
  113. let localPostCallbacks = JSON.parse(nodeDataArray[i].callbacks).leave;
  114. if (!!localPostCallbacks.length) {
  115. let localPostCallbacksDecorators = localPostCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator);
  116. callbacks[`onleave${stateName}`] = localPostCallbacksDecorators;
  117. }
  118. //stateNameInterface.push(`${stateName}:string`);
  119. //stateNameConfig.push(`${stateName}:"${stateName}"`);
  120. uniqueStateNameArray[stateName] = true;
  121. for (let i = 0, l = localPreCallbacks.length; i < l; i++) {
  122. let callbackName = localPreCallbacks[i];
  123. uniqueCallbacksArray[callbackName] = true;
  124. //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`);
  125. }
  126. for (let i = 0, l = localPostCallbacks.length; i < l; i++) {
  127. let callbackName = localPostCallbacks[i];
  128. uniqueCallbacksArray[callbackName] = true;
  129. //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`);
  130. }
  131. }
  132. for (let i = 0, l = linkDataArray.length; i < l; i++) {
  133. let name = linkDataArray[i].text || "event";
  134. let from = keyToNodeDataArray[linkDataArray[i].from].text;
  135. let to = keyToNodeDataArray[linkDataArray[i].to].text;
  136. events.push({name, from, to});
  137. let eventName = name;
  138. let localPreCallbacks = JSON.parse(linkDataArray[i].callbacks).before;
  139. if (!!localPreCallbacks.length) {
  140. let localPreCallbacksDecorators = localPreCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator);
  141. let callbacksIdxName = `onbefore${eventName}`;
  142. if (callbacks[callbacksIdxName] === undefined) {
  143. callbacks[callbacksIdxName] = {};
  144. }
  145. callbacks[callbacksIdxName][from] = localPreCallbacksDecorators;
  146. }
  147. let localPostCallbacks = JSON.parse(linkDataArray[i].callbacks).after;
  148. if (!!localPostCallbacks.length) {
  149. let localPostCallbacksDecorators = localPostCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator);
  150. //callbacks[`onafter${eventName}`] = localPostCallbacksDecorators;
  151. let callbacksIdxName = `onafter${eventName}`;
  152. if (callbacks[callbacksIdxName] === undefined) {
  153. callbacks[callbacksIdxName] = {};
  154. }
  155. callbacks[callbacksIdxName][from] = localPostCallbacksDecorators;
  156. }
  157. //eventNameInterface.push(`${eventName}:string`);
  158. //eventNameConfig.push(`${eventName}:"${eventName}"`);
  159. //triggersConfig.push(`public ${eventName}(...args:any[]): void {this.fsm["${eventName}"](...args);}`);
  160. uniqueEventNameArray[eventName] = true;
  161. for (let i = 0, l = localPreCallbacks.length; i < l; i++) {
  162. let callbackName = localPreCallbacks[i];
  163. uniqueCallbacksArray[callbackName] = true;
  164. //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`);
  165. }
  166. for (let i = 0, l = localPostCallbacks.length; i < l; i++) {
  167. let callbackName = localPostCallbacks[i];
  168. uniqueCallbacksArray[callbackName] = true;
  169. //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`);
  170. }
  171. }
  172. let globalCallbacksText = modelAsObj.globalCallbacksText
  173. let globalCallbacksObj = JSON.parse(globalCallbacksText);
  174. let globalEnterCallbacks = globalCallbacksObj.enter;
  175. let globalLeaveCallbacks = globalCallbacksObj.leave;
  176. let globalBeforeCallbacks = globalCallbacksObj.before;
  177. let globalAfterCallbacks = globalCallbacksObj.after;
  178. if (!!globalEnterCallbacks.length) {
  179. let globalEnterCallbacksDecorators = globalEnterCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator);
  180. callbacks[`onenterstate`] = globalEnterCallbacksDecorators;
  181. }
  182. if (!!globalLeaveCallbacks.length) {
  183. let globalLeaveCallbacksDecorators = globalLeaveCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator);
  184. callbacks[`onleavestate`] = globalLeaveCallbacksDecorators;
  185. }
  186. if (!!globalBeforeCallbacks.length) {
  187. let globalBeforeCallbacksDecorators = globalBeforeCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator);
  188. callbacks[`onbeforeevent`] = globalBeforeCallbacksDecorators;
  189. }
  190. if (!!globalAfterCallbacks.length) {
  191. let globalAfterCallbacksDecorators = globalAfterCallbacks.filter(filterForEmptyString).map(fsmCallbacksDecorator);
  192. callbacks[`onafterevent`] = globalAfterCallbacksDecorators;
  193. }
  194. for (let i = 0, l = globalEnterCallbacks.length; i < l; i++) {
  195. let callbackName = globalEnterCallbacks[i];
  196. uniqueCallbacksArray[callbackName] = true;
  197. //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`);
  198. }
  199. for (let i = 0, l = globalLeaveCallbacks.length; i < l; i++) {
  200. let callbackName = globalLeaveCallbacks[i];
  201. uniqueCallbacksArray[callbackName] = true;
  202. //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`);
  203. }
  204. for (let i = 0, l = globalBeforeCallbacks.length; i < l; i++) {
  205. let callbackName = globalBeforeCallbacks[i];
  206. uniqueCallbacksArray[callbackName] = true;
  207. //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`);
  208. }
  209. for (let i = 0, l = globalAfterCallbacks.length; i < l; i++) {
  210. let callbackName = globalAfterCallbacks[i];
  211. uniqueCallbacksArray[callbackName] = true;
  212. //callbacksConfig.push(`protected abstract ${callbackName}(eventName:string,from:string,to:string,...args:any[])`);
  213. }
  214. let uniqueCallbacksIdxArray = Object.keys(uniqueCallbacksArray);
  215. for (let i = 0, l = uniqueCallbacksIdxArray.length; i < l; i++) {
  216. let callbackName = uniqueCallbacksIdxArray[i];
  217. callbacksConfig.push(`public abstract ${callbackName}(eventName: string, from: string, to: string, ...args: any[]) : void`);
  218. //callbacksInterface.push(`${callbackName}(eventName:string,from:string,to:string,...args:any[]) : void`);
  219. //callbacksImplementConfig.push(`protected ${callbackName} = (eventName:string,from:string,to:string,...args:any[]): void => {}`);
  220. }
  221. let uniqueEventNameIdxArray = Object.keys(uniqueEventNameArray);
  222. for (let i = 0, l = uniqueEventNameIdxArray.length; i < l; i++) {
  223. let eventName = uniqueEventNameIdxArray[i];
  224. triggersConfig.push(`public ${eventName}(...args: any[]): void { this.fsm['${eventName}'](...args); }`);
  225. eventNameInterface.push(`${eventName}: string`);
  226. eventNameConfig.push(`${eventName}: '${eventName}'`);
  227. }
  228. let uniqueStateNameIdxArray = Object.keys(uniqueStateNameArray);
  229. for (let i = 0, l = uniqueStateNameIdxArray.length; i < l; i++) {
  230. let stateName = uniqueStateNameIdxArray[i];
  231. stateNameInterface.push(`${stateName}: string`);
  232. stateNameConfig.push(`${stateName}: '${stateName}'`);
  233. }
  234. let fsmConfig = {
  235. initial,
  236. events,
  237. callbacks
  238. }
  239. //let dist = {};
  240. //dist.flowForFsm = fsmConfig;
  241. //dist.flowForGoJs = JSON.parse(myDiagram.model.toJson());
  242. //console.log(dist);
  243. //editKey = null;
  244. //selectLink = false;
  245. //editor.setText(JSON.stringify(dist));
  246. //editor.setMode("code");
  247. //editor.expandAll();
  248. var fsmConfigDecorator = JSON.stringify(fsmConfig).replace(/"#/g, "").replace(/#"/g, "")
  249. .replace(/"/g, "'").replace(/{'/g, "{").replace(/':/g, ": ").replace(/,'/g, ",")
  250. //var flowForFsm = new File([fsmConfigDecorator], "flowForFsm.json", {type: "text/plain;charset=utf-8"});
  251. //saveAs(flowForFsm);
  252. //var flowForGoJs = new File([myDiagram.model.toJson()], "flowForGoJs.json", {type: "text/plain;charset=utf-8"});
  253. //saveAs(flowForGoJs);
  254. // if(exportType === "exportGoJs"){
  255. // //var flowForGoJsFile = new File([myDiagram.model.toJson()], "flow-for-gojs.json", {type: "text/plain;charset=utf-8"});
  256. // var flowForGoJsFile = new File([JSON.stringify(modelAsObj)], "FlowForGojs.json", {type: "text/plain;charset=utf-8"});
  257. // saveAs(flowForGoJsFile);
  258. // return;
  259. // }else if(exportType === "all"){
  260. // //var flowForGoJsFile = new File([myDiagram.model.toJson()], "flow-for-gojs.json", {type: "text/plain;charset=utf-8"});
  261. // var flowForGoJsFile = new File([JSON.stringify(modelAsObj)], "FlowForGojs.json", {type: "text/plain;charset=utf-8"});
  262. // saveAs(flowForGoJsFile);
  263. // }
  264. //let className = currentName.split(".")[0];
  265. //console.log(fsmConfigDecorator);
  266. let tsString = tsTemplate.replace(/#fsmConfig#/g, fsmConfigDecorator)
  267. .replace(/#className#/g, preClassName)
  268. .replace(/#gojsConfig#/g, "###" + JSON.stringify(modelAsObj) + "###")
  269. .replace(/#stateNameConfig#/g, stateNameConfig.join(",\n ") + ",")
  270. .replace(/#eventNameConfig#/g, eventNameConfig.join(",\n ") + ",")
  271. .replace(/#stateNameInterface#/g, stateNameInterface.join(";\n ") + ";\n")
  272. .replace(/#eventNameInterface#/g, eventNameInterface.join(";\n ") + ";\n")
  273. .replace(/#triggersConfig#/g, triggersConfig.join("\n ") + "\n")
  274. if (callbacksConfig.length > 0) {
  275. tsString = tsString.replace(/#callbacksConfig#/g, callbacksConfig.join("\n ") + "\n")
  276. } else {
  277. tsString = tsString.replace(/#callbacksConfig#/g, "") // 仅去掉标签
  278. }
  279. //.replace(/#callbacksImplementConfig#/g,callbacksImplementConfig.join(";\n ") + ";\n")
  280. //.replace(/#callbacksInterface#/g,callbacksInterface.join("\n ") + ";\n");
  281. //console.log(tsString);
  282. //todo 验证fsm和状态机库的存在性 done
  283. // let isFsmFolderExists = Editor.assetdb.existsByPath(savePath);
  284. // Editor.log(isFsmFolderExists);
  285. // if (!isFsmFolderExists) {
  286. // Editor.Ipc.sendToMain("asset-db:create-asset", savePath, function (err, result) {
  287. // //todo 验证状态机库的存在性
  288. // })
  289. // } else {
  290. checkAndCreateFsmFolder(() => {
  291. checkAndMoveStateMachineLib(async () => {
  292. //todo 验证文件存在性
  293. let path = await Editor.Message.request('asset-db', 'query-path', savePath + preClassName + '.ts');
  294. writeFileSync(path as string, tsString);
  295. Editor.Message.request("asset-db", 'refresh-asset', savePath + preClassName + '.ts').then((results) => {
  296. Editor.Dialog.info(preClassName + '.ts' + " 保存成功!", {
  297. buttons: ["好的,我知道了"],
  298. title: "提示",
  299. detail: preClassName + '.ts' + " 将会被保存于路径 " + savePath
  300. }).then((result) => {
  301. self.$.iframe.contentWindow.postMessage({message: "save-callback", data: {}}, '*');
  302. });
  303. });
  304. });
  305. }, tsString);
  306. },
  307. // modify 下划线
  308. async selectionSelected(type: string, uuid: string) {
  309. //Editor.log(e);
  310. //Editor.log("args",args);
  311. //Editor.log(uuidArray[0]);
  312. if (type !== 'asset') {
  313. //Editor.log("before return");
  314. return;
  315. } else {
  316. let fspath = await Editor.Message.request('asset-db', 'query-path', uuid);
  317. if (fspath && uuid) {
  318. checkAndLoadFsm(this.$.iframe, uuid, fspath, () => {
  319. //Editor.Dialog.error("导入的文件不是状态机代码");
  320. });
  321. }
  322. }
  323. },
  324. },
  325. ready() {
  326. let self = this as any
  327. window.addEventListener('message', (event) => {
  328. // 处理消息
  329. if (self[event.data.message]) self[event.data.message](event.data.args, event.data.callback)
  330. // 你可以在这里根据需要处理接收到的消息
  331. });
  332. },
  333. beforeClose() {
  334. },
  335. close() {
  336. },
  337. });
  338. let checkAndCreateFsmFolder = async function (nextCb: Function, tsString: string) {
  339. let isFsmFolderExists = await Editor.Message.request('asset-db', 'query-path', savePath);
  340. //Editor.log("FsmFolder", isFsmFolderExists);
  341. if (!isFsmFolderExists) {
  342. Editor.Message.request("asset-db", "create-asset", savePath, tsString).then((results) => {
  343. if (results && nextCb) {
  344. nextCb();
  345. }
  346. })
  347. } else {
  348. if (nextCb) {
  349. nextCb();
  350. }
  351. }
  352. }
  353. let checkAndMoveStateMachineLib = async (nextCb: Function) => {
  354. let isStateMachineLibExists = await Editor.Message.request('asset-db', 'query-path', savePath + "StateMachine.ts");
  355. let originalStateMachineLib = await Editor.Message.request('asset-db', 'query-path', "extensions://visual-fsm/panel/StateMachine.ts");
  356. //Editor.log("StateMachineLib", isStateMachineLibExists);
  357. if (!isStateMachineLibExists) {
  358. writeFileSync(isStateMachineLibExists as string, readFileSync(originalStateMachineLib as string));
  359. Editor.Message.request("asset-db", "refresh-asset", savePath + "StateMachine.ts").then((results) => {
  360. if (nextCb) {
  361. nextCb();
  362. }
  363. });
  364. } else {
  365. if (nextCb) {
  366. nextCb();
  367. }
  368. }
  369. }
  370. let fsmCallbacksDecorator = function (rawCallback: any) {
  371. return `#this.${rawCallback}#`
  372. //return `#target.${rawCallback}#`
  373. }
  374. let filterForEmptyString = function (rawCallback: any) {
  375. if (rawCallback === null || rawCallback.length === 0) {
  376. return false;
  377. }
  378. return true;
  379. }
  380. let checkAndLoadFsm = function (iframe: any, uuid: any, filePath: any, failcb: Function) {
  381. //选中后判断是否是状态机代码
  382. if (filePath) {
  383. let postfix = path.extname(filePath);
  384. let name = path.basename(filePath);
  385. //Editor.log(postfix);
  386. //Editor.log(name);
  387. if (postfix === '.ts') {
  388. //是代码
  389. //判断是否是状态机代码
  390. readFile(filePath, 'utf-8', function (err, data) {
  391. let gojsResultRegArray = /###(.*)###/.exec(data);
  392. if (gojsResultRegArray !== null && gojsResultRegArray.length >= 2) {
  393. let gojsResult = gojsResultRegArray[1];
  394. //是状态机代码
  395. //进入编辑模式 修改后需要提醒是导出还是修改保存,还是取消
  396. currentUuid = uuid;
  397. currentName = name;
  398. let reEditFsmParam = {
  399. name: currentName,
  400. model: gojsResult
  401. };
  402. //todo
  403. // 添加回调锁,控制异步
  404. iframe.contentWindow.postMessage({message: "re-edit-fsm", data: reEditFsmParam}, '*');
  405. } else {
  406. //Editor.log("fail");
  407. if (failcb !== undefined) {
  408. failcb();
  409. }
  410. }
  411. })
  412. } else {
  413. //Editor.log("fail");
  414. if (failcb !== undefined) {
  415. failcb();
  416. }
  417. }
  418. } else {
  419. //Editor.log("fail");
  420. if (failcb !== undefined) {
  421. failcb();
  422. }
  423. }
  424. }
  425. let tsTemplate = `
  426. //#gojsConfig#
  427. interface StateNameInterface {
  428. #stateNameInterface#
  429. }
  430. interface EventNameInterface {
  431. #eventNameInterface#
  432. }
  433. import StateMachine from './StateMachine';
  434. export abstract class #className# extends cc.Component {
  435. public fsm: any;
  436. public fsmTrigger (eventName:string, ...args: any[]) {
  437. this.fsm[eventName](...args);
  438. }
  439. public fsmIs(stateName: string):boolean {
  440. return this.fsm.is(stateName);
  441. }
  442. public fsmCan(eventName: string):boolean {
  443. return this.fsm.can(eventName);
  444. }
  445. public fsmCannot(eventName: string):boolean {
  446. return this.fsm.cannot(eventName);
  447. }
  448. public fsmCurrent():string {
  449. return this.fsm.current;
  450. }
  451. public fsmStartUp() {
  452. this.fsm = StateMachine.create(#fsmConfig#, this);
  453. }
  454. public readonly stateName: StateNameInterface = {
  455. #stateNameConfig#
  456. };
  457. public readonly eventName: EventNameInterface = {
  458. #eventNameConfig#
  459. };
  460. #triggersConfig#
  461. #callbacksConfig#
  462. }
  463. `;