lanyunfei 2 月之前
父節點
當前提交
ad618251b8
共有 19 個文件被更改,包括 43152 次插入0 次删除
  1. 24 0
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 675 0
      index.html
  4. 1019 0
      package-lock.json
  5. 19 0
      package.json
  6. 893 0
      public/jsoneditor-icons.svg
  7. 1 0
      public/vite.svg
  8. 32 0
      src/App.vue
  9. 1 0
      src/assets/vue.svg
  10. 13 0
      src/components/Fsm.vue
  11. 43 0
      src/components/HelloWorld.vue
  12. 2051 0
      src/go.js
  13. 932 0
      src/jsoneditor.css
  14. 36377 0
      src/jsoneditor.js
  15. 5 0
      src/main.js
  16. 109 0
      src/notie.css
  17. 869 0
      src/notie.js
  18. 79 0
      src/style.css
  19. 7 0
      vite.config.js

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 675 - 0
index.html

@@ -0,0 +1,675 @@
+<!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>

文件差異過大導致無法顯示
+ 1019 - 0
package-lock.json


+ 19 - 0
package.json

@@ -0,0 +1,19 @@
+{
+  "name": "cocosfsm",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "vue": "^3.4.37",
+    "gojs": "^2.1.16"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.1.2",
+    "vite": "^5.4.1"
+  }
+}

文件差異過大導致無法顯示
+ 893 - 0
public/jsoneditor-icons.svg


文件差異過大導致無法顯示
+ 1 - 0
public/vite.svg


+ 32 - 0
src/App.vue

