Selaa lähdekoodia

新增插件文件夹

lanyunfei 2 kuukautta sitten
vanhempi
commit
62062f5b10

+ 2 - 4
README.md

@@ -1,5 +1,3 @@
-# Vue 3 + Vite
+cocos creator3.0 状态机插件
 
-This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
-
-Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
+插件在fsm下

+ 81 - 0
fsm/@types/schema/package/base/panels.json

@@ -0,0 +1,81 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+    "description": "面板数据 / Panel data",
+    "additionalProperties": false,
+    "patternProperties": {
+        "^[a-zA-Z0-9_-]+$": {
+            "type": "object",
+            "description": "面板名 / Panel name",
+            "properties": {
+                "title": {
+                    "type": "string",
+                    "default": "Default Panel",
+                    "description": "面板标题,支持 i18n:key / Panel title, support for i18n:key (required)"
+                },
+                "main": {
+                    "type": "string",
+                    "default": "dist/panels/default/index.js",
+                    "description": "入口函数 / Entry function (required)"
+                },
+                "icon": {
+                    "type": "string",
+                    "description": "面板图标存放相对目录 / Relative directory for panel icon storage"
+                },
+                "type": {
+                    "type": "string",
+                    "enum": ["dockable", "simple"],
+                    "default": "dockable",
+                    "description": "面板类型(dockable | simple) / Panel type (dockable | simple)"
+                },
+                "flags": {
+                    "type": "object",
+                    "properties": {
+                        "resizable": {
+                            "type": "boolean",
+                            "default": true,
+                            "description": "是否可以改变大小,默认 true / Whether the size can be changed, default true"
+                        },
+                        "save": {
+                            "type": "boolean",
+                            "default": true,
+                            "description": "是否需要保存,默认 false / Whether to save, default false"
+                        },
+                        "alwaysOnTop": {
+                            "type": "boolean",
+                            "default": true,
+                            "description": "是否保持顶层显示,默认 false / Whether to keep the top level display, default false"
+                        }
+                    }
+                },
+                "size": {
+                    "type": "object",
+                    "description": "面板大小信息 / Panel size information",
+                    "properties": {
+                        "min-width": {
+                            "type": "number",
+                            "default": 200,
+                            "description": "面板最小宽度 / Minimum panel width"
+                        },
+                        "min-height": {
+                            "type": "number",
+                            "default": 200,
+                            "description": "面板最小高度 / Minimum panel height"
+                        },
+                        "width": {
+                            "type": "number",
+                            "default": 400,
+                            "description": " 面板默认宽度 / Panel Default Width"
+                        },
+                        "height": {
+                            "type": "number",
+                            "default": 600,
+                            "description": "面板默认高度 / Panel Default Height"
+                        }
+                    }
+                }
+            },
+            "required": ["title", "main"]
+        }
+    }
+}

+ 9 - 0
fsm/@types/schema/package/contributions/index.json

@@ -0,0 +1,9 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+    "description": "其他扩展插件的扩展配置 / Extended configuration for other extension plugins",
+    "properties": {
+
+    },
+    "required": []
+}

+ 64 - 0
fsm/@types/schema/package/index.json

@@ -0,0 +1,64 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "type": "object",
+    "description": "插件定义文件 / Extension definition file",
+    "properties": {
+        "author": {
+            "type": "string",
+            "description": "作者 / Author",
+            "default": "Cocos Creator Developer"
+        },
+        "contributions": {
+            "$ref": "./contributions/index.json"
+        },
+        "dependencies": {
+            "type": "object",
+            "description": "发布时所需的依赖库 / Dependencies required for publishing"
+        },
+        "description": {
+            "type": "string",
+            "description": "简要介绍扩展关键特性、用途,支持 i18n / Brief introduction of the key features and uses of the extension, supporting i18n"
+        },
+        "devDependencies": {
+            "type": "object",
+            "description": "开发时所需的依赖库 / Dependencies required for development"
+        },
+        "editor": {
+            "type": "string",
+            "description": "支持的 Cocos Creator 编辑器版本,支持 semver 格式 / Supported Cocos Creator editor version, supporting semver format"
+        },
+        "main": {
+            "type": "string",
+            "description": "入口函数 / Entry function",
+            "default": "./dist/index.js"
+        },
+        "name": {
+            "type": "string",
+            "description": "不能以 _ 或 . 开头、不能含有大写字母,也不能含有 URL 的非法字符例如 .、' 和 ,。 / Cannot start with _ or., cannot contain uppercase letters, and cannot contain URL illegal characters such as.,'and,",
+            "default": "Custom Extension"
+        },
+        "package_version": {
+            "type": "number",
+            "description": "扩展系统预留版本号 / Extension system reserved version number",
+            "default": 2
+        },
+        "panels": {
+            "$ref": "./base/panels.json"
+        },
+        "scripts": {
+            "type": "object",
+            "description": "NPM 脚本 / NPM scripts"
+        },
+        "version": {
+            "type": "string",
+            "description": "版本号字符串 / Version number string",
+            "default": "1.0.0"
+        }
+    },
+    "required": [
+        "author",
+        "name",
+        "package_version",
+        "version"
+    ]
+}

