123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676 |
- <!doctype html>
- <html lang="en">
- <head>
- <meta charset="UTF-8"/>
- <link rel="icon" type="image/svg+xml" href="/vite.svg"/>
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
- <title>cocos fsm</title>
- </head>
- <body>
- <div id="app"></div>
- <script type="module" src="/src/main.js"></script>
- <style>
- textarea {
- background-color: rgb(255, 255, 224);
- resize: none;
- border-color: yellow;
- border-style: solid dashed;
- border-width: 10px;
- border-radius: 15px 15px;
- }
- .fsm-menu-item:hover {
- border-style: none;
- }
- .fsm-menu-item {
- color: black;
- }
- .fsm-menu-item:hover {
- text-decoration: none;
- color: lightgray;
- }
- .fsm-menu {
- padding-left: 5px;
- padding-right: 5px;
- height: 25px;
- }
- </style>
- <link href="/src/jsoneditor.css" rel="stylesheet" type="text/css">
- <script src="/src/jsoneditor.js"></script>
- <script src="/src/notie.js"></script>
- <script src="/src/go.js"></script>
- <link href="/src/notie.css" rel="stylesheet" type="text/css">
- <div id="menu" style="z-index:2;left:0px;right:0px;top:0px;height:20px;background-color:white;position: absolute">
- <span class="fsm-menu"><a href="#" class="fsm-menu-item" onclick="importWorkFlow();">导入(alt + o)</a></span>
- <span class="fsm-menu"><a href="#" class="fsm-menu-item" onclick="exportWorkFlow()">导出(alt + e)</a></span>
- <span class="fsm-menu"><a href="#" class="fsm-menu-item" onclick="saveWorkFlow();">保存(alt + s)</a></span>
- <span class="fsm-menu"><a href="#" class="fsm-menu-item"
- onclick="switchModeWorkFlow();">切换模式(alt + q)</a></span>
- <span class="fsm-menu"><a href="#" class="fsm-menu-item"
- onclick="setInitStateWorkFlow();">初始状态(alt + c)</a></span>
- <span class="fsm-menu"><a href="#" class="fsm-menu-item" onclick="resetWorkFlow();">重置清空(alt + r)</a></span>
- <span class="fsm-menu"><a href="#" class="fsm-menu-item" onclick="getHelp();">帮助(alt + h)</a></span>
- <span class="fsm-menu"><a href="#" class="fsm-menu-item" onclick="getAbout();">关于</a></span>
- </div>
- <h3 id="tips" style="color:#B0B0B0; z-index:3;position:absolute; left:25px;bottom:0px;">norm mode</h3>
- <h3 id="tips2" style="color:#B0B0B0; z-index:3;position:absolute; left:25px;bottom:25px;">help : alt+h </h3>
- <h3 id="reEditNameTips" style="color:#B0B0B0; z-index:3;position:absolute; left:25px;bottom:50px;"></h3>
- <div id="myDiagramDiv"
- style="z-index:1;border:1px #ccc solid;position:absolute; top:20px; left:0px; right:405px; bottom:0px;background-color: #444;"></div>
- <div id="jsonEditorDiv"
- style="border:1px #ccc solid;width:400px;background-color: #444;position:absolute; right:0px; bottom:0px;top: 20px;"></div>
- <script>
- /// 测试Ipc监听
- // require('electron').ipcRenderer.on('test-render-on', function(event, args) {
- // alert("hello");
- // document.getElementById("tips").innerHTML = args;
- // });
- //流程加锁
- let saving = false;
- //保存完成
- // require("electron").ipcRenderer.on("saved", function (event, args) {
- // saving = false;
- // })
- //关于
- let getAbout = function () {
- let info =
- `
- 作者:qbkivlin
- 寄语:望 cocos creator 越来越好
- `;
- alert(info);
- }
- window.addEventListener('message', (event) => {
- // 处理接收到的消息
- console.log('iframe Message received:', event.data);
- let result = event.data.data
- if (event.data.message == 're-edit-fsm') {
- let fileName = result.name;
- let flowForGoJs = JSON.parse(result.model);
- globalCallbacksText = flowForGoJs.globalCallbacksText;
- editor.setText(globalCallbacksText);
- expandJson();
- if (flowForGoJs) {
- myDiagram.model = go.Model.fromJson(flowForGoJs);
- document.getElementById("reEditNameTips").innerText = fileName;
- // fix bug: should set oldInitialStateKey when reload the model
- oldInitialStateKey = getInitialKeyFromGoJs(flowForGoJs);
- } else {
- notie.removeAll();
- notie.alert({type: "error", text: "状态机解析出错", stay: true});
- }
- } else if (event.data.message == 'import-failed') {
- notie.removeAll();
- notie.alert({type: "error", text: "状态机导入出错", stay: true});
- } else if (event.data.message == 'save-callback') {
- saving = false;
- if (result?.error) {
- notie.removeAll();
- notie.alert({
- type: "error",
- text: "不存在状态机保存源,请通过导出的方式( alt + e )生成状态机代码",
- stay: true
- });
- }
- } else if (event.data.message == 'export-callback') {
- if (result.isFileExists) {
- notie.removeAll();
- notie.alert({type: "error", text: "文件名存在冲突", stay: true});
- } else {
- //修改: 直接让main进程进行代码保存
- safetySaveBeforeExport();
- let modelAsText = myDiagram.model.toJson();
- let modelAsObj = JSON.parse(modelAsText);
- modelAsObj.globalCallbacksText = globalCallbacksText;
- saving = true;
- //todo 添加异步验证
- let postDataTmp = {
- message: "save",
- args: {isExport: true, model: modelAsObj, className: result.className},
- }
- window.parent.postMessage(postDataTmp, '*');
- }
- }
- });
- //导入
- let importWorkFlow = function () {
- let postData = {
- message: "import",
- }
- window.parent.postMessage(postData, '*');
- //Editor.Message.send("visual-fsm:import");
- }
- document.addEventListener("keydown", function (e) {
- if ((e.key.toLowerCase() == 'o' || e.code == "KeyO") && e.altKey) {
- importWorkFlow();
- }
- });
- //清空
- let resetWorkFlow = function () {
- notie.removeAll();
- notie.confirm({
- text: "确定要清空当前状态机编辑面板吗?",
- submitText: "确定", // optional, default = 'Yes'
- cancelText: "取消", // optional, default = 'Cancel'
- position: "top", // optional, default = 'top', enum: ['top', 'bottom']
- submitCallback: function () {
- let modelAsObj = myDiagram.model;
- modelAsObj.nodeDataArray = [];
- modelAsObj.linkDataArray = [];
- myDiagram.model = modelAsObj;
- editor.setText(rawGlobalCallbacksText);
- expandJson();
- document.getElementById("reEditNameTips").innerText = "";
- let postData = {
- message: "reset",
- }
- window.parent.postMessage(postData, '*');
- }, // optional
- cancelCallback: function () {
- }, // optional
- });
- }
- document.addEventListener("keydown", function (e) {
- if ((e.key.toLowerCase() == 'r' || e.code == "KeyR") && e.altKey) {
- resetWorkFlow();
- }
- });
- let globalCallbacksText = `{"enter":[],"leave":[],"before":[],"after":[]}`;
- //初始状态的全局回调
- let rawGlobalCallbacksText = `{"enter":[],"leave":[],"before":[],"after":[]}`;
- let modesArray = ['code', 'form', 'text', 'tree', 'view'];
- let expandableArray = ['tree', 'view', 'form'];
- var container = document.getElementById("jsonEditorDiv");
- var options = {
- sortObjectKeys: false,
- mode: 'tree',
- modes: modesArray, // allowed modes
- onError: function (err) {
- notie.removeAll();
- notie.alert({type: "error", text: err, stay: true});
- },
- onModeChange: function () {
- expandJson();
- },
- onEditable: function (node) {
- if (node.field === "enter" || node.field === "leave" || node.field === "before" || node.field === "after")
- return {field: false, value: true};
- return true;
- }
- };
- var editor = new JSONEditor(container, options);
- editor.setText(globalCallbacksText);
- let expandJson = function () {
- let currentMode = editor.getMode();
- if (expandableArray.some((value) => {
- return value === currentMode
- }))
- editor.expandAll();
- }
- expandJson();
- var $ = go.GraphObject.make; // for conciseness in defining templates
- var myDiagram =
- $(go.Diagram, "myDiagramDiv", // must name or refer to the DIV HTML element
- {
- // start everything in the middle of the viewport
- initialContentAlignment: go.Spot.Center,
- // have mouse wheel events zoom in and out instead of scroll up and down
- "toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
- // support double-click in background creating a new node
- "clickCreatingTool.archetypeNodeData": {text: "state", isInit: false},
- // enable undo & redo
- "undoManager.isEnabled": true,
- //"toolManager.linkingTool.isEnabled":false,
- });
- myDiagram.toolManager.linkingTool.isEnabled = false;
- myDiagram.scrollMode = go.Diagram.InfiniteScroll;
- let switchModeWorkFlow = function () {
- myDiagram.toolManager.linkingTool.isEnabled = !myDiagram.toolManager.linkingTool.isEnabled;
- document.getElementById("tips").innerHTML = myDiagram.toolManager.linkingTool.isEnabled ? "link mode" : "norm mode";
- }
- document.addEventListener("keydown", function (e) {
- if ((e.key.toLowerCase() == 'q' || e.code == "KeyQ") && e.altKey) {
- switchModeWorkFlow();
- }
- }.bind(this));
- let preValidateInitialState = function () {
- let flowForGoJs = myDiagram.model;
- let nodeDataArray = flowForGoJs.nodeDataArray;
- for (let i = 0, l = nodeDataArray.length; i < l; i++) {
- if (nodeDataArray[i].isInit) {
- return true;
- }
- }
- return false;
- }
- let isEmptyStateCheck = function () {
- let modelAsObj = myDiagram.model;
- return !modelAsObj.nodeDataArray.length;
- }
- let saveWorkFlow = function () {
- //exportSelect();
- //验证初始化状态
- if (saving) {
- return;
- }
- let isEmptyState = isEmptyStateCheck();
- if (isEmptyState) {
- notie.removeAll();
- notie.alert({type: "error", text: "状态机不能为空", stay: true});
- return;
- }
- let isPass = preValidateInitialState();
- if (!isPass) {
- notie.removeAll();
- notie.alert({type: "error", text: "未指定状态机初始状态", stay: true});
- return;
- }
- let fsmName = document.getElementById("reEditNameTips").innerText;
- notie.removeAll();
- notie.confirm({
- text: `确定要保存状态机 ${fsmName} 吗?`,
- submitText: "确定", // optional, default = 'Yes'
- cancelText: "取消", // optional, default = 'Cancel'
- position: "top", // optional, default = 'top', enum: ['top', 'bottom']
- submitCallback: function () {
- //修改: 直接让main进程进行代码保存
- safetySaveBeforeExport();
- let modelAsText = myDiagram.model.toJson();
- let modelAsObj = JSON.parse(modelAsText);
- modelAsObj.globalCallbacksText = globalCallbacksText;
- saving = true;
- //todo 添加异步验证 done
- let postData = {
- message: "save",
- args: {isExport: false, model: modelAsObj},
- }
- window.parent.postMessage(postData, '*');
- }, // optional
- cancelCallback: function () {
- }, // optional
- });
- }
- document.addEventListener("keydown", function (e) {
- if ((e.key.toLowerCase() == 's' || e.code == "KeyS") && e.altKey) {
- saveWorkFlow();
- }
- });
- let exportWorkFlow = function () {
- //exportSelect();
- //验证初始化状态
- if (saving) {
- return;
- }
- let isEmptyState = isEmptyStateCheck();
- if (isEmptyState) {
- notie.removeAll();
- notie.alert({type: "error", text: "状态机不能为空", stay: true});
- return;
- }
- let isPass = preValidateInitialState();
- if (!isPass) {
- notie.removeAll();
- notie.alert({type: "error", text: "未指定状态机初始状态", stay: true});
- return;
- }
- classNameInput(function (value) {
- let className = value;
- //todo 添加同名验证
- if (className) {
- let postData = {
- message: "checkSameName",
- args: {className: className},
- }
- window.parent.postMessage(postData, '*')
- }
- });
- }
- document.addEventListener("keydown", function (e) {
- if ((e.key.toLowerCase() == 'e' || e.code == "KeyE") && e.altKey) {
- exportWorkFlow();
- }
- })
- document.addEventListener("keydown", function (e) {
- if ((e.key.toLowerCase() == 'h' || e.code == "KeyH") && e.altKey) {
- getHelp();
- }
- });
- let setInitStateWorkFlow = function () {
- if (editKey !== null) {
- let node = myDiagram.findNodeForKey(editKey);
- if (node) {
- myDiagram.model.startTransaction("change isInit");
- myDiagram.model.setDataProperty(node.data, "isInit", !node.data.isInit);
- myDiagram.model.commitTransaction("change isInit");
- }
- }
- if (oldInitialStateKey !== null && oldInitialStateKey !== editKey) {
- let node = myDiagram.findNodeForKey(oldInitialStateKey);
- if (node) {
- myDiagram.model.startTransaction("change isInit");
- myDiagram.model.setDataProperty(node.data, "isInit", false);
- myDiagram.model.commitTransaction("change isInit");
- }
- }
- oldInitialStateKey = editKey;
- }
- document.addEventListener("keydown", function (e) {
- if ((e.key.toLowerCase() == 'c' || e.code == "KeyC") && e.altKey) {
- setInitStateWorkFlow();
- }
- })
- var editKey = null;
- var selectLink = false;
- var linkData = null;
- var multiSelect = false;
- var oldInitialStateKey = null;
- myDiagram.addDiagramListener("ChangedSelection", function (e) {
- if (editKey) {
- let node = myDiagram.findNodeForKey(editKey);
- if (node) {
- myDiagram.model.startTransaction("change callbacks");
- myDiagram.model.setDataProperty(node.data, "callbacks", editor.getText());
- myDiagram.model.commitTransaction("change callbacks");
- }
- }
- if (selectLink) {
- let link = myDiagram.findLinkForData(linkData);
- if (link) {
- myDiagram.model.startTransaction("change callbacks");
- myDiagram.model.setDataProperty(link.data, "callbacks", editor.getText());
- myDiagram.model.commitTransaction("change callbacks");
- }
- }
- if (!editKey && !selectLink && !multiSelect) {
- globalCallbacksText = editor.getText();
- }
- let selection = e.diagram.selection;
- let count = selection.count;
- if (count === 1) {
- let obj = selection.first();
- editKey = obj.data.key;
- if (editKey) {
- //document.getElementById("txt").value = "";
- //Editor.log(obj.data.parameter);
- //editor.set(JSON.parse(obj.data.paramter));
- selectLink = false;
- multiSelect = false;
- editor.setText(obj.data.callbacks || '{"enter":[],"leave":[]}');
- expandJson();
- } else {
- // this is link
- selectLink = true;
- multiSelect = false;
- editor.setText(obj.data.callbacks || '{"before":[],"after":[]}');
- linkData = obj.data;
- expandJson();
- }
- } else if (count === 0) {
- editKey = null;
- selectLink = false;
- multiSelect = false;
- editor.setText(globalCallbacksText);
- expandJson();
- } else if (count >= 2) {
- multiSelect = true;
- }
- //editor.expandAll();
- });
- // define the Node template
- myDiagram.nodeTemplate =
- $(go.Node, "Auto",
- new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
- // define the node's outer shape, which will surround the TextBlock
- $(go.Shape, "RoundedRectangle",
- {
- parameter1: 20, // the corner has a large radius
- //fill: $(go.Brush, "Linear", { 0: "rgb(254, 201, 0)", 1: "rgb(254, 162, 0)" }),
- stroke: null,
- // portId: "", // this Shape is the Node's port, not the whole Node
- // fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
- // toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true,
- // cursor: "pointer"
- }, new go.Binding("fill", "isInit", function (s) {
- return s ?
- $(go.Brush, "Radial", {
- 0: "rgb(151,255,151)",
- 0.8: "rgb(151,255,151)",
- 1: "rgba(151,255,151, 0)"
- }) :
- $(go.Brush, "Radial", {
- 0: "rgb(255,255,224)",
- 0.8: "rgb(255,255,224)",
- 1: "rgba(255,255,224, 0)"
- })
- })//.makeTwoWay()
- ),
- $(go.TextBlock,
- {
- font: "bold 20pt helvetica, bold arial, sans-serif",
- editable: true // editing the text automatically updates the model data
- },
- new go.Binding("text").makeTwoWay()),
- {
- portId: "", // this Shape is the Node's port, not the whole Node
- fromLinkable: true, fromLinkableSelfNode: true, fromLinkableDuplicates: true,
- toLinkable: true, toLinkableSelfNode: true, toLinkableDuplicates: true,
- cursor: "pointer"
- }
- );
- myDiagram.linkTemplate =
- $(go.Link, // the whole link panel
- {
- curve: go.Link.Bezier, adjusting: go.Link.Stretch,
- reshapable: true, relinkableFrom: true, relinkableTo: true,
- toShortLength: 3,
- },
- new go.Binding("points").makeTwoWay(),
- new go.Binding("curviness"),
- $(go.Shape, // the link shape
- {fill: 'white', stroke: 'white', strokeWidth: 1.5}),
- $(go.Shape, // the arrowhead
- {toArrow: "standard", fill: 'white', stroke: 'white', strokeWidth: 2}),
- $(go.Panel, "Auto",
- $(go.Shape, // the label background, which becomes transparent around the edges
- {
- fill: $(go.Brush, "Radial",
- {0: "rgb(240, 240, 240)", 0.3: "rgb(240, 240, 240)", 1: "rgba(240, 240, 240, 0)"}),
- stroke: null
- }),
- $(go.TextBlock, "event",// the label text
- {
- textAlign: "center",
- font: "20pt helvetica, arial, sans-serif",
- margin: 10,
- editable: true, // enable in-place editing
- },
- // editing the text automatically updates the model data
- new go.Binding("text").makeTwoWay())
- )
- );
- function classNameInput(flowCallback) {
- notie.removeAll();
- notie.input({
- text: "输入状态机文件名?",
- submitText: "确认", // optional, default = 'Submit'
- cancelText: "取消", // optional, default = 'Cancel'
- position: "top", // optional, default = 'top', enum: ['top', 'bottom']
- submitCallback: function (value) {
- flowCallback(value)
- }, // optional
- cancelCallback: function () {
- }, // optional
- autofocus: 'true', // default: 'true'
- placeholder: 'FsmImplClass', // default: ''
- type: 'text', // default: 'text'
- })
- }
- function safetySaveBeforeExport() {
- if (editKey) {
- let node = myDiagram.findNodeForKey(editKey);
- if (node) {
- myDiagram.model.startTransaction("change callbacks");
- myDiagram.model.setDataProperty(node.data, "callbacks", editor.getText());
- myDiagram.model.commitTransaction("change callbacks");
- }
- }
- if (selectLink) {
- let link = myDiagram.findLinkForData(linkData);
- if (link) {
- myDiagram.model.startTransaction("change callbacks");
- myDiagram.model.setDataProperty(link.data, "callbacks", editor.getText());
- myDiagram.model.commitTransaction("change callbacks");
- }
- }
- if (!editKey && !selectLink && !multiSelect) {
- globalCallbacksText = editor.getText();
- }
- }
- // 修复 bug 当重新加载gojs时恢复init key
- let getInitialKeyFromGoJs = function (flowForGoJs) {
- let nodeDataArray = flowForGoJs.nodeDataArray;
- for (let i = 0, l = nodeDataArray.length; i < l; i++) {
- if (nodeDataArray[i].isInit) {
- return nodeDataArray[i].key;
- }
- }
- }
- window.addEventListener('beforeunload', (event) => {
- let postData = {
- message: "panelClose",
- }
- window.parent.postMessage(postData, "*");
- });
- let handleFiles = function (files) {
- let l = 1;
- for (var i = 0; i < l; i++) {
- var file = files[i];
- var reader = new FileReader();
- reader.onload = function (e) {
- //console.log(reader.result);
- //let gojsResult = reader.result.match(/###(.*)###/g)[0];
- let gojsResult = /###(.*)###/.exec(reader.result)[1];
- let flowForGoJs = JSON.parse(gojsResult);
- //let flowForGoJs = JSON.parse(reader.result);
- globalCallbacksText = flowForGoJs.globalCallbacksText;
- editor.setText(globalCallbacksText);
- expandJson();
- if (flowForGoJs) {
- myDiagram.model = go.Model.fromJson(flowForGoJs);
- } else {
- notie.removeAll();
- notie.alert({type: "error", text: "状态机解析出错", stay: true});
- }
- };
- reader.readAsText(file);
- }
- }
- let helpText =
- [
- `双击 : 创建状态`,
- `alt + q : 切换创建模式与连线模式`,
- `alt + c : 设置初始化状态`,
- `alt + s : 保存状态机`,
- `alt + e : 导出状态机`,
- `alt + o : 导入状态机`,
- `聚焦资源管理fsm目录下状态机 : 快速导入状态机`,
- `属性编辑面板 : 添加状态或事件的方法回调句柄`,
- `状态机编辑面板快捷键 : 详情查看 <a href="http://gojs.net/latest/api/index.html">http://gojs.net/latest/api/index.html</a>`,
- `属性编辑面板快捷键 : 详情查看 <a href="https://github.com/josdejong/jsoneditor/blob/master/docs/shortcut_keys.md">https://github.com/josdejong/jsoneditor/blob/master/docs/shortcut_keys.md</a>`,
- `状态机代码使用方式: 详情查看 <a href="http://www.jianshu.com/p/f4f887426700">http://www.jianshu.com/p/f4f887426700</a>`
- ];
- let tipsI = 0;
- let tipsL = helpText.length;
- let generateTipsConfirm = function (idx) {
- notie.removeAll();
- notie.confirm({
- text: helpText[idx],
- submitText: "上一条", // optional, default = 'Yes'
- cancelText: "下一条", // optional, default = 'Cancel'
- position: "top", // optional, default = 'top', enum: ['top', 'bottom']
- submitCallback: function () {
- generateTipsConfirm((tipsI = (tipsI - 1 + tipsL) % tipsL))
- }, // optional
- cancelCallback: function () {
- generateTipsConfirm((tipsI = (tipsI + 1) % tipsL))
- }, // optional
- });
- }
- let getHelp = function () {
- generateTipsConfirm(tipsI % tipsL);
- }
- </script>
- </body>
- </html>
|