@@ -0,0 +1,32 @@
+<script setup>
+import HelloWorld from './components/HelloWorld.vue'
+import Fsm from "./components/Fsm.vue";
+</script>
+
+<template>
+<!--  <div>-->
+<!--    <a href="https://vitejs.dev" target="_blank">-->
+<!--      <img src="/vite.svg" class="logo" alt="Vite logo" />-->
+<!--    </a>-->
+<!--    <a href="https://vuejs.org/" target="_blank">-->
+<!--      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />-->
+<!--    </a>-->
+<!--  </div>-->
+<!--  <HelloWorld msg="Vite + Vue" />-->
+  <Fsm/>
+</template>
+
+<style scoped>
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.vue:hover {
+  filter: drop-shadow(0 0 2em #42b883aa);
+}
+</style>

+ 1 - 0
src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

+ 13 - 0
src/components/Fsm.vue

@@ -0,0 +1,13 @@
+<template>
+
+</template>
+
+<script>
+export default {
+  name: "Fsm"
+}
+</script>
+
+<style scoped>
+
+</style>

+ 43 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,43 @@
+<script setup>
+import { ref } from 'vue'
+
+defineProps({
+  msg: String,
+})
+
+const count = ref(0)
+</script>
+
+<template>
+  <h1>{{ msg }}</h1>
+
+  <div class="card">
+    <button type="button" @click="count++">count is {{ count }}</button>
+    <p>
+      Edit
+      <code>components/HelloWorld.vue</code> to test HMR
+    </p>
+  </div>
+
+  <p>
+    Check out
+    <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
+      >create-vue</a
+    >, the official Vue + Vite starter
+  </p>
+  <p>
+    Learn more about IDE Support for Vue in the
+    <a
+      href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
+      target="_blank"
+      >Vue Docs Scaling up Guide</a
+    >.
+  </p>
+  <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
+</template>
+
+<style scoped>
+.read-the-docs {
+  color: #888;
+}
+</style>

文件差異過大導致無法顯示
+ 2051 - 0
src/go.js


+ 932 - 0
src/jsoneditor.css

@@ -0,0 +1,932 @@
+/* reset styling (prevent conflicts with bootstrap, materialize.css, etc.) */
+
+div.jsoneditor .jsoneditor-search input {
+  height: auto;
+  border: inherit;
+}
+
+div.jsoneditor .jsoneditor-search input:focus {
+  border: none !important;
+  box-shadow: none !important;
+}
+
+div.jsoneditor table {
+  border-collapse: collapse;
+  width: auto;
+}
+
+div.jsoneditor td,
+div.jsoneditor th {
+  padding: 0;
+  display: table-cell;
+  text-align: left;
+  vertical-align: inherit;
+  border-radius: inherit;
+}
+
+
+div.jsoneditor-field,
+div.jsoneditor-value,
+div.jsoneditor-readonly {
+  border: 1px solid transparent;
+  min-height: 16px;
+  min-width: 32px;
+  padding: 2px;
+  margin: 1px;
+  word-wrap: break-word;
+  float: left;
+}
+
+/* adjust margin of p elements inside editable divs, needed for Opera, IE */
+
+div.jsoneditor-field p,
+div.jsoneditor-value p {
+  margin: 0;
+}
+
+div.jsoneditor-value {
+  word-break: break-word;
+}
+
+div.jsoneditor-readonly {
+  min-width: 16px;
+  color: #fff;
+}
+
+div.jsoneditor-empty {
+  border-color: lightgray;
+  border-style: dashed;
+  border-radius: 2px;
+}
+
+div.jsoneditor-field.jsoneditor-empty::after,
+div.jsoneditor-value.jsoneditor-empty::after {
+  pointer-events: none;
+  color: lightgray;
+  font-size: 8pt;
+}
+
+div.jsoneditor-field.jsoneditor-empty::after {
+  content: "field";
+}
+
+div.jsoneditor-value.jsoneditor-empty::after {
+  content: "value";
+}
+
+div.jsoneditor-value.jsoneditor-url,
+a.jsoneditor-value.jsoneditor-url {
+  color: green;
+  text-decoration: underline;
+}
+
+a.jsoneditor-value.jsoneditor-url {
+  display: inline-block;
+  padding: 2px;
+  margin: 2px;
+}
+
+a.jsoneditor-value.jsoneditor-url:hover,
+a.jsoneditor-value.jsoneditor-url:focus {
+  color: #ee422e;
+}
+
+div.jsoneditor td.jsoneditor-separator {
+  padding: 3px 0;
+  vertical-align: top;
+  color: gray;
+}
+
+div.jsoneditor-field[contenteditable=true]:focus,
+div.jsoneditor-field[contenteditable=true]:hover,
+div.jsoneditor-value[contenteditable=true]:focus,
+div.jsoneditor-value[contenteditable=true]:hover,
+div.jsoneditor-field.jsoneditor-highlight,
+div.jsoneditor-value.jsoneditor-highlight {
+  /*background-color: #FFFFAB;*/
+  border: 1px solid yellow;
+  border-radius: 2px;
+}
+
+div.jsoneditor-field.jsoneditor-highlight-active,
+div.jsoneditor-field.jsoneditor-highlight-active:focus,
+div.jsoneditor-field.jsoneditor-highlight-active:hover,
+div.jsoneditor-value.jsoneditor-highlight-active,
+div.jsoneditor-value.jsoneditor-highlight-active:focus,
+div.jsoneditor-value.jsoneditor-highlight-active:hover {
+  /*background-color: #ffee00;*/
+  border: 1px solid #ffc700;
+  border-radius: 2px;
+}
+
+div.jsoneditor-value.jsoneditor-string {
+  color: #fff;
+}
+
+div.jsoneditor-value.jsoneditor-object,
+div.jsoneditor-value.jsoneditor-array {
+  min-width: 16px;
+  color: #808080;
+}
+
+div.jsoneditor-value.jsoneditor-number {
+  color: #ee422e;
+}
+
+div.jsoneditor-value.jsoneditor-boolean {
+  color: #ff8c00;
+}
+
+div.jsoneditor-value.jsoneditor-null {
+  color: #004ED0;
+}
+
+div.jsoneditor-value.jsoneditor-invalid {
+  color: #000000;
+}
+
+div.jsoneditor-tree button {
+  width: 24px;
+  height: 24px;
+  padding: 0;
+  margin: 0;
+  border: none;
+  cursor: pointer;
+  background: transparent url("/jsoneditor-icons.svg");
+}
+
+div.jsoneditor-mode-view tr.jsoneditor-expandable td.jsoneditor-tree,
+div.jsoneditor-mode-form tr.jsoneditor-expandable td.jsoneditor-tree {
+  cursor: pointer;
+}
+
+div.jsoneditor-tree button.jsoneditor-collapsed {
+  background-position: 0 -48px;
+}
+
+div.jsoneditor-tree button.jsoneditor-expanded {
+  background-position: 0 -72px;
+}
+
+div.jsoneditor-tree button.jsoneditor-contextmenu {
+  background-position: -48px -72px;
+}
+
+div.jsoneditor-tree button.jsoneditor-contextmenu:hover,
+div.jsoneditor-tree button.jsoneditor-contextmenu:focus,
+div.jsoneditor-tree button.jsoneditor-contextmenu.jsoneditor-selected,
+tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu {
+  background-position: -48px -48px;
+}
+
+div.jsoneditor-tree *:focus {
+  outline: none;
+}
+
+div.jsoneditor-tree button:focus {
+  /* TODO: nice outline for buttons with focus
+  outline: #97B0F8 solid 2px;
+  box-shadow: 0 0 8px #97B0F8;
+  */
+  background-color: #f5f5f5;
+  outline: #e5e5e5 solid 1px;
+}
+
+div.jsoneditor-tree button.jsoneditor-invisible {
+  visibility: hidden;
+  background: none;
+}
+
+div.jsoneditor {
+  color: #1A1A1A;
+  border: 1px solid #3883fa;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  position: relative;
+  padding: 0;
+  line-height: 100%;
+}
+
+div.jsoneditor-tree table.jsoneditor-tree {
+  border-collapse: collapse;
+  border-spacing: 0;
+  width: 100%;
+  margin: 0;
+}
+
+div.jsoneditor-outer {
+  position: static;
+  width: 100%;
+  height: 100%;
+  margin: -35px 0 0 0;
+  padding: 35px 0 0 0;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+}
+
+textarea.jsoneditor-text,
+.ace-jsoneditor {
+  min-height: 150px;
+}
+
+div.jsoneditor-tree {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  overflow: auto;
+}
+
+textarea.jsoneditor-text {
+  width: 100%;
+  height: 100%;
+  margin: 0;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  outline-width: 0;
+  border: none;
+  background-color: white;
+  resize: none;
+}
+
+tr.jsoneditor-highlight {
+  background-color: #444;
+}
+tr.jsoneditor-selected {
+  background-color: #e6e6e6;
+}
+
+tr.jsoneditor-selected button.jsoneditor-dragarea,
+tr.jsoneditor-selected button.jsoneditor-contextmenu {
+  visibility: hidden;
+}
+
+tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea,
+tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu {
+  visibility: visible;
+}
+
+div.jsoneditor-tree button.jsoneditor-dragarea {
+  background: url("/jsoneditor-icons.svg") -72px -72px;
+  cursor: move;
+}
+
+div.jsoneditor-tree button.jsoneditor-dragarea:hover,
+div.jsoneditor-tree button.jsoneditor-dragarea:focus,
+tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea {
+  background-position: -72px -48px;
+}
+
+div.jsoneditor tr,
+div.jsoneditor th,
+div.jsoneditor td {
+  padding: 0;
+  margin: 0;
+}
+
+div.jsoneditor td {
+  vertical-align: top;
+}
+
+div.jsoneditor td.jsoneditor-tree {
+  vertical-align: top;
+}
+
+div.jsoneditor-field,
+div.jsoneditor-value,
+div.jsoneditor td,
+div.jsoneditor th,
+div.jsoneditor textarea,
+.jsoneditor-schema-error {
+  font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
+  font-size: 10pt;
+  color: #fff;
+}
+
+/* popover */
+
+.jsoneditor-schema-error {
+  cursor: default;
+  display: inline-block;
+  /*font-family: arial, sans-serif;*/
+  height: 24px;
+  line-height: 24px;
+  position: relative;
+  text-align: center;
+  width: 24px;
+}
+
+div.jsoneditor-tree .jsoneditor-schema-error {
+  width: 24px;
+  height: 24px;
+  padding: 0;
+  margin: 0 4px 0 0;
+  background: url("/jsoneditor-icons.svg")  -168px -48px;
+}
+
+.jsoneditor-schema-error .jsoneditor-popover {
+  background-color: #4c4c4c;
+  border-radius: 3px;
+  box-shadow: 0 0 5px rgba(0,0,0,0.4);
+  color: #fff;
+  display: none;
+  padding: 7px 10px;
+  position: absolute;
+  width: 200px;
+  z-index: 4;
+}
+
+.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above {
+  bottom: 32px;
+  left: -98px;
+}
+
+.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below {
+  top: 32px;
+  left: -98px;
+}
+
+.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left {
+  top: -7px;
+  right: 32px;
+}
+
+.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right {
+  top: -7px;
+  left: 32px;
+}
+
+.jsoneditor-schema-error .jsoneditor-popover:before {
+  border-right: 7px solid transparent;
+  border-left: 7px solid transparent;
+  content: '';
+  display: block;
+  left: 50%;
+  margin-left: -7px;
+  position: absolute;
+}
+
+.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above:before {
+  border-top: 7px solid #4c4c4c;
+  bottom: -7px;
+}
+
+.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below:before {
+  border-bottom: 7px solid #4c4c4c;
+  top: -7px;
+}
+
+.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left:before {
+  border-left: 7px solid #4c4c4c;
+  border-top: 7px solid transparent;
+  border-bottom: 7px solid transparent;
+  content: '';
+  top: 19px;
+  right: -14px;
+  left: inherit;
+  margin-left: inherit;
+  margin-top: -7px;
+  position: absolute;
+}
+
+.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right:before {
+  border-right: 7px solid #4c4c4c;
+  border-top: 7px solid transparent;
+  border-bottom: 7px solid transparent;
+  content: '';
+  top: 19px;
+  left: -14px;
+  margin-left: inherit;
+  margin-top: -7px;
+  position: absolute;
+}
+
+.jsoneditor-schema-error:hover .jsoneditor-popover,
+.jsoneditor-schema-error:focus .jsoneditor-popover {
+  display: block;
+  -webkit-animation: fade-in .3s linear 1, move-up .3s linear 1;
+  -moz-animation: fade-in .3s linear 1, move-up .3s linear 1;
+  -ms-animation: fade-in .3s linear 1, move-up .3s linear 1;
+}
+
+@-webkit-keyframes fade-in {
+  from {
+    opacity: 0;
+  }
+
+  to {
+    opacity: 1;
+  }
+}
+
+@-moz-keyframes fade-in {
+  from {
+    opacity: 0;
+  }
+
+  to {
+    opacity: 1;
+  }
+}
+
+@-ms-keyframes fade-in {
+  from {
+    opacity: 0;
+  }
+
+  to {
+    opacity: 1;
+  }
+}
+
+/*@-webkit-keyframes move-up {*/
+
+/*from   { bottom: 24px; }*/
+
+/*to { bottom: 32px; }*/
+
+/*}*/
+
+/*@-moz-keyframes move-up {*/
+
+/*from   { bottom: 24px; }*/
+
+/*to { bottom: 32px; }*/
+
+/*}*/
+
+/*@-ms-keyframes move-up {*/
+
+/*from   { bottom: 24px; }*/
+
+/*to { bottom: 32px; }*/
+
+/*}*/
+
+/* JSON schema errors displayed at the bottom of the editor in mode text and code */
+
+.jsoneditor .jsoneditor-text-errors {
+  width: 100%;
+  border-collapse: collapse;
+  background-color: #ffef8b;
+  border-top: 1px solid #ffd700;
+}
+
+.jsoneditor .jsoneditor-text-errors td {
+  padding: 3px 6px;
+  vertical-align: middle;
+}
+
+.jsoneditor-text-errors .jsoneditor-schema-error {
+  border: none;
+  width: 24px;
+  height: 24px;
+  padding: 0;
+  margin: 0 4px 0 0;
+  background: url("/jsoneditor-icons.svg")  -168px -48px;
+}
+/* ContextMenu - main menu */
+
+div.jsoneditor-contextmenu-root {
+  position: relative;
+  width: 0;
+  height: 0;
+}
+
+div.jsoneditor-contextmenu {
+  position: absolute;
+  box-sizing: content-box;
+  z-index: 99999;
+}
+
+div.jsoneditor-contextmenu ul,
+div.jsoneditor-contextmenu li {
+  box-sizing: content-box;
+}
+
+div.jsoneditor-contextmenu ul {
+  position: relative;
+  left: 0;
+  top: 0;
+  width: 124px;
+  background: white;
+  border: 1px solid #d3d3d3;
+  box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3);
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}
+
+div.jsoneditor-contextmenu ul li button {
+  padding: 0;
+  margin: 0;
+  width: 124px;
+  height: 24px;
+  border: none;
+  cursor: pointer;
+  color: #4d4d4d;
+  background: transparent;
+  font-size: 10pt;
+  font-family: arial, sans-serif;
+  box-sizing: border-box;
+  line-height: 26px;
+  text-align: left;
+}
+
+/* Fix button padding in firefox */
+
+div.jsoneditor-contextmenu ul li button::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+
+div.jsoneditor-contextmenu ul li button:hover,
+div.jsoneditor-contextmenu ul li button:focus {
+  color: #1a1a1a;
+  background-color: #f5f5f5;
+  outline: none;
+}
+
+div.jsoneditor-contextmenu ul li button.jsoneditor-default {
+  width: 92px;
+}
+
+div.jsoneditor-contextmenu ul li button.jsoneditor-expand {
+  float: right;
+  width: 32px;
+  height: 24px;
+  border-left: 1px solid #e5e5e5;
+}
+
+div.jsoneditor-contextmenu div.jsoneditor-icon {
+  float: left;
+  width: 24px;
+  height: 24px;
+  border: none;
+  padding: 0;
+  margin: 0;
+  background-image: url("/jsoneditor-icons.svg");
+}
+
+div.jsoneditor-contextmenu ul li button div.jsoneditor-expand {
+  float: right;
+  width: 24px;
+  height: 24px;
+  padding: 0;
+  margin: 0 4px 0 0;
+  background: url("/jsoneditor-icons.svg") 0 -72px;
+  opacity: 0.4;
+}
+
+div.jsoneditor-contextmenu ul li button:hover div.jsoneditor-expand,
+div.jsoneditor-contextmenu ul li button:focus div.jsoneditor-expand,
+div.jsoneditor-contextmenu ul li.jsoneditor-selected div.jsoneditor-expand,
+div.jsoneditor-contextmenu ul li button.jsoneditor-expand:hover div.jsoneditor-expand,
+div.jsoneditor-contextmenu ul li button.jsoneditor-expand:focus div.jsoneditor-expand {
+  opacity: 1;
+}
+
+div.jsoneditor-contextmenu div.jsoneditor-separator {
+  height: 0;
+  border-top: 1px solid #e5e5e5;
+  padding-top: 5px;
+  margin-top: 5px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-remove > div.jsoneditor-icon {
+  background-position: -24px -24px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-remove:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-remove:focus > div.jsoneditor-icon {
+  background-position: -24px 0;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-append > div.jsoneditor-icon {
+  background-position: 0 -24px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-append:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-append:focus > div.jsoneditor-icon {
+  background-position: 0 0;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-insert > div.jsoneditor-icon {
+  background-position: 0 -24px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-insert:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-insert:focus > div.jsoneditor-icon {
+  background-position: 0 0;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-duplicate > div.jsoneditor-icon {
+  background-position: -48px -24px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-duplicate:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-duplicate:focus > div.jsoneditor-icon {
+  background-position: -48px 0;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-sort-asc > div.jsoneditor-icon {
+  background-position: -168px -24px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-sort-asc:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-sort-asc:focus > div.jsoneditor-icon {
+  background-position: -168px 0;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-sort-desc > div.jsoneditor-icon {
+  background-position: -192px -24px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-sort-desc:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-sort-desc:focus > div.jsoneditor-icon {
+  background-position: -192px 0;
+}
+
+/* ContextMenu - sub menu */
+
+div.jsoneditor-contextmenu ul li button.jsoneditor-selected,
+div.jsoneditor-contextmenu ul li button.jsoneditor-selected:hover,
+div.jsoneditor-contextmenu ul li button.jsoneditor-selected:focus {
+  color: white;
+  background-color: #ee422e;
+}
+
+div.jsoneditor-contextmenu ul li {
+  overflow: hidden;
+}
+
+div.jsoneditor-contextmenu ul li ul {
+  display: none;
+  position: relative;
+  left: -10px;
+  top: 0;
+  border: none;
+  box-shadow: inset 0 0 10px rgba(128, 128, 128, 0.5);
+  padding: 0 10px;
+  /* TODO: transition is not supported on IE8-9 */
+  -webkit-transition: all 0.3s ease-out;
+  -moz-transition: all 0.3s ease-out;
+  -o-transition: all 0.3s ease-out;
+  transition: all 0.3s ease-out;
+}
+
+
+
+div.jsoneditor-contextmenu ul li ul li button {
+  padding-left: 24px;
+  animation: all ease-in-out 1s;
+}
+
+div.jsoneditor-contextmenu ul li ul li button:hover,
+div.jsoneditor-contextmenu ul li ul li button:focus {
+  background-color: #f5f5f5;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-type-string > div.jsoneditor-icon {
+  background-position: -144px -24px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-type-string:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-type-string:focus > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-type-string.jsoneditor-selected > div.jsoneditor-icon {
+  background-position: -144px 0;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-type-auto > div.jsoneditor-icon {
+  background-position: -120px -24px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-type-auto:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-type-auto:focus > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-type-auto.jsoneditor-selected > div.jsoneditor-icon {
+  background-position: -120px 0;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-type-object > div.jsoneditor-icon {
+  background-position: -72px -24px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-type-object:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-type-object:focus > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-type-object.jsoneditor-selected > div.jsoneditor-icon {
+  background-position: -72px 0;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-type-array > div.jsoneditor-icon {
+  background-position: -96px -24px;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-type-array:hover > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-type-array:focus > div.jsoneditor-icon,
+div.jsoneditor-contextmenu button.jsoneditor-type-array.jsoneditor-selected > div.jsoneditor-icon {
+  background-position: -96px 0;
+}
+
+div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon {
+  background-image: none;
+  width: 6px;
+}
+div.jsoneditor-menu {
+  width: 100%;
+  height: 35px;
+  padding: 2px;
+  margin: 0;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  color: white;
+  background-color: #CCCCCC;
+  border-bottom: 1px solid #CCCCCC;
+}
+
+div.jsoneditor-menu > button,
+div.jsoneditor-menu > div.jsoneditor-modes > button {
+  width: 26px;
+  height: 26px;
+  margin: 2px;
+  padding: 0;
+  border-radius: 2px;
+  border: 1px solid transparent;
+  background: transparent url("/jsoneditor-icons.svg");
+  color: white;
+  opacity: 0.8;
+  font-family: arial, sans-serif;
+  font-size: 10pt;
+  float: left;
+}
+
+div.jsoneditor-menu > button:hover,
+div.jsoneditor-menu > div.jsoneditor-modes > button:hover {
+  background-color: rgba(255,255,255,0.2);
+  border: 1px solid rgba(255,255,255,0.4);
+}
+
+div.jsoneditor-menu > button:focus,
+div.jsoneditor-menu > button:active,
+div.jsoneditor-menu > div.jsoneditor-modes > button:focus,
+div.jsoneditor-menu > div.jsoneditor-modes > button:active {
+  background-color: rgba(255,255,255,0.3);
+}
+
+div.jsoneditor-menu > button:disabled,
+div.jsoneditor-menu > div.jsoneditor-modes > button:disabled {
+  opacity: 0.5;
+}
+
+div.jsoneditor-menu > button.jsoneditor-collapse-all {
+  background-position: 0 -96px;
+}
+
+div.jsoneditor-menu > button.jsoneditor-expand-all {
+  background-position: 0 -120px;
+}
+
+div.jsoneditor-menu > button.jsoneditor-undo {
+  background-position: -24px -96px;
+}
+
+div.jsoneditor-menu > button.jsoneditor-undo:disabled {
+  background-position: -24px -120px;
+}
+
+div.jsoneditor-menu > button.jsoneditor-redo {
+  background-position: -48px -96px;
+}
+
+div.jsoneditor-menu > button.jsoneditor-redo:disabled {
+  background-position: -48px -120px;
+}
+
+div.jsoneditor-menu > button.jsoneditor-compact {
+  background-position: -72px -96px;
+}
+
+div.jsoneditor-menu > button.jsoneditor-format {
+  background-position: -72px -120px;
+}
+
+div.jsoneditor-menu > div.jsoneditor-modes {
+  display: inline-block;
+  float: left;
+}
+
+div.jsoneditor-menu > div.jsoneditor-modes > button {
+  background-image: none;
+  width: auto;
+  padding-left: 6px;
+  padding-right: 6px;
+}
+
+div.jsoneditor-menu > button.jsoneditor-separator,
+div.jsoneditor-menu > div.jsoneditor-modes > button.jsoneditor-separator {
+  margin-left: 10px;
+}
+
+div.jsoneditor-menu a {
+  font-family: arial, sans-serif;
+  font-size: 10pt;
+  color: white;
+  opacity: 0.8;
+  vertical-align: middle;
+}
+
+div.jsoneditor-menu a:hover {
+  opacity: 1;
+}
+
+div.jsoneditor-menu a.jsoneditor-poweredBy {
+  font-size: 8pt;
+  position: absolute;
+  right: 0;
+  top: 0;
+  padding: 10px;
+}
+table.jsoneditor-search input,
+table.jsoneditor-search div.jsoneditor-results {
+  font-family: arial, sans-serif;
+  font-size: 10pt;
+  color: #1A1A1A;
+  background: transparent;
+  /* For Firefox */
+}
+
+table.jsoneditor-search div.jsoneditor-results {
+  color: white;
+  padding-right: 5px;
+  line-height: 24px;
+}
+
+table.jsoneditor-search {
+  position: absolute;
+  right: 4px;
+  top: 4px;
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+table.jsoneditor-search div.jsoneditor-frame {
+  border: 1px solid transparent;
+  background-color: white;
+  padding: 0 2px;
+  margin: 0;
+}
+
+table.jsoneditor-search div.jsoneditor-frame table {
+  border-collapse: collapse;
+}
+
+table.jsoneditor-search input {
+  width: 120px;
+  border: none;
+  outline: none;
+  margin: 1px;
+  line-height: 20px;
+}
+
+table.jsoneditor-search button {
+  width: 16px;
+  height: 24px;
+  padding: 0;
+  margin: 0;
+  border: none;
+  background: url("/jsoneditor-icons.svg");
+  vertical-align: top;
+}
+
+table.jsoneditor-search button:hover {
+  background-color: transparent;
+}
+
+table.jsoneditor-search button.jsoneditor-refresh {
+  width: 18px;
+  background-position: -99px -73px;
+}
+
+table.jsoneditor-search button.jsoneditor-next {
+  cursor: pointer;
+  background-position: -124px -73px;
+}
+
+table.jsoneditor-search button.jsoneditor-next:hover {
+  background-position: -124px -49px;
+}
+
+table.jsoneditor-search button.jsoneditor-previous {
+  cursor: pointer;
+  background-position: -148px -73px;
+  margin-right: 2px;
+}
+
+table.jsoneditor-search button.jsoneditor-previous:hover {
+  background-position: -148px -49px;
+}

文件差異過大導致無法顯示
+ 36377 - 0
src/jsoneditor.js


+ 5 - 0
src/main.js

@@ -0,0 +1,5 @@
+import { createApp } from 'vue'
+import './style.css'
+import App from './App.vue'
+
+createApp(App).mount('#app')

+ 109 - 0
src/notie.css

@@ -0,0 +1,109 @@
+.notie-container {
+  font-size: 1.6rem;
+  height: auto;
+  left: 0;
+  position: fixed;
+  text-align: center;
+  width: 100%;
+  z-index: 2147483647;
+  box-sizing: border-box;
+  -o-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.5);
+  -ms-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.5);
+  -moz-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.5);
+  -webkit-box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.5);
+  box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.5); }
+  @media screen and (max-width: 900px) {
+    .notie-container {
+      font-size: 1.4rem; } }
+  @media screen and (max-width: 750px) {
+    .notie-container {
+      font-size: 1.2rem; } }
+  @media screen and (max-width: 400px) {
+    .notie-container {
+      font-size: 1rem; } }
+
+.notie-background-success {
+  background-color: #444; }
+
+.notie-background-warning {
+  background-color: #444; }
+
+.notie-background-error {
+  background-color: #444; }
+
+.notie-background-info {
+  background-color: #444; }
+
+.notie-background-neutral {
+  background-color: #444; }
+
+.notie-background-overlay {
+  background-color: #444; }
+
+.notie-textbox {
+  color: #FFFFFF;
+  padding: 20px; }
+
+.notie-textbox-inner {
+  margin: 0 auto;
+  max-width: 900px; }
+
+.notie-overlay {
+  height: 100%;
+  left: 0;
+  opacity: 0;
+  position: fixed;
+  top: 0;
+  width: 100%;
+  z-index: 2147483646; }
+
+.notie-button {
+  color: #FFFFFF;
+  padding: 10px;
+  cursor: pointer; }
+
+.notie-element {
+  color: #FFFFFF;
+  padding: 10px; }
+
+.notie-element-half {
+  display: inline-block;
+  width: 50%;
+  box-sizing: border-box; }
+
+.notie-element-third {
+  display: inline-block;
+  width: 33.3333%;
+  box-sizing: border-box; }
+
+.notie-alert {
+  cursor: pointer; }
+
+.notie-input-field {
+  background-color: #FFFFFF;
+  border: 0;
+  font-family: inherit;
+  font-size: inherit;
+  outline: 0;
+  padding: 10px;
+  text-align: center;
+  width: 100%;
+  box-sizing: border-box; }
+
+.notie-select-choice-repeated {
+  border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+  box-sizing: border-box; }
+
+.notie-date-selector-inner {
+  margin: 0 auto;
+  max-width: 900px;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  -o-user-select: none;
+  user-select: none; }
+  .notie-date-selector-inner [contenteditable], .notie-date-selector-inner [contenteditable]:focus {
+    outline: 0px solid transparent; }
+
+.notie-date-selector-up {
+  transform: rotate(180deg); }

+ 869 - 0
src/notie.js

@@ -0,0 +1,869 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["notie"] = factory();
+	else
+		root["notie"] = factory();
+})(this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId])
+/******/ 			return installedModules[moduleId].exports;
+
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+
+
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+
+/******/ 	// identity function for calling harmony imports with the correct context
+/******/ 	__webpack_require__.i = function(value) { return value; };
+
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+
+// ====================
+// options
+// ====================
+
+let currentId = null;
+let currentPosition = null;
+
+var positions = {
+  top: 'top',
+  bottom: 'bottom'
+};
+
+var options = {
+  alertTime: 3,
+  dateMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+  overlayClickDismiss: true,
+  overlayOpacity: 0.75,
+  transitionCurve: 'ease',
+  transitionDuration: 0.3,
+  transitionSelector: 'all',
+  classes: {
+    container: 'notie-container',
+    textbox: 'notie-textbox',
+    textboxInner: 'notie-textbox-inner',
+    button: 'notie-button',
+    element: 'notie-element',
+    elementHalf: 'notie-element-half',
+    elementThird: 'notie-element-third',
+    overlay: 'notie-overlay',
+    backgroundSuccess: 'notie-background-success',
+    backgroundWarning: 'notie-background-warning',
+    backgroundError: 'notie-background-error',
+    backgroundInfo: 'notie-background-info',
+    backgroundNeutral: 'notie-background-neutral',
+    backgroundOverlay: 'notie-background-overlay',
+    alert: 'notie-alert',
+    inputField: 'notie-input-field',
+    selectChoiceRepeated: 'notie-select-choice-repeated',
+    dateSelectorInner: 'notie-date-selector-inner',
+    dateSelectorUp: 'notie-date-selector-up'
+  },
+  ids: {
+    overlay: 'notie-overlay'
+  },
+  positions: {
+    alert: positions.top,
+    force: positions.top,
+    confirm: positions.top,
+    input: positions.top,
+    select: positions.bottom,
+    date: positions.top
+  }
+};
+
+var setOptions = exports.setOptions = function setOptions(newOptions) {
+  options = _extends({}, options, newOptions, {
+    classes: _extends({}, options.classes, newOptions.classes),
+    ids: _extends({}, options.ids, newOptions.ids),
+    positions: _extends({}, options.positions, newOptions.positions)
+  });
+};
+
+// ====================
+// helpers
+// ====================
+
+var tick = function tick() {
+  return new Promise(function (resolve) {
+    return setTimeout(resolve, 0);
+  });
+};
+var wait = function wait(time) {
+  return new Promise(function (resolve) {
+    return setTimeout(resolve, time * 1000);
+  });
+};
+
+var blur = function blur() {
+  document.activeElement && document.activeElement.blur();
+};
+
+var generateRandomId = function generateRandomId() {
+  // RFC4122 version 4 compliant UUID
+  var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+    var r = Math.random() * 16 | 0;
+    var v = c === 'x' ? r : r & 0x3 | 0x8;
+    return v.toString(16);
+  });
+  return 'notie-' + id;
+};
+
+var typeToClassLookup = {
+  1: options.classes.backgroundSuccess,
+  success: options.classes.backgroundSuccess,
+  2: options.classes.backgroundWarning,
+  warning: options.classes.backgroundWarning,
+  3: options.classes.backgroundError,
+  error: options.classes.backgroundError,
+  4: options.classes.backgroundInfo,
+  info: options.classes.backgroundInfo,
+  5: options.classes.backgroundNeutral,
+  neutral: options.classes.backgroundNeutral
+};
+
+var getTransition = function getTransition() {
+  return options.transitionSelector + ' ' + options.transitionDuration + 's ' + options.transitionCurve;
+};
+
+var enterClicked = function enterClicked(event) {
+  return event.keyCode === 13;
+};
+var escapeClicked = function escapeClicked(event) {
+  return event.keyCode === 27;
+};
+
+var addToDocument = function addToDocument(element, position) {
+  element.classList.add(options.classes.container);
+  element.style[position] = '-10000px';
+  document.body.appendChild(element);
+  element.style[position] = '-' + element.offsetHeight + 'px';
+
+  if (element.listener) window.addEventListener('keydown', element.listener);
+
+  tick().then(function () {
+    element.style.transition = getTransition();
+    element.style[position] = 0;
+  });
+};
+
+var removeFromDocument = function removeFromDocument(id, position) {
+  var element = document.getElementById(id);
+  if (!element) return;
+  element.style[position] = '-' + element.offsetHeight + 'px';
+
+  if (element.listener) window.removeEventListener('keydown', element.listener);
+
+  wait(options.transitionDuration).then(function () {
+    if (element.parentNode) element.parentNode.removeChild(element);
+  });
+};
+
+var addOverlayToDocument = function addOverlayToDocument(owner, position) {
+  var element = document.createElement('div');
+  element.id = options.ids.overlay;
+  element.classList.add(options.classes.overlay);
+  element.classList.add(options.classes.backgroundOverlay);
+  element.style.opacity = 0;
+  if (owner && options.overlayClickDismiss) {
+    element.onclick = function () {
+      removeFromDocument(owner.id, position);
+      removeOverlayFromDocument();
+    };
+  }
+
+  document.body.appendChild(element);
+
+  tick().then(function () {
+    element.style.transition = getTransition();
+    element.style.opacity = options.overlayOpacity;
+  });
+};
+
+var removeOverlayFromDocument = function removeOverlayFromDocument() {
+  var element = document.getElementById(options.ids.overlay);
+  if(!element){return;}
+  element.style.opacity = 0;
+  wait(options.transitionDuration).then(function () {
+    if (element.parentNode) element.parentNode.removeChild(element);
+  });
+};
+
+var hideAlerts = exports.hideAlerts = function hideAlerts(callback) {
+  var alertsShowing = document.getElementsByClassName(options.classes.alert);
+  if (alertsShowing.length) {
+    for (var i = 0; i < alertsShowing.length; i++) {
+      var _alert = alertsShowing[i];
+      removeFromDocument(_alert.id, _alert.position);
+    }
+    if (callback) wait(options.transitionDuration).then(function () {
+      return callback();
+    });
+  }
+};
+
+var removeAll = exports.removeAll = function removeAll(){
+  if(currentId && currentPosition){
+      removeFromDocument(currentId,currentPosition);
+      removeOverlayFromDocument();
+      currentId = null;
+      currentPosition = null;
+  }
+}
+
+// ====================
+// exports
+// ====================
+
+var alert = exports.alert = function alert(_ref) {
+  var _ref$type = _ref.type,
+      type = _ref$type === undefined ? 4 : _ref$type,
+      text = _ref.text,
+      _ref$time = _ref.time,
+      time = _ref$time === undefined ? options.alertTime : _ref$time,
+      _ref$stay = _ref.stay,
+      stay = _ref$stay === undefined ? false : _ref$stay,
+      _ref$position = _ref.position,
+      position = _ref$position === undefined ? options.positions.alert || position.top : _ref$position;
+
+  blur();
+  hideAlerts();
+
+  var element = document.createElement('div');
+  var id = generateRandomId();
+  element.id = id;
+  element.position = position;
+  
+  currentId = id;
+  currentPosition = position;
+
+  element.classList.add(options.classes.textbox);
+  element.classList.add(typeToClassLookup[type]);
+  element.classList.add(options.classes.alert);
+  element.innerHTML = '<div class="' + options.classes.textboxInner + '">' + text + '</div>';
+  element.onclick = function () {
+    return removeFromDocument(id, position);
+  };
+
+  element.listener = function (event) {
+    if (enterClicked(event) || escapeClicked(event)) hideAlerts();
+  };
+
+  addToDocument(element, position);
+
+  if (time && time < 1) time = 1;
+  if (!stay && time) wait(time).then(function () {
+    return removeFromDocument(id, position);
+  });
+};
+
+var force = exports.force = function force(_ref2, callbackArg) {
+  var _ref2$type = _ref2.type,
+      type = _ref2$type === undefined ? 5 : _ref2$type,
+      text = _ref2.text,
+      _ref2$buttonText = _ref2.buttonText,
+      buttonText = _ref2$buttonText === undefined ? 'OK' : _ref2$buttonText,
+      callback = _ref2.callback,
+      _ref2$position = _ref2.position,
+      position = _ref2$position === undefined ? options.positions.force || position.top : _ref2$position;
+
+  blur();
+  hideAlerts();
+
+  var element = document.createElement('div');
+  var id = generateRandomId();
+  element.id = id;
+
+  var elementText = document.createElement('div');
+  elementText.classList.add(options.classes.textbox);
+  elementText.classList.add(options.classes.backgroundInfo);
+  elementText.innerHTML = '<div class="' + options.classes.textboxInner + '">' + text + '</div>';
+
+  var elementButton = document.createElement('div');
+  elementButton.classList.add(options.classes.button);
+  elementButton.classList.add(typeToClassLookup[type]);
+  elementButton.innerHTML = buttonText;
+  elementButton.onclick = function () {
+    removeFromDocument(id, position);
+    removeOverlayFromDocument();
+    if (callback) callback();else if (callbackArg) callbackArg();
+  };
+
+  element.appendChild(elementText);
+  element.appendChild(elementButton);
+
+  element.listener = function (event) {
+    if (enterClicked(event)) elementButton.click();
+  };
+
+  addToDocument(element, position);
+
+  addOverlayToDocument();
+};
+
+//var oldConfirmId = null;
+//var oldConfirmPosition = null;
+
+var confirm = exports.confirm = function confirm(_ref3, submitCallbackArg, cancelCallbackArg) {
+  // if(oldConfirmId && oldConfirmPosition){
+  //   removeFromDocument(oldConfirmId, oldConfirmPosition);
+  //   removeOverlayFromDocument();
+  //   oldConfirmId = null;
+  //   oldConfirmPosition = null;
+  // }
+  var text = _ref3.text,
+      _ref3$submitText = _ref3.submitText,
+      submitText = _ref3$submitText === undefined ? 'Yes' : _ref3$submitText,
+      _ref3$cancelText = _ref3.cancelText,
+      cancelText = _ref3$cancelText === undefined ? 'Cancel' : _ref3$cancelText,
+      submitCallback = _ref3.submitCallback,
+      cancelCallback = _ref3.cancelCallback,
+      _ref3$position = _ref3.position,
+      position = _ref3$position === undefined ? options.positions.confirm || position.top : _ref3$position;
+
+  blur();
+  hideAlerts();
+
+  var element = document.createElement('div');
+  var id = generateRandomId();
+  element.id = id;
+
+  currentId = id;
+  currentPosition = position;
+
+  var elementText = document.createElement('div');
+  elementText.classList.add(options.classes.textbox);
+  elementText.classList.add(options.classes.backgroundInfo);
+  elementText.innerHTML = '<div class="' + options.classes.textboxInner + '">' + text + '</div>';
+
+  var elementButtonLeft = document.createElement('div');
+  elementButtonLeft.classList.add(options.classes.button);
+  elementButtonLeft.classList.add(options.classes.elementHalf);
+  elementButtonLeft.classList.add(options.classes.backgroundSuccess);
+  elementButtonLeft.innerHTML = submitText;
+
+  //oldConfirmId = id;
+  //oldConfirmPosition = position;
+
+  elementButtonLeft.onclick = function () {
+    removeFromDocument(id, position);
+    removeOverlayFromDocument();
+    //oldConfirmId = null;
+    //oldConfirmPosition = null;
+    if (submitCallback) submitCallback();else if (submitCallbackArg) submitCallbackArg();
+  };
+
+  var elementButtonRight = document.createElement('div');
+  elementButtonRight.classList.add(options.classes.button);
+  elementButtonRight.classList.add(options.classes.elementHalf);
+  elementButtonRight.classList.add(options.classes.backgroundError);
+  elementButtonRight.innerHTML = cancelText;
+  elementButtonRight.onclick = function () {
+    removeFromDocument(id, position);
+    removeOverlayFromDocument();
+    //oldConfirmId = null;
+    //oldConfirmPosition = null;
+    if (cancelCallback) cancelCallback();else if (cancelCallbackArg) cancelCallbackArg();
+  };
+
+  element.appendChild(elementText);
+  element.appendChild(elementButtonLeft);
+  element.appendChild(elementButtonRight);
+
+  element.listener = function (event) {
+    if (enterClicked(event)) elementButtonLeft.click();else if (escapeClicked(event)) elementButtonRight.click();
+  };
+
+  addToDocument(element, position);
+
+  addOverlayToDocument(element, position);
+};
+
+//let oldInputId = null;
+//let oldInputPosition = null;
+
+var input = function input(_ref4, submitCallbackArg, cancelCallbackArg) {
+  // if(oldInputId && oldInputPosition){
+  //   removeFromDocument(oldInputId, oldInputPosition);
+  //   removeOverlayFromDocument();
+  //   //oldInputId = null;
+  //   //oldInputPosition = null;
+  // }
+  var text = _ref4.text,
+      _ref4$submitText = _ref4.submitText,
+      submitText = _ref4$submitText === undefined ? 'Submit' : _ref4$submitText,
+      _ref4$cancelText = _ref4.cancelText,
+      cancelText = _ref4$cancelText === undefined ? 'Cancel' : _ref4$cancelText,
+      submitCallback = _ref4.submitCallback,
+      cancelCallback = _ref4.cancelCallback,
+      _ref4$position = _ref4.position,
+      position = _ref4$position === undefined ? options.positions.input || position.top : _ref4$position,
+      settings = _objectWithoutProperties(_ref4, ['text', 'submitText', 'cancelText', 'submitCallback', 'cancelCallback', 'position']);
+
+  blur();
+  hideAlerts();
+
+  var element = document.createElement('div');
+  var id = generateRandomId();
+  element.id = id;
+
+  //oldInputId = id;
+  //oldInputPosition = position;
+  
+  currentId = id;
+  currentPosition = position;
+
+  var elementText = document.createElement('div');
+  elementText.classList.add(options.classes.textbox);
+  elementText.classList.add(options.classes.backgroundInfo);
+  elementText.innerHTML = '<div class="' + options.classes.textboxInner + '">' + text + '</div>';
+
+  var elementInput = document.createElement('input');
+  elementInput.classList.add(options.classes.inputField);
+
+  elementInput.setAttribute('autocapitalize', settings.autocapitalize || 'none');
+  elementInput.setAttribute('autocomplete', settings.autocomplete || 'off');
+  elementInput.setAttribute('autocorrect', settings.autocorrect || 'off');
+  elementInput.setAttribute('autofocus', settings.autofocus || 'true');
+  elementInput.setAttribute('inputmode', settings.inputmode || 'verbatim');
+  elementInput.setAttribute('max', settings.max || '');
+  elementInput.setAttribute('maxlength', settings.maxlength || '');
+  elementInput.setAttribute('min', settings.min || '');
+  elementInput.setAttribute('minlength', settings.minlength || '');
+  elementInput.setAttribute('placeholder', settings.placeholder || '');
+  elementInput.setAttribute('spellcheck', settings.spellcheck || 'default');
+  elementInput.setAttribute('step', settings.step || 'any');
+  elementInput.setAttribute('type', settings.type || 'text');
+
+  elementInput.value = settings.value || '';
+
+  // As-you-type input restrictions
+  if (settings.allowed) {
+    elementInput.oninput = function () {
+      var regex = void 0;
+      if (Array.isArray(settings.allowed)) {
+        var regexString = '';
+        var allowed = settings.allowed;
+        for (var i = 0; i < allowed.length; i++) {
+          if (allowed[i] === 'an') regexString += '0-9a-zA-Z';else if (allowed[i] === 'a') regexString += 'a-zA-Z';else if (allowed[i] === 'n') regexString += '0-9';
+          if (allowed[i] === 's') regexString += ' ';
+        }
+        regex = new RegExp('[^' + regexString + ']', 'g');
+      } else if (_typeof(settings.allowed) === 'object') {
+        regex = settings.allowed;
+      }
+      elementInput.value = elementInput.value.replace(regex, '');
+    };
+  }
+
+  var elementButtonLeft = document.createElement('div');
+  elementButtonLeft.classList.add(options.classes.button);
+  elementButtonLeft.classList.add(options.classes.elementHalf);
+  elementButtonLeft.classList.add(options.classes.backgroundSuccess);
+  elementButtonLeft.innerHTML = submitText;
+  elementButtonLeft.onclick = function () {
+    removeFromDocument(id, position);
+    removeOverlayFromDocument();
+    //oldInputId = null;
+    //oldInputPosition = null;
+    if (submitCallback) submitCallback(elementInput.value);else if (submitCallbackArg) submitCallbackArg(elementInput.value);
+  };
+
+  var elementButtonRight = document.createElement('div');
+  elementButtonRight.classList.add(options.classes.button);
+  elementButtonRight.classList.add(options.classes.elementHalf);
+  elementButtonRight.classList.add(options.classes.backgroundError);
+  elementButtonRight.innerHTML = cancelText;
+  elementButtonRight.onclick = function () {
+    removeFromDocument(id, position);
+    removeOverlayFromDocument();
+    //oldInputId = null;
+    //oldInputPosition = null;
+    if (cancelCallback) cancelCallback(elementInput.value);else if (cancelCallbackArg) cancelCallbackArg(elementInput.value);
+  };
+
+  element.appendChild(elementText);
+  element.appendChild(elementInput);
+  element.appendChild(elementButtonLeft);
+  element.appendChild(elementButtonRight);
+
+  element.listener = function (event) {
+    if (enterClicked(event)) elementButtonLeft.click();else if (escapeClicked(event)) elementButtonRight.click();
+  };
+
+  addToDocument(element, position);
+
+  elementInput.focus();
+
+  addOverlayToDocument(element, position);
+};
+
+exports.input = input;
+
+
+//var oldSelectId = null;
+//var oldSelectPosition = null;
+var select = exports.select = function select(_ref5, cancelCallbackArg) {
+  // if(oldSelectId && oldSelectPosition){
+  //   removeFromDocument(oldSelectId, oldSelectPosition);
+  //   removeOverlayFromDocument();
+  //   oldSelectId = null;
+  //   oldSelectPosition = null;
+  // }
+  var text = _ref5.text,
+      _ref5$cancelText = _ref5.cancelText,
+      cancelText = _ref5$cancelText === undefined ? 'Cancel' : _ref5$cancelText,
+      cancelCallback = _ref5.cancelCallback,
+      choices = _ref5.choices,
+      _ref5$position = _ref5.position,
+      position = _ref5$position === undefined ? options.positions.select || position.top : _ref5$position;
+
+  blur();
+  hideAlerts();
+
+  var element = document.createElement('div');
+  var id = generateRandomId();
+  element.id = id;
+
+  // oldSelectId = id;
+  // oldSelectPosition = position;
+  
+  currentId = id;
+  currentPosition = position;
+
+  var elementText = document.createElement('div');
+  elementText.classList.add(options.classes.textbox);
+  elementText.classList.add(options.classes.backgroundInfo);
+  elementText.innerHTML = '<div class="' + options.classes.textboxInner + '">' + text + '</div>';
+
+  element.appendChild(elementText);
+
+  choices.forEach(function (_ref6, index) {
+    var _ref6$type = _ref6.type,
+        type = _ref6$type === undefined ? 1 : _ref6$type,
+        text = _ref6.text,
+        handler = _ref6.handler;
+
+    var elementChoice = document.createElement('div');
+    elementChoice.classList.add(typeToClassLookup[type]);
+    elementChoice.classList.add(options.classes.button);
+    elementChoice.classList.add(options.classes.selectChoice);
+
+    var nextChoice = choices[index + 1];
+    if (nextChoice && !nextChoice.type) nextChoice.type = 1;
+    if (nextChoice && nextChoice.type === type) {
+      elementChoice.classList.add(options.classes.selectChoiceRepeated);
+    }
+
+    elementChoice.innerHTML = text;
+    elementChoice.onclick = function () {
+      removeFromDocument(id, position);
+      removeOverlayFromDocument();
+      // oldSelectId = null;
+      // oldSelectPosition = null;
+      handler();
+    };
+
+    element.appendChild(elementChoice);
+  });
+
+  var elementCancel = document.createElement('div');
+  elementCancel.classList.add(options.classes.backgroundNeutral);
+  elementCancel.classList.add(options.classes.button);
+  elementCancel.innerHTML = cancelText;
+  elementCancel.onclick = function () {
+    removeFromDocument(id, position);
+    removeOverlayFromDocument();
+    if (cancelCallback) cancelCallback();else if (cancelCallbackArg) cancelCallbackArg();
+  };
+
+  element.appendChild(elementCancel);
+
+  element.listener = function (event) {
+    if (escapeClicked(event)) elementCancel.click();
+  };
+
+  addToDocument(element, position);
+
+  addOverlayToDocument(element, position);
+};
+
+var date = exports.date = function date(_ref7, submitCallbackArg, cancelCallbackArg) {
+  var _ref7$value = _ref7.value,
+      value = _ref7$value === undefined ? new Date() : _ref7$value,
+      _ref7$submitText = _ref7.submitText,
+      submitText = _ref7$submitText === undefined ? 'OK' : _ref7$submitText,
+      _ref7$cancelText = _ref7.cancelText,
+      cancelText = _ref7$cancelText === undefined ? 'Cancel' : _ref7$cancelText,
+      submitCallback = _ref7.submitCallback,
+      cancelCallback = _ref7.cancelCallback,
+      _ref7$position = _ref7.position,
+      position = _ref7$position === undefined ? options.positions.date || position.top : _ref7$position;
+
+  blur();
+  hideAlerts();
+
+  var arrow = '&#9662';
+
+  var elementDateMonth = document.createElement('div');
+  var elementDateDay = document.createElement('div');
+  var elementDateYear = document.createElement('div');
+
+  var setValueHTML = function setValueHTML(date) {
+    elementDateMonth.innerHTML = options.dateMonths[date.getMonth()];
+    elementDateDay.innerHTML = date.getDate();
+    elementDateYear.innerHTML = date.getFullYear();
+  };
+
+  var handleDayInput = function handleDayInput(event) {
+    var daysInMonth = new Date(value.getFullYear(), value.getMonth() + 1, 0).getDate();
+    var day = event.target.textContent.replace(/^0+/, '').replace(/[^\d]/g, '').slice(0, 2);
+    if (Number(day) > daysInMonth) day = daysInMonth.toString();
+    event.target.textContent = day;
+    if (Number(day) < 1) day = '1';
+    value.setDate(Number(day));
+  };
+
+  var handleYearInput = function handleYearInput(event) {
+    var year = event.target.textContent.replace(/^0+/, '').replace(/[^\d]/g, '').slice(0, 4);
+    event.target.textContent = year;
+    value.setFullYear(Number(year));
+  };
+
+  var handleBlur = function handleBlur(event) {
+    setValueHTML(value);
+  };
+
+  var updateMonth = function updateMonth(amount) {
+    var daysInNextMonth = new Date(value.getFullYear(), value.getMonth() + amount + 1, 0).getDate();
+    if (value.getDate() > daysInNextMonth) value.setDate(daysInNextMonth);
+    value.setMonth(value.getMonth() + amount);
+    setValueHTML(value);
+  };
+
+  var updateDay = function updateDay(amount) {
+    value.setDate(value.getDate() + amount);
+    setValueHTML(value);
+  };
+
+  var updateYear = function updateYear(amount) {
+    var nextYear = value.getFullYear() + amount;
+    if (nextYear < 0) value.setFullYear(0);else value.setFullYear(value.getFullYear() + amount);
+    setValueHTML(value);
+  };
+
+  var element = document.createElement('div');
+  var id = generateRandomId();
+  element.id = id;
+
+  var elementDateSelector = document.createElement('div');
+  elementDateSelector.classList.add(options.classes.backgroundInfo);
+
+  var elementDateSelectorInner = document.createElement('div');
+  elementDateSelectorInner.classList.add(options.classes.dateSelectorInner);
+
+  var elementDateUpMonth = document.createElement('div');
+  elementDateUpMonth.classList.add(options.classes.button);
+  elementDateUpMonth.classList.add(options.classes.elementThird);
+  elementDateUpMonth.classList.add(options.classes.dateSelectorUp);
+  elementDateUpMonth.innerHTML = arrow;
+
+  var elementDateUpDay = document.createElement('div');
+  elementDateUpDay.classList.add(options.classes.button);
+  elementDateUpDay.classList.add(options.classes.elementThird);
+  elementDateUpDay.classList.add(options.classes.dateSelectorUp);
+  elementDateUpDay.innerHTML = arrow;
+
+  var elementDateUpYear = document.createElement('div');
+  elementDateUpYear.classList.add(options.classes.button);
+  elementDateUpYear.classList.add(options.classes.elementThird);
+  elementDateUpYear.classList.add(options.classes.dateSelectorUp);
+  elementDateUpYear.innerHTML = arrow;
+
+  elementDateMonth.classList.add(options.classes.element);
+  elementDateMonth.classList.add(options.classes.elementThird);
+  elementDateMonth.innerHTML = options.dateMonths[value.getMonth()];
+
+  elementDateDay.classList.add(options.classes.element);
+  elementDateDay.classList.add(options.classes.elementThird);
+  elementDateDay.setAttribute('contentEditable', true);
+  elementDateDay.addEventListener('input', handleDayInput);
+  elementDateDay.addEventListener('blur', handleBlur);
+  elementDateDay.innerHTML = value.getDate();
+
+  elementDateYear.classList.add(options.classes.element);
+  elementDateYear.classList.add(options.classes.elementThird);
+  elementDateYear.setAttribute('contentEditable', true);
+  elementDateYear.addEventListener('input', handleYearInput);
+  elementDateYear.addEventListener('blur', handleBlur);
+  elementDateYear.innerHTML = value.getFullYear();
+
+  var elementDateDownMonth = document.createElement('div');
+  elementDateDownMonth.classList.add(options.classes.button);
+  elementDateDownMonth.classList.add(options.classes.elementThird);
+  elementDateDownMonth.innerHTML = arrow;
+
+  var elementDateDownDay = document.createElement('div');
+  elementDateDownDay.classList.add(options.classes.button);
+  elementDateDownDay.classList.add(options.classes.elementThird);
+  elementDateDownDay.innerHTML = arrow;
+
+  var elementDateDownYear = document.createElement('div');
+  elementDateDownYear.classList.add(options.classes.button);
+  elementDateDownYear.classList.add(options.classes.elementThird);
+  elementDateDownYear.innerHTML = arrow;
+
+  elementDateUpMonth.onclick = function () {
+    return updateMonth(1);
+  };
+  elementDateUpDay.onclick = function () {
+    return updateDay(1);
+  };
+  elementDateUpYear.onclick = function () {
+    return updateYear(1);
+  };
+  elementDateDownMonth.onclick = function () {
+    return updateMonth(-1);
+  };
+  elementDateDownDay.onclick = function () {
+    return updateDay(-1);
+  };
+  elementDateDownYear.onclick = function () {
+    return updateYear(-1);
+  };
+
+  var elementButtonLeft = document.createElement('div');
+  elementButtonLeft.classList.add(options.classes.button);
+  elementButtonLeft.classList.add(options.classes.elementHalf);
+  elementButtonLeft.classList.add(options.classes.backgroundSuccess);
+  elementButtonLeft.innerHTML = submitText;
+  elementButtonLeft.onclick = function () {
+    removeFromDocument(id, position);
+    removeOverlayFromDocument();
+    if (submitCallback) submitCallback(value);else if (submitCallbackArg) submitCallbackArg(value);
+  };
+
+  var elementButtonRight = document.createElement('div');
+  elementButtonRight.classList.add(options.classes.button);
+  elementButtonRight.classList.add(options.classes.elementHalf);
+  elementButtonRight.classList.add(options.classes.backgroundError);
+  elementButtonRight.innerHTML = cancelText;
+  elementButtonRight.onclick = function () {
+    removeFromDocument(id, position);
+    removeOverlayFromDocument();
+    if (cancelCallback) cancelCallback(value);else if (cancelCallbackArg) cancelCallbackArg(value);
+  };
+
+  elementDateSelectorInner.appendChild(elementDateUpMonth);
+  elementDateSelectorInner.appendChild(elementDateUpDay);
+  elementDateSelectorInner.appendChild(elementDateUpYear);
+  elementDateSelectorInner.appendChild(elementDateMonth);
+  elementDateSelectorInner.appendChild(elementDateDay);
+  elementDateSelectorInner.appendChild(elementDateYear);
+  elementDateSelectorInner.appendChild(elementDateDownMonth);
+  elementDateSelectorInner.appendChild(elementDateDownDay);
+  elementDateSelectorInner.appendChild(elementDateDownYear);
+  elementDateSelector.appendChild(elementDateSelectorInner);
+  element.appendChild(elementDateSelector);
+  element.appendChild(elementButtonLeft);
+  element.appendChild(elementButtonRight);
+
+  element.listener = function (event) {
+    if (enterClicked(event)) elementButtonLeft.click();else if (escapeClicked(event)) elementButtonRight.click();
+  };
+
+  addToDocument(element, position);
+
+  addOverlayToDocument(element, position);
+};
+
+exports.default = {
+  alert: alert,
+  force: force,
+  confirm: confirm,
+  input: input,
+  select: select,
+  date: date,
+  setOptions: setOptions,
+  hideAlerts: hideAlerts,
+  removeAll: removeAll,
+};
+
+/***/ })
+/******/ ]);
+});

+ 79 - 0
src/style.css

@@ -0,0 +1,79 @@
+:root {
+  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+  line-height: 1.5;
+  font-weight: 400;
+
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
+
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
+}
+a:hover {
+  color: #535bf2;
+}
+
+body {
+  margin: 0;
+  display: flex;
+  place-items: center;
+  min-width: 320px;
+  min-height: 100vh;
+}
+
+h1 {
+  font-size: 3.2em;
+  line-height: 1.1;
+}
+
+button {
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
+}
+button:hover {
+  border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+  outline: 4px auto -webkit-focus-ring-color;
+}
+
+.card {
+  padding: 2em;
+}
+
+#app {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+@media (prefers-color-scheme: light) {
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
+  a:hover {
+    color: #747bff;
+  }
+  button {
+    background-color: #f9f9f9;
+  }
+}

+ 7 - 0
vite.config.js

@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  plugins: [vue()],
+})