+ 24 - 0
fsm/README-zh-CN.md

@@ -0,0 +1,24 @@
+# 项目简介
+
+一份包含面板的扩展,展示了如何通过消息和菜单打开面板,以及与面板通讯。
+
+## 开发环境
+
+Node.js
+
+## 安装
+
+```bash
+# 安装依赖模块
+npm install
+# 构建
+npm run build
+```
+
+## 用法
+
+启用扩展后,点击主菜单栏中的 `面板 -> {name} -> 默认面板`,即可打开扩展的默认面板。
+
+依次点击顶部菜单的 `开发者 -> {name} -> 发送消息给面板` 即可发送消息给默认面板,如果此时存在默认面板,将会调用面板的 `hello` 方法。
+
+点击 `发送消息给面板` 后,根据 `package.json` 中 `contributions.menu` 的定义将发送一条消息 `send-to-panel` 给扩展。根据 `package.json` 中 `contributions.messages` 的定义当扩展收到 `send-to-panel` 后将会使 `default` 面板调用 `hello` 方法。

+ 24 - 0
fsm/README.md

@@ -0,0 +1,24 @@
+# Project Title
+
+An extension that shows how to open and communicate with the panel through messages and menus.
+
+## Development Environment
+
+Node.js
+
+## Install
+
+```bash
+# Install dependent modules
+npm install
+# build
+npm run build
+```
+
+## Usage
+
+After enabling the extension, click `Panel -> fsm -> Default Panel` in the main menu bar to open the default panel of the extension.
+
+To send a message to the default panel, click `Developer -> fsm -> Send Message to Panel` at the top of the menu. If the default panel exists, the `hello` method of the panel will be called.
+
+After clicking `Send Message to Panel`, a message `send-to-panel` will be sent to the extension as defined by `contributions.menu` in `package.json`. When the extension receives the `send-to-panel` message, it will cause the `default` panel to call the `hello` method as defined by `contributions.messages` in `package.json`.

+ 22 - 0
fsm/base.tsconfig.json

@@ -0,0 +1,22 @@
+{
+    "$schema": "https://schemastore.azurewebsites.net/schemas/json/tsconfig.json",
+    "compilerOptions": {
+        "target": "ES2017",
+        "module": "CommonJS",
+        "moduleResolution": "node",
+        "inlineSourceMap": true,
+        "inlineSources": true,
+        "esModuleInterop": true,
+        "skipLibCheck": true,
+        "strict": true,
+        "experimentalDecorators": true,
+        "forceConsistentCasingInFileNames": true,
+        "resolveJsonModule": true,
+        "outDir": "./dist",
+        "rootDir": "./source",
+        "types": [
+            "node",
+            "@cocos/creator-types/editor",
+        ]
+    }
+}

+ 1 - 0
fsm/i18n/en.js

@@ -0,0 +1 @@
+"use strict";module.exports={open_panel:"Default Panel",send_to_panel:"Send message to Default Panel",description:"Extension with a panel"};

+ 1 - 0
fsm/i18n/zh.js

@@ -0,0 +1 @@
+"use strict";module.exports={open_panel:"默认面板",send_to_panel:"发送消息给面板",description:"含有一个面板的扩展"};

+ 97 - 0
fsm/package-lock.json

