Ctrl K
ComunidadGuías…

extensions/web-scripts/web-scripts.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
define(['./scripts.xml', './scripts-add.xml',
  './script-blockly.xml', './toolbox.xml', './blocks.json', './blocks-lua',
  './script-view.xml', './script-view-config.xml', './view-schema.json',
  './script-editor.xml'],
  function(scriptsTemplate, scriptsAddTemplate,
    scriptBlocklyTemplate, toolboxXml, blocks, blocksLua,
    scriptViewTemplate, scriptViewConfigTemplate, scriptsViewConfigSchema,
    scriptEditorTemplate)
{
  var SCRIPT_PATH = '/engine/scripts/';
  var SCRIPT_FILES_PATH = '/engine/scriptFiles/';

  function getMatches(s, r) {
    var matches = [];
    var m;
    var re = new RegExp(r, 'g');
    while ((m = re.exec(s)) !== null) {
      matches.push(m[1]);
    }
    return matches;
  }
  function enumToOptions(e) {
    return [e.title, e.const];
  }
  function buildOptions(l) {
    if (isArrayWithItems(l)) {
      return l.map(enumToOptions);
    }
    return [['(no things)', '-empty-']];
  }
  function exportToLua(workspace) {
    //Blockly.Lua.INFINITE_LOOP_TRAP = 'if(--window.LoopTrap == 0) throw "Infinite loop.";\n';
    var lines = [
      "local script = ...",
      "local logger = require('jls.lang.logger')",
      "local Date = require('jls.util.Date')",
      "local utils = require('lha.utils')",
      ""
    ];
    var varRegExp = new RegExp('^[a-zA-Z_][a-zA-Z0-9_]*$'); // Protection against invalid variable names
    var varNames = workspace.getAllVariables().map(function(v) {return v.name;}).filter(function(n) {return varRegExp.test(n)});
    varNames.sort();
    if (varNames.length > 0) {
      lines.push('local ' + varNames.join(', '), '');
    }
    var code = Blockly.Lua.workspaceToCode(workspace);
    // TODO Find a proper way to extract the function names
    var names = getMatches(code, /\nfunction ([a-zA-Z][a-zA-Z0-9_]+)\(/g);
    names.sort();
    if (names.length > 0) {
      lines.push('local ' + names.join(', '), '');
    }
    lines.push(code, '');
    return lines.join('\n');
  }
  function exportToXml(workspace) {
    var xml = Blockly.Xml.workspaceToDom(workspace);
    // domToPrettyText domToText
    var xmlText = Blockly.Xml.domToPrettyText(xml);
    //console.log('scriptsEditor.save()', xmlText);
    return xmlText;
  }
  function exportAs(text, filename, type) {
    //console.log('exportAs()', text);
    var blob = new window.Blob([text], {type : (type || 'text/plain')});
    var blobUrl = window.URL.createObjectURL(blob);
    var clickHandler = function() {
      setTimeout(function() {
        URL.revokeObjectURL(blobUrl);
      }, 150);
    };
    const a = document.createElement('a');
    a.href = blobUrl;
    a.download = filename || 'download';
    a.addEventListener('click', clickHandler, {capture: false, once: true});
    a.click();
    //window.open(blobUrl);
    //window.URL.revokeObjectURL(blobUrl);
  }
  function loadBlocklyWorkspace(self) {
    //console.log('using toolbox', toolboxXml);
    var workspace = Blockly.inject('scriptsEditorBlocklyDiv', {
      media: '/static/blockly/media/',
      toolbox: toolboxXml
    });
    // Registers action buttons
    workspace.registerButtonCallback('refresh', function() {
      self.refresh().then(function() {
        toaster.toast('Refreshed');
      });
    });
    workspace.registerButtonCallback('clear', function() {
      workspace.clear();
      toaster.toast('Cleared');
    });
    workspace.registerButtonCallback('delete', self.onDelete);
    workspace.registerButtonCallback('save', self.onSave);
    workspace.registerButtonCallback('showLua', function() {
      var win = window.open('', 'Lua-' + self.scriptId, 'popup=yes,scrollbars=yes,resizable=yes,toolbar=no,location=no,directories=no,status=no,menubar=no');
      win.document.title = 'Lua ' + self.name;
      win.document.body.innerHTML = '<pre>' + exportToLua(workspace) + '</pre>';
    });
    workspace.registerButtonCallback('exportLua', function() {
      exportAs(exportToLua(workspace), 'lha-script-' + self.name.replace(/\W/g, '-') + '.lua');
    });
    workspace.registerButtonCallback('exportXml', function() {
      exportAs(exportToXml(workspace), 'lha-blocks-' + self.name.replace(/\W/g, '-') + '.xml');
    });
    workspace.registerButtonCallback('importXml', function() {
      self.$refs.uploadInput.click();
    });
    return workspace;
  }
  function customizeBlockly(blockEnv) {
    for (var name in blocks) {
      Blockly.Blocks[name] = (function (name, block) {
        return {
          init: function() {
            //console.log('init block ' + name);
            var b = deepMap(block, function(v) {
              if ((typeof v === 'string') && (v.charAt(0) === '$')) {
                var vv = blockEnv[v.substring(1)]
                if (vv !== undefined) {
                  return vv;
                }
              }
              return v;
            });
            this.jsonInit(b);
          }
        };
      })(name, blocks[name]);
    }
    for (var name in blocksLua) {
      Blockly.Lua[name] = blocksLua[name];
    }
  }
  function onDelete() {
    var scriptId = this.scriptId;
    console.log('scriptsEditor.onDelete(), scriptId is "' + scriptId + '"');
    if (scriptId) {
      confirmation.ask('Delete the script?').then(function() {
        fetch(SCRIPT_PATH + scriptId + '/', {
          method: 'DELETE'
        }).then(assertIsOk).then(function() {
          app.replacePage('scripts');
          toaster.toast('Deleted');
        });
      });
    }
  }
  function onRename() {
    var self = this;
    return fetch(SCRIPT_PATH + this.scriptId + '/name', {
      method: 'PUT',
      body: this.newName
    }).then(assertIsOk).then(function() {
      self.name = self.newName;
      self.newName = false;
      toaster.toast('Renamed');
    });
  }
  function onApply() {
    var scriptId = this.scriptId;
    return this.onSave().then(function() {
      return fetch(SCRIPT_PATH + scriptId + '/reload', {method: 'POST'});
    }).then(function() {
      toaster.toast('Script reloaded');
    });
  }
  function loadBlockly() {
    if (typeof Blockly === 'undefined') {
      return loadScripts(
        'static/blockly/blockly_compressed.js',
        'static/blockly/msg/js/en.js',
        'static/blockly/blocks_compressed.js',
        'static/blockly/javascript_compressed.js',
        'static/blockly/lua_compressed.js'
      );
    }
    return Promise.resolve();
  }

  var blockEnv = {
    lhaDataColor: 38,
    lhaEventColor: 58,
    lhaExpressionColor: 78,
    lhaExperimentalColor: 0,
    lhaEventNames: [
      { const: "-disabled-", title: "never" },
      { const: "startup", title: "startup" },
      { const: "shutdown", title: "shutdown" },
      { const: "poll", title: "polling" },
      { const: "data", title: "data" },
      { const: "heartbeat", title: "heartbeat" },
      { const: "test", title: "testing" }
    ].map(enumToOptions),
    getThingPathOptions: [],
    setThingPathOptions: [],
    eventThingPathOptions: []
  };

  var scriptsBlocklyVue = new Vue({
    template: scriptBlocklyTemplate,
    data: {
      scriptId: '',
      newName: false,
      name: '',
      things: [],
      workspace: null
    },
    methods: {
      onShow: function(scriptId) {
        console.log('scriptsEditor.onShow()');
        var self = this;
        loadBlockly().then(function() {
          if (self.workspace === null) {
            self.workspace = loadBlocklyWorkspace(self);
            customizeBlockly(blockEnv);
          }
          return app.getEnumsById();
        }).then(function(enumsById) {
          // Prepare data fields
          assignMap(blockEnv, {
            getThingPathOptions: buildOptions(enumsById.readablePropertyPaths),
            setThingPathOptions: buildOptions(enumsById.writablePropertyPaths),
            eventThingPathOptions: buildOptions(enumsById.allPropertyPaths)
          });
        }).finally(function() {
          if (scriptId) {
            self.scriptId = scriptId;
            self.refresh();
          }
        });
      },
      uploadThenSave: function(event) {
        var input = event.target;
        if (input.files.length !== 1) {
          return;
        }
        var self = this;
        fetch(SCRIPT_FILES_PATH + self.scriptId + '/blocks.xml', {
          method: 'PUT',
          body: input.files[0]
        }).then(assertIsOk).then(function() {
          toaster.toast('Blocks uploaded');
          self.refresh();
        });
      },
      onPoll: function() {
        return fetch('/engine/extensions/' + this.scriptId + '/poll', {method: 'POST'}).then(function() {
          toaster.toast('Script polled');
        });
      },
      onTest: function() {
        console.log('Testing script');
        return fetch('/engine/extensions/' + this.scriptId + '/test', {method: 'POST'});
      },
      onLogs: function(logs) {
        if (logs) {
          for (var i = 0; i < logs.length; i++) {
            var log = logs[i];
            var msg = 'Engine: ' + log.message
            switch (log.level) {
            case 100:
              console.error(msg);
              break;
            case 90:
              console.warn(msg);
              break;
            case 80:
              console.info(msg);
              break;
            case 70:
              console.log(msg);
              break;
            default:
              console.debug(msg);
              break;
            }
          }
        }
      },
      onDelete: onDelete,
      onRename: onRename,
      onApply: onApply,
      onSave: function() {
        var scriptId = this.scriptId;
        console.log('scriptsEditor.onSave(), scriptId is "' + scriptId + '"');
        var workspace = this.workspace;
        if (!scriptId || !workspace) {
          return;
        }
        // generate
        var code, xmlText;
        try {
          code = exportToLua(workspace);
          xmlText = exportToXml(workspace);
        } catch (e) {
          toaster.toast('Error');
          return Promise.reject(e);
        }
        // save
        return Promise.all([
          fetch(SCRIPT_FILES_PATH + scriptId + '/blocks.xml', {
            method: 'PUT',
            body: xmlText
          }).then(assertIsOk),
          fetch(SCRIPT_FILES_PATH + scriptId + '/script.lua', {
            method: 'PUT',
            body: code
          }).then(assertIsOk)
        ]).then(function() {
          toaster.toast('Saved');
        });
      },
      refresh: function() {
        var workspace = this.workspace;
        if (!workspace) {
          return Promise.reject('workspace not initialized');
        }
        return Promise.all([
          fetch(SCRIPT_FILES_PATH + this.scriptId + '/blocks.xml').then(getResponseText),
          fetch(SCRIPT_FILES_PATH + this.scriptId + '/manifest.json').then(getResponseJson)
        ]).then(apply(this, function(xmlText, manifest) {
          workspace.clear();
          var xml = Blockly.Xml.textToDom(xmlText);
          Blockly.Xml.domToWorkspace(xml, workspace);
          this.name = manifest.name;
        }));
      }
    }
  });

  var scriptsViewVue = new Vue({
    template: scriptViewTemplate,
    data: {
      scriptId: '',
      newName: false,
      name: '',
      text: ''
    },
    methods: {
      onShow: function(scriptId) {
        if (scriptId) {
          this.scriptId = scriptId;
          this.refresh();
        }
      },
      onDelete: onDelete,
      onRename: onRename,
      onApply: onApply,
      onSave: function() {
        console.log('scriptsEditor.onSave(), scriptId is "' + this.scriptId + '"');
        if (!this.scriptId) {
          return;
        }
        return fetch(SCRIPT_FILES_PATH + this.scriptId + '/view.xml', {method: 'PUT', body: this.text}).then(assertIsOk).then(function() {
          toaster.toast('Saved');
        });
      },
      refresh: function() {
        return Promise.all([
          fetch(SCRIPT_FILES_PATH + this.scriptId + '/view.xml').then(assertIsOk).then(getResponseText),
          fetch(SCRIPT_FILES_PATH + this.scriptId + '/manifest.json').then(assertIsOk).then(getJson)
        ]).then(apply(this, function(text, manifest) {
          this.text = text;
          this.name = manifest.name;
        }));
      }
    }
  });

  var scriptsViewConfigVue = new Vue({
    template: scriptViewConfigTemplate,
    data: {
      scriptId: '',
      schema: {},
      config: {}
    },
    methods: {
      onShow: function(scriptId) {
        this.scriptId = scriptId;
        return Promise.all([
          fetch(SCRIPT_FILES_PATH + this.scriptId + '/config.json').then(getJson),
          app.getEnumsById()
        ]).then(apply(this, function(config, enumsById) {
          //console.info('schema:', populateJsonSchema(scriptsViewConfigSchema, enumsById));
          this.schema = populateJsonSchema(scriptsViewConfigSchema, enumsById);
          this.config = config;
        }));
      },
      onSave: function() {
        return putJson(SCRIPT_FILES_PATH + this.scriptId + '/config.json', this.config).then(assertIsOk).then(function() {
          app.back();
          toaster.toast('Saved');
        });
      }
    }
  });
  
  var scriptsEditorVue = new Vue({
    template: scriptEditorTemplate,
    data: {
      scriptId: '',
      newName: false,
      name: '',
      text: ''
    },
    methods: {
      onShow: function(scriptId) {
        if (scriptId) {
          this.scriptId = scriptId;
          this.refresh();
        }
      },
      onDelete: onDelete,
      onRename: onRename,
      onApply: onApply,
      onSave: function() {
        var scriptId = this.scriptId;
        console.log('scriptsEditor.onSave(), scriptId is "' + scriptId + '"');
        if (!scriptId) {
          return;
        }
        return fetch(SCRIPT_FILES_PATH + scriptId + '/script.lua', {
          method: 'PUT',
          body: this.text
        }).then(assertIsOk).then(function() {
          toaster.toast('Saved');
        });
      },
      refresh: function() {
        return Promise.all([
          fetch(SCRIPT_FILES_PATH + this.scriptId + '/script.lua').then(assertIsOk).then(getResponseText),
          fetch(SCRIPT_FILES_PATH + this.scriptId + '/manifest.json').then(assertIsOk).then(getJson)
        ]).then(apply(this, function(luaText, manifest) {
          this.text = luaText;
          this.name = manifest.name;
        }));
      }
    }
  });

  var scriptsAddVue = new Vue({
    template: scriptsAddTemplate,
    methods: {
      newScript: function () {
        fetch(SCRIPT_PATH, {method: 'PUT'}).then(assertIsOk).then(function() {
          app.replacePage('scripts');
        });
      },
      newBlocks: function () {
        fetch(SCRIPT_PATH, {method: 'PUT', headers: {'LHA-Name': 'New Blocks'}}).then(assertIsOk).then(getResponseText).then(function(scriptId) {
          return fetch(SCRIPT_FILES_PATH + scriptId + '/blocks.xml', {
            method: 'PUT',
            body: '<xml xmlns="http://www.w3.org/1999/xhtml"></xml>'
          });
        }).then(function() {
          app.replacePage('scripts');
        });
      },
      newView: function () {
        fetch(SCRIPT_PATH, {method: 'PUT', headers: {
          'LHA-Name': 'New View',
          'LHA-Script': '//web-scripts/view-addon.lua'
        }}).then(assertIsOk).then(getResponseText).then(function(scriptId) {
          return Promise.all([
            fetch(SCRIPT_FILES_PATH + scriptId + '/view.xml', {method: 'PUT', body: '<!-- View content -->'}),
            fetch(SCRIPT_FILES_PATH + scriptId + '/config.json', {method: 'PUT', body: [
              '{',
              '  "id": "view-' + scriptId + '",',
              '  "title": "View ' + scriptId + '"',
              '}'
            ].join('\n')}),
            fetch(SCRIPT_FILES_PATH + scriptId + '/init.js', {method: 'PUT', body: [
              "define(['addon/web-scripts/view-loader.js', './config.json', './view.xml'], function(viewLoader, config, viewXml) {",
              "  viewLoader.load(config, viewXml);",
              "});"
            ].join('\n')})
          ]);
        }).then(function() {
          app.replacePage('scripts');
        });
      }
    }
  });

  var scriptsVue = new Vue({
    template: scriptsTemplate,
    data: {
      scripts: []
    },
    methods: {
      onShow: function () {
        this.scripts = [];
        var self = this;
        fetch(SCRIPT_PATH, {headers: {"Accept": 'application/json'}}).then(getResponseJson).then(function(scripts) {
          self.scripts = scripts;
        });
      },
      pollScript: function (script) {
        fetch('/engine/extensions/' + script.id + '/poll', {method: 'POST'}).then(function() {
          toaster.toast('Script polled');
        });
      },
      reloadScript: function (script) {
        fetch(SCRIPT_PATH + script.id + '/reload', {method: 'POST'}).then(function() {
          toaster.toast('Script reloaded');
        });
      },
      openScript: function (script) {
        var pageId = 'scriptsEditor';
        if (script.hasBlocks) {
          pageId = 'scriptsBlockly';
        } else if (script.hasView) {
          pageId = 'scriptsView';
        }
        app.toPage(pageId, script.id);
      },
      activateScript: function (script) {
        //console.log('activateScript()' + script.active);
        var activate = !script.active;
        return fetch('/engine/extensions/' + script.id + '/' + (activate ? 'enable' : 'disable'), {method: 'POST'}).then(function() {
          toaster.toast('Script ' + (activate ? 'enabled' : 'disabled'));
        });
      },
      onNew: function() {
        app.toPage('scripts-add');
      }
    }
  });

  addPageComponent(scriptsAddVue);
  addPageComponent(scriptsBlocklyVue);
  addPageComponent(scriptsViewVue);
  addPageComponent(scriptsViewConfigVue);
  addPageComponent(scriptsEditorVue);
  addPageComponent(scriptsVue, 'scroll', true, true);

});