@@ -0,0 +1,97 @@
+{
+    "name": "fsm",
+    "version": "1.0.0",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {
+        "": {
+            "name": "fsm",
+            "version": "1.0.0",
+            "hasInstallScript": true,
+            "dependencies": {
+                "fs-extra": "^10.0.0",
+                "typescript": "^5.5.4"
+            },
+            "devDependencies": {
+                "@cocos/creator-types": "^3.8.3",
+                "@types/fs-extra": "^9.0.5",
+                "@types/node": "^18.17.1"
+            }
+        },
+        "node_modules/@cocos/creator-types": {
+            "version": "3.8.3",
+            "resolved": "https://registry.npmmirror.com/@cocos/creator-types/-/creator-types-3.8.3.tgz",
+            "integrity": "sha512-dVAgORX+dWzINdM6gGOcgFNbFXUItEv9Dg2DCWWPHN1qMHaqUe1h+hslcIsJK9OxgOcmS6Lh9FZcklfXn5jkOQ==",
+            "dev": true
+        },
+        "node_modules/@types/fs-extra": {
+            "version": "9.0.13",
+            "resolved": "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-9.0.13.tgz",
+            "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
+            "dev": true,
+            "dependencies": {
+                "@types/node": "*"
+            }
+        },
+        "node_modules/@types/node": {
+            "version": "18.19.50",
+            "resolved": "https://registry.npmmirror.com/@types/node/-/node-18.19.50.tgz",
+            "integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==",
+            "dev": true,
+            "dependencies": {
+                "undici-types": "~5.26.4"
+            }
+        },
+        "node_modules/fs-extra": {
+            "version": "10.1.0",
+            "license": "MIT",
+            "dependencies": {
+                "graceful-fs": "^4.2.0",
+                "jsonfile": "^6.0.1",
+                "universalify": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "node_modules/graceful-fs": {
+            "version": "4.2.11",
+            "license": "ISC"
+        },
+        "node_modules/jsonfile": {
+            "version": "6.1.0",
+            "license": "MIT",
+            "dependencies": {
+                "universalify": "^2.0.0"
+            },
+            "optionalDependencies": {
+                "graceful-fs": "^4.1.6"
+            }
+        },
+        "node_modules/typescript": {
+            "version": "5.5.4",
+            "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.5.4.tgz",
+            "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+            "bin": {
+                "tsc": "bin/tsc",
+                "tsserver": "bin/tsserver"
+            },
+            "engines": {
+                "node": ">=14.17"
+            }
+        },
+        "node_modules/undici-types": {
+            "version": "5.26.5",
+            "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz",
+            "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+            "dev": true
+        },
+        "node_modules/universalify": {
+            "version": "2.0.0",
+            "license": "MIT",
+            "engines": {
+                "node": ">= 10.0.0"
+            }
+        }
+    }
+}

+ 57 - 0
fsm/package.json

@@ -0,0 +1,57 @@
+{
+  "$schema": "./@types/schema/package/index.json",
+  "package_version": 2,
+  "name": "fsm",
+  "version": "1.0.0",
+  "author": "lan",
+  "editor": ">=3.8.3",
+  "scripts": {
+    "preinstall": "node ./scripts/preinstall.js",
+    "build": "tsc"
+  },
+  "description": "i18n:fsm.description",
+  "main": "./dist/main.js",
+  "dependencies": {
+    "fs-extra": "^10.0.0",
+    "typescript": "^5.5.4"
+  },
+  "devDependencies": {
+    "@cocos/creator-types": "^3.8.3",
+    "@types/fs-extra": "^9.0.5",
+    "@types/node": "^18.17.1"
+  },
+  "panels": {
+    "default": {
+      "title": "fsm Default Panel",
+      "type": "dockable",
+      "main": "dist/panels/default",
+      "size": {
+        "min-width": 400,
+        "min-height": 300,
+        "width": 1024,
+        "height": 600
+      }
+    }
+  },
+  "contributions": {
+    "menu": [
+      {
+        "path": "i18n:menu.panel/fsm",
+        "label": "i18n:fsm.open_panel",
+        "message": "open-panel"
+      }
+    ],
+    "messages": {
+      "open-panel": {
+        "methods": [
+          "openPanel"
+        ]
+      },
+      "selection:select": {
+        "methods": [
+          "default.selectionSelected"
+        ]
+      }
+    }
+  }
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
fsm/scripts/preinstall.js


+ 27 - 0
fsm/source/main.ts

@@ -0,0 +1,27 @@
+// @ts-ignore
+import packageJSON from '../package.json';
+/**
+ * @en Registration method for the main process of Extension
+ * @zh 为扩展的主进程的注册方法
+ */
+export const methods: { [key: string]: (...any: any) => any } = {
+    /**
+     * @en A method that can be triggered by message
+     * @zh 通过 message 触发的方法
+     */
+    openPanel() {
+        Editor.Panel.open(packageJSON.name);
+    },
+};
+
+/**
+ * @en Method Triggered on Extension Startup
+ * @zh 扩展启动时触发的方法
+ */
+export function load() { }
+
+/**
+ * @en Method triggered when uninstalling the extension
+ * @zh 卸载扩展时触发的方法
+ */
+export function unload() { }

+ 496 - 0
fsm/source/panels/default/index.ts

@@ -0,0 +1,496 @@
+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#
+}
+`;

+ 22 - 0
fsm/static/style/default/index.css

@@ -0,0 +1,22 @@
+/* 确保 HTML 和 body 元素占满整个视口 */
+html, body {
+    margin: 0;
+    padding: 0;
+    height: 100%;
+    width: 100%;
+}
+
+/* 父容器 (div) 需要撑满整个视口 */
+.fullscreen-container {
+    height: 100%;
+    width: 100%;
+    margin: 0;
+    padding: 0;
+}
+
+/* 确保 iframe 占据父容器的全部空间 */
+iframe {
+    width: 100%;
+    height: 100%;
+    border: none; /* 去掉 iframe 的边框 */
+}

+ 6 - 0
fsm/static/template/default/index.html

@@ -0,0 +1,6 @@
+<head>
+    <meta http-equiv="Content-Security-Policy" content="frame-src http://localhost:5173/">
+</head>
+<div class="fullscreen-container">
+    <iframe id="iframe" src="http://localhost:5173/"></iframe>
+</div>

+ 11 - 0
fsm/tsconfig.json

@@ -0,0 +1,11 @@
+{
+    "extends": "./base.tsconfig.json",
+    "compilerOptions": {
+        "outDir": "./dist",
+        "rootDir": "./source",
+        "types": [
+            "node",
+            "@cocos/creator-types/editor",
+        ]
+    }
+}