juanmard/icestudio

View on GitHub
services/blocks.js

Summary

Maintainability
F
1 mo
Test Coverage
angular
  .module('icestudio')
  .service('blocks', function ($log, joint, utils, common, gettextCatalog) {
    'use strict';

    const _tcStr = function (str, args) {
      return gettextCatalog.getString(str, args);
    };

    var gridsize = 8;
    var resultAlert = null;

    this.newBasic = newBasic;
    this.newGeneric = newGeneric;

    this.loadBasic = loadBasic;
    this.loadGeneric = loadGeneric;
    this.loadWire = loadWire;

    this.editBasic = editBasic;

    //-- New

    function newBasic(type, callback) {
      if (type.slice(0, 6) !== 'basic.') {
        return;
      }
      const fmap = {
        input: newBasicInput,
        output: newBasicOutput,
        outputLabel: newBasicOutputLabel,
        inputLabel: newBasicInputLabel,
        constant: newBasicConstant,
        memory: newBasicMemory,
        code: newBasicCode,
        info: newBasicInfo,
      };
      const fcn = fmap[type.slice(6)];
      if (fcn) {
        fcn(callback);
        return;
      }
      $log.error('[srv.blocks.newBasic] unknown type:', type);
    }

    const colorOpts = [
      {value: 'indianred', label: _tcStr('IndianRed')},
      {value: 'red', label: _tcStr('Red')},
      {value: 'deeppink', label: _tcStr('DeepPink')},
      {value: 'mediumVioletRed', label: _tcStr('MediumVioletRed')},
      {value: 'coral', label: _tcStr('Coral')},
      {value: 'orangered', label: _tcStr('OrangeRed')},
      {value: 'darkorange', label: _tcStr('DarkOrange')},
      {value: 'gold', label: _tcStr('Gold')},
      {value: 'yellow', label: _tcStr('Yellow')},
      {value: 'fuchsia', label: _tcStr('Fuchsia')},
      {value: 'slateblue', label: _tcStr('SlateBlue')},
      {value: 'greenyellow', label: _tcStr('GreenYellow')},
      {value: 'springgreen', label: _tcStr('SpringGreen')},
      {value: 'darkgreen', label: _tcStr('DarkGreen')},
      {value: 'olivedrab', label: _tcStr('OliveDrab')},
      {value: 'lightseagreen', label: _tcStr('LightSeaGreen')},
      {value: 'turquoise', label: _tcStr('Turquoise')},
      {value: 'steelblue', label: _tcStr('SteelBlue')},
      {value: 'deepskyblue', label: _tcStr('DeepSkyBlue')},
      {value: 'royalblue', label: _tcStr('RoyalBlue')},
      {value: 'navy', label: _tcStr('Navy')},
    ];

    function newBasicOutputLabel(callback) {
      var blockInstance = {
        id: null,
        data: {},
        type: 'basic.outputLabel',
        position: {x: 0, y: 0},
      };
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Enter the input blocks'),
          value: '',
        },
        {
          type: 'combobox',
          label: _tcStr('Choose a color'),
          value: 'fuchsia',
          options: colorOpts,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var labels = values[0].replace(/\s*,\s*/g, ',').split(',');
        var color = values[1];
        var virtual = !values[2];
        var clock = values[2];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var portInfo,
          portInfos = [];
        for (var l in labels) {
          portInfo = utils.parsePortLabel(
            labels[l],
            common.PATTERN_GLOBAL_PORT_LABEL
          );
          if (portInfo) {
            evt.cancel = false;
            portInfos.push(portInfo);
          } else {
            evt.cancel = true;
            resultAlert = alertify.warning(
              _tcStr('Wrong block name {{name}}', {
                name: labels[l],
              })
            );
            return;
          }
        }
        // Create blocks
        var cells = [];
        for (var p in portInfos) {
          portInfo = portInfos[p];
          if (portInfo.rangestr && clock) {
            evt.cancel = true;
            resultAlert = alertify.warning(
              _tcStr('Clock not allowed for data buses')
            );
            return;
          }
          var pins = getPins(portInfo);
          blockInstance.data = {
            blockColor: color,
            name: portInfo.name,
            range: portInfo.rangestr,
            pins: pins,
            virtual: virtual,
            clock: clock,
          };
          cells.push(loadBasic(blockInstance));
          // Next block position
          blockInstance.position.y +=
            (virtual ? 10 : 6 + 4 * pins.length) * gridsize;
        }
        if (callback) {
          callback(cells);
        }
      });
    }

    function newBasicInput(callback) {
      var blockInstance = {
        id: null,
        data: {},
        type: 'basic.input',
        position: {x: 0, y: 0},
      };
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Enter the input blocks'),
          value: '',
        },
        {
          type: 'checkbox',
          label: _tcStr('FPGA pin'),
          value: true,
        },
        {
          type: 'checkbox',
          label: _tcStr('Show clock'),
          value: false,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var labels = values[0].replace(/\s*,\s*/g, ',').split(',');
        var virtual = !values[1];
        var clock = values[2];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var portInfo,
          portInfos = [];
        for (var l in labels) {
          portInfo = utils.parsePortLabel(
            labels[l],
            common.PATTERN_GLOBAL_PORT_LABEL
          );
          if (portInfo) {
            evt.cancel = false;
            portInfos.push(portInfo);
          } else {
            evt.cancel = true;
            resultAlert = alertify.warning(
              _tcStr('Wrong block name {{name}}', {
                name: labels[l],
              })
            );
            return;
          }
        }
        // Create blocks
        var cells = [];
        for (var p in portInfos) {
          portInfo = portInfos[p];
          if (portInfo.rangestr && clock) {
            evt.cancel = true;
            resultAlert = alertify.warning(
              _tcStr('Clock not allowed for data buses')
            );
            return;
          }
          var pins = getPins(portInfo);
          blockInstance.data = {
            name: portInfo.name,
            range: portInfo.rangestr,
            pins: pins,
            virtual: virtual,
            clock: clock,
          };
          cells.push(loadBasic(blockInstance));
          // Next block position
          blockInstance.position.y +=
            (virtual ? 10 : 6 + 4 * pins.length) * gridsize;
        }
        if (callback) {
          callback(cells);
        }
      });
    }

    function newBasicOutput(callback) {
      var blockInstance = {
        id: null,
        data: {},
        type: 'basic.output',
        position: {x: 0, y: 0},
      };
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Enter the output blocks'),
          value: '',
        },
        {
          type: 'checkbox',
          label: _tcStr('FPGA pin'),
          value: true,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var labels = values[0].replace(/\s*,\s*/g, ',').split(',');
        var virtual = !values[1];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var portInfo,
          portInfos = [];
        for (var l in labels) {
          portInfo = utils.parsePortLabel(
            labels[l],
            common.PATTERN_GLOBAL_PORT_LABEL
          );
          if (portInfo) {
            evt.cancel = false;
            portInfos.push(portInfo);
          } else {
            evt.cancel = true;
            resultAlert = alertify.warning(
              _tcStr('Wrong block name {{name}}', {
                name: labels[l],
              })
            );
            return;
          }
        }
        // Create blocks
        var cells = [];
        for (var p in portInfos) {
          portInfo = portInfos[p];
          var pins = getPins(portInfo);
          blockInstance.data = {
            name: portInfo.name,
            range: portInfo.rangestr,
            pins: pins,
            virtual: virtual,
          };
          cells.push(loadBasic(blockInstance));
          // Next block position
          blockInstance.position.y +=
            (virtual ? 10 : 6 + 4 * pins.length) * gridsize;
        }
        if (callback) {
          callback(cells);
        }
      });
    }

    function newBasicInputLabel(callback) {
      var blockInstance = {
        id: null,
        data: {},
        type: 'basic.inputLabel',
        position: {x: 0, y: 0},
      };
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Enter the output blocks'),
          value: '',
        },
        {
          type: 'combobox',
          label: _tcStr('Choose a color'),
          value: 'fuchsia',
          options: colorOpts,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var labels = values[0].replace(/\s*,\s*/g, ',').split(',');
        var color = values[1];
        var virtual = !values[2];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var portInfo,
          portInfos = [];
        for (var l in labels) {
          portInfo = utils.parsePortLabel(
            labels[l],
            common.PATTERN_GLOBAL_PORT_LABEL
          );
          if (portInfo) {
            evt.cancel = false;
            portInfos.push(portInfo);
          } else {
            evt.cancel = true;
            resultAlert = alertify.warning(
              _tcStr('Wrong block name {{name}}', {
                name: labels[l],
              })
            );
            return;
          }
        }
        // Create blocks
        var cells = [];
        for (var p in portInfos) {
          portInfo = portInfos[p];
          var pins = getPins(portInfo);
          blockInstance.data = {
            blockColor: color,
            name: portInfo.name,
            range: portInfo.rangestr,
            pins: pins,
            virtual: virtual,
          };
          cells.push(loadBasic(blockInstance));
          // Next block position
          blockInstance.position.y +=
            (virtual ? 10 : 6 + 4 * pins.length) * gridsize;
        }
        if (callback) {
          callback(cells);
        }
      });
    }

    function getPins(portInfo) {
      var pins = [];
      if (portInfo.range) {
        for (var r in portInfo.range) {
          pins.push({index: portInfo.range[r].toString(), name: '', value: ''});
        }
      } else {
        pins.push({index: '0', name: 'NULL', value: 'NULL'});
      }
      return pins;
    }

    function newBasicConstant(callback) {
      var blockInstance = {
        id: null,
        data: {},
        type: 'basic.constant',
        position: {x: 0, y: 0},
      };
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Enter the constant blocks'),
          value: '',
        },
        {
          type: 'checkbox',
          label: _tcStr('Local parameter'),
          value: false,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var labels = values[0].replace(/\s*,\s*/g, ',').split(',');
        var local = values[1];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var paramInfo,
          paramInfos = [];
        for (var l in labels) {
          paramInfo = utils.parseParamLabel(
            labels[l],
            common.PATTERN_GLOBAL_PARAM_LABEL
          );
          if (paramInfo) {
            evt.cancel = false;
            paramInfos.push(paramInfo);
          } else {
            evt.cancel = true;
            resultAlert = alertify.warning(
              _tcStr('Wrong block name {{name}}', {
                name: labels[l],
              })
            );
            return;
          }
        }
        // Create blocks
        var cells = [];
        for (var p in paramInfos) {
          paramInfo = paramInfos[p];
          blockInstance.data = {
            name: paramInfo.name,
            value: '',
            local: local,
          };
          cells.push(loadBasicConstant(blockInstance));
          blockInstance.position.x += 15 * gridsize;
        }
        if (callback) {
          callback(cells);
        }
      });
    }

    function newBasicMemory(callback) {
      var blockInstance = {
        id: null,
        data: {},
        type: 'basic.memory',
        position: {x: 0, y: 0},
        size: {width: 96, height: 104},
      };
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Enter the memory blocks'),
          value: '',
        },
        {
          type: 'combobox',
          label: _tcStr('Address format'),
          value: 10,
          options: [
            {value: 2, label: _tcStr('Binary')},
            {value: 10, label: _tcStr('Decimal')},
            {value: 16, label: _tcStr('Hexadecimal')},
          ],
        },
        {
          type: 'checkbox',
          label: _tcStr('Local parameter'),
          value: false,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var labels = values[0].replace(/\s*,\s*/g, ',').split(',');
        var local = values[2];
        var format = parseInt(values[1]);
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var paramInfo,
          paramInfos = [];
        for (var l in labels) {
          paramInfo = utils.parseParamLabel(
            labels[l],
            common.PATTERN_GLOBAL_PARAM_LABEL
          );
          if (paramInfo) {
            evt.cancel = false;
            paramInfos.push(paramInfo);
          } else {
            evt.cancel = true;
            resultAlert = alertify.warning(
              _tcStr('Wrong block name {{name}}', {
                name: labels[l],
              })
            );
            return;
          }
        }
        // Create blocks
        var cells = [];
        for (var p in paramInfos) {
          paramInfo = paramInfos[p];
          blockInstance.data = {
            name: paramInfo.name,
            list: '',
            local: local,
            format: format,
          };
          cells.push(loadBasicMemory(blockInstance));
          blockInstance.position.x += 15 * gridsize;
        }
        if (callback) {
          callback(cells);
        }
      });
    }

    function newBasicCode(callback, block) {
      var blockInstance = {
        id: null,
        data: {
          code: '',
          params: [],
          ports: {in: [], out: []},
        },
        type: 'basic.code',
        position: {x: 0, y: 0},
        size: {width: 192, height: 128},
      };
      var defaultValues = ['', '', ''];
      if (block) {
        blockInstance = block;
        var index, port;
        if (block.data.ports) {
          var inPorts = [];
          for (index in block.data.ports.in) {
            port = block.data.ports.in[index];
            inPorts.push(port.name + (port.range || ''));
          }
          defaultValues[0] = inPorts.join(' , ');
          var outPorts = [];
          for (index in block.data.ports.out) {
            port = block.data.ports.out[index];
            outPorts.push(port.name + (port.range || ''));
          }
          defaultValues[1] = outPorts.join(' , ');
        }
        if (block.data.params) {
          var params = [];
          for (index in block.data.params) {
            params.push(block.data.params[index].name);
          }
          defaultValues[2] = params.join(' , ');
        }
      }
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Enter the input ports'),
          value: defaultValues[0],
        },
        {
          type: 'text',
          title: _tcStr('Enter the output ports'),
          value: defaultValues[1],
        },
        {
          type: 'text',
          title: _tcStr('Enter the parameters'),
          value: defaultValues[2],
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var inPorts = values[0].replace(/\s*,\s*/g, ',').split(',');
        var outPorts = values[1].replace(/\s*,\s*/g, ',').split(',');
        var params = values[2].replace(/\s*,\s*/g, ',').split(',');
        var allNames = [];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var i,
          inPortInfo,
          inPortInfos = [];

        let nib = 0,
          nob = 0;
        for (i in inPorts) {
          if (inPorts[i]) {
            inPortInfo = utils.parsePortLabel(
              inPorts[i],
              common.PATTERN_PORT_LABEL
            );
            if (inPortInfo && inPortInfo.name) {
              evt.cancel = false;
              inPortInfos.push(inPortInfo);
            } else {
              evt.cancel = true;
              resultAlert = alertify.warning(
                _tcStr('Wrong port name {{name}}', {
                  name: inPorts[i],
                })
              );
              return;
            }
          } else {
            nib++;
          }
        }

        var o,
          outPortInfo,
          outPortInfos = [];
        for (o in outPorts) {
          if (outPorts[o]) {
            outPortInfo = utils.parsePortLabel(
              outPorts[o],
              common.PATTERN_PORT_LABEL
            );
            if (outPortInfo && outPortInfo.name) {
              evt.cancel = false;
              outPortInfos.push(outPortInfo);
            } else {
              evt.cancel = true;
              resultAlert = alertify.warning(
                _tcStr('Wrong port name {{name}}', {
                  name: outPorts[o],
                })
              );
              return;
            }
          } else {
            nob++;
          }
        }
        if (nib >= inPorts.length && nob >= outPorts.length) {
          evt.cancel = true;
          resultAlert = alertify.warning(
            _tcStr('Code block needs at least one input or one output')
          );
          return;
        }

        var p,
          paramInfo,
          paramInfos = [];
        for (p in params) {
          if (params[p]) {
            paramInfo = utils.parseParamLabel(
              params[p],
              common.PATTERN_PARAM_LABEL
            );
            if (paramInfo) {
              evt.cancel = false;
              paramInfos.push(paramInfo);
            } else {
              evt.cancel = true;
              resultAlert = alertify.warning(
                _tcStr('Wrong parameter name {{name}}', {
                  name: params[p],
                })
              );
              return;
            }
          }
        }
        // Create ports
        var pins;
        blockInstance.data.ports.in = [];
        for (i in inPortInfos) {
          if (inPortInfos[i]) {
            pins = getPins(inPortInfos[i]);
            blockInstance.data.ports.in.push({
              name: inPortInfos[i].name,
              range: inPortInfos[i].rangestr,
              size: pins.length > 1 ? pins.length : undefined,
            });
            allNames.push(inPortInfos[i].name);
          }
        }
        blockInstance.data.ports.out = [];
        for (o in outPortInfos) {
          if (outPortInfos[o]) {
            pins = getPins(outPortInfos[o]);
            blockInstance.data.ports.out.push({
              name: outPortInfos[o].name,
              range: outPortInfos[o].rangestr,
              size: pins.length > 1 ? pins.length : undefined,
            });
            allNames.push(outPortInfos[o].name);
          }
        }
        blockInstance.data.params = [];
        for (p in paramInfos) {
          if (paramInfos[p]) {
            blockInstance.data.params.push({
              name: paramInfos[p].name,
            });
            allNames.push(paramInfos[p].name);
          }
        }
        // Check duplicated attributes
        var numNames = allNames.length;
        if (numNames === $.unique(allNames).length) {
          evt.cancel = false;
          // Create block
          if (callback) {
            callback([loadBasicCode(blockInstance)]);
          }
        } else {
          evt.cancel = true;
          resultAlert = alertify.warning(_tcStr('Duplicated block attributes'));
        }
      });
    }

    function newBasicInfo(callback) {
      var blockInstance = {
        id: null,
        data: {info: '', readonly: false},
        type: 'basic.info',
        position: {x: 0, y: 0},
        size: {width: 192, height: 128},
      };
      if (callback) {
        callback([loadBasicInfo(blockInstance)]);
      }
    }

    function newGeneric(type, block, callback) {
      var blockInstance = {
        id: null,
        type: type,
        position: {x: 0, y: 0},
      };
      if (resultAlert) {
        resultAlert.dismiss(false);
      }
      if (
        block &&
        block.design &&
        block.design.graph &&
        block.design.graph.blocks &&
        block.design.graph.wires
      ) {
        if (callback) {
          callback(loadGeneric(blockInstance, block));
        }
      } else {
        resultAlert = alertify.error(
          _tcStr('Wrong block format: {{type}}', {
            type: type,
          }),
          30
        );
      }
    }

    //-- Load

    function loadBasic(instance, disabled) {
      switch (instance.type) {
        case 'basic.input':
          return loadBasicInput(instance, disabled);
        case 'basic.output':
          return loadBasicOutput(instance, disabled);
        case 'basic.outputLabel':
          return loadBasicOutputLabel(instance, disabled);
        case 'basic.inputLabel':
          return loadBasicInputLabel(instance, disabled);

        case 'basic.constant':
          return loadBasicConstant(instance, disabled);
        case 'basic.memory':
          return loadBasicMemory(instance, disabled);
        case 'basic.code':
          return loadBasicCode(instance, disabled);
        case 'basic.info':
          return loadBasicInfo(instance, disabled);
        default:
          break;
      }
    }

    function loadBasicInput(instance, disabled) {
      var data = instance.data;
      var rightPorts = [
        {
          id: 'out',
          name: '',
          label: '',
          size: data.pins ? data.pins.length : data.size || 1,
        },
      ];

      var cell = new joint.shapes.ice.Input({
        id: instance.id,
        blockType: instance.type,
        data: instance.data,
        position: instance.position,
        disabled: disabled,
        rightPorts: rightPorts,
        choices: common.pinoutInputHTML,
      });

      return cell;
    }

    function loadBasicOutputLabel(instance, disabled) {
      var data = instance.data;
      var rightPorts = [
        {
          id: 'outlabel',
          name: '',
          label: '',
          size: data.pins ? data.pins.length : data.size || 1,
        },
      ];

      var cell = new joint.shapes.ice.OutputLabel({
        id: instance.id,
        blockType: instance.type,
        data: instance.data,
        position: instance.position,
        disabled: disabled,
        rightPorts: rightPorts,
        choices: common.pinoutInputHTML,
      });
      return cell;
    }

    function loadBasicOutput(instance, disabled) {
      var data = instance.data;
      var leftPorts = [
        {
          id: 'in',
          name: '',
          label: '',
          size: data.pins ? data.pins.length : data.size || 1,
        },
      ];
      var cell = new joint.shapes.ice.Output({
        id: instance.id,
        blockType: instance.type,
        data: instance.data,
        position: instance.position,
        disabled: disabled,
        leftPorts: leftPorts,
        choices: common.pinoutOutputHTML,
      });
      return cell;
    }
    function loadBasicInputLabel(instance, disabled) {
      var data = instance.data;
      var leftPorts = [
        {
          id: 'inlabel',
          name: '',
          label: '',
          size: data.pins ? data.pins.length : data.size || 1,
        },
      ];

      //var cell = new joint.shapes.ice.Output({
      var cell = new joint.shapes.ice.InputLabel({
        id: instance.id,
        blockColor: instance.blockColor,
        blockType: instance.type,
        data: instance.data,
        position: instance.position,
        disabled: disabled,
        leftPorts: leftPorts,
        choices: common.pinoutOutputHTML,
      });
      return cell;
    }

    function loadBasicConstant(instance, disabled) {
      var bottomPorts = [
        {
          id: 'constant-out',
          name: '',
          label: '',
        },
      ];
      var cell = new joint.shapes.ice.Constant({
        id: instance.id,
        blockType: instance.type,
        data: instance.data,
        position: instance.position,
        disabled: disabled,
        bottomPorts: bottomPorts,
      });
      return cell;
    }

    function loadBasicMemory(instance, disabled) {
      var bottomPorts = [
        {
          id: 'memory-out',
          name: '',
          label: '',
        },
      ];
      var cell = new joint.shapes.ice.Memory({
        id: instance.id,
        blockType: instance.type,
        data: instance.data,
        position: instance.position,
        size: instance.size,
        disabled: disabled,
        bottomPorts: bottomPorts,
      });
      return cell;
    }

    function loadBasicCode(instance, disabled) {
      var port;
      var leftPorts = [];
      var rightPorts = [];
      var topPorts = [];

      for (var i in instance.data.ports.in) {
        port = instance.data.ports.in[i];
        if (!port.range) {
          port.default = utils.hasInputRule(port.name);
        }
        leftPorts.push({
          id: port.name,
          name: port.name,
          label: port.name + (port.range || ''),
          size: port.size || 1,
        });
      }

      for (var o in instance.data.ports.out) {
        port = instance.data.ports.out[o];
        rightPorts.push({
          id: port.name,
          name: port.name,
          label: port.name + (port.range || ''),
          size: port.size || 1,
        });
      }

      for (var p in instance.data.params) {
        port = instance.data.params[p];
        topPorts.push({
          id: port.name,
          name: port.name,
          label: port.name,
        });
      }

      var cell = new joint.shapes.ice.Code({
        id: instance.id,
        blockType: instance.type,
        data: instance.data,
        position: instance.position,
        size: instance.size,
        disabled: disabled,
        leftPorts: leftPorts,
        rightPorts: rightPorts,
        topPorts: topPorts,
      });

      return cell;
    }

    function loadBasicInfo(instance, disabled) {
      // Translate info content
      if (instance.data.info && instance.data.readonly) {
        instance.data.text = _tcStr(instance.data.info);
      }
      var cell = new joint.shapes.ice.Info({
        id: instance.id,
        blockType: instance.type,
        data: instance.data,
        position: instance.position,
        size: instance.size,
        disabled: disabled,
      });
      return cell;
    }

    function loadGeneric(instance, block, disabled) {
      var i;
      var leftPorts = [];
      var rightPorts = [];
      var topPorts = [];
      var bottomPorts = [];

      instance.data = {ports: {in: []}};

      for (i in block.design.graph.blocks) {
        var item = block.design.graph.blocks[i];
        if (item.type === 'basic.input') {
          if (!item.data.range) {
            instance.data.ports.in.push({
              name: item.id,
              default: utils.hasInputRule(
                (item.data.clock ? 'clk' : '') || item.data.name
              ),
            });
          }
          leftPorts.push({
            id: item.id,
            name: item.data.name,
            label: item.data.name + (item.data.range || ''),
            size: item.data.pins ? item.data.pins.length : item.data.size || 1,
            clock: item.data.clock,
          });
        } else if (item.type === 'basic.output') {
          rightPorts.push({
            id: item.id,
            name: item.data.name,
            label: item.data.name + (item.data.range || ''),
            size: item.data.pins ? item.data.pins.length : item.data.size || 1,
          });
        } else if (
          item.type === 'basic.constant' ||
          item.type === 'basic.memory'
        ) {
          if (!item.data.local) {
            topPorts.push({
              id: item.id,
              name: item.data.name,
              label: item.data.name,
            });
          }
        }
      }

      const _img = block.package.image;
      return new joint.shapes.ice.Generic({
        id: instance.id,
        blockType: instance.type,
        data: instance.data,
        config: block.design.config,
        pullup: block.design.pullup,
        image: !_img ? '' : _img.startsWith('%3Csvg') ? _img : encodeURI(_img),
        label: block.package.name,
        tooltip: _tcStr(block.package.description),
        position: instance.position,
        // instance.size ? instance.size :
        size: {
          width: Math.max(
            4 * gridsize * Math.max(topPorts.length, bottomPorts.length),
            12 * gridsize
          ),
          height: Math.max(
            4 * gridsize * Math.max(leftPorts.length, rightPorts.length),
            8 * gridsize
          ),
        },
        disabled: disabled,
        leftPorts: leftPorts,
        rightPorts: rightPorts,
        topPorts: topPorts,
      });
    }

    function loadWire(instance, source, target) {
      // Find selectors
      var sourceSelector, targetSelector;
      var leftPorts = target.get('leftPorts');
      var rightPorts = source.get('rightPorts');

      for (var _out = 0; _out < rightPorts.length; _out++) {
        if (rightPorts[_out] === instance.source.port) {
          sourceSelector = _out;
          break;
        }
      }
      for (var _in = 0; _in < leftPorts.length; _in++) {
        if (leftPorts[_in] === instance.target.port) {
          targetSelector = _in;
          break;
        }
      }

      var _wire = new joint.shapes.ice.Wire({
        source: {
          id: source.id,
          selector: sourceSelector,
          port: instance.source.port,
        },
        target: {
          id: target.id,
          selector: targetSelector,
          port: instance.target.port,
        },
        vertices: instance.vertices,
      });
      return _wire;
    }

    //-- Edit

    function editBasic(type, cellView, callback) {
      switch (type) {
        case 'basic.input':
          editBasicInput(cellView, callback);
          break;
        case 'basic.output':
          editBasicOutput(cellView, callback);
          break;
        case 'basic.outputLabel':
          editBasicOutputLabel(cellView, callback);
          break;
        case 'basic.inputLabel':
          editBasicInputLabel(cellView, callback);
          break;

        case 'basic.constant':
          editBasicConstant(cellView);
          break;
        case 'basic.memory':
          editBasicMemory(cellView);
          break;
        case 'basic.code':
          editBasicCode(cellView, callback);
          break;
        case 'basic.info':
          editBasicInfo(cellView);
          break;
        default:
          break;
      }
    }

    function editBasicOutputLabel(cellView, callback) {
      var graph = cellView.paper.model;
      var block = cellView.model.attributes;
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Update the block name'),
          value: block.data.name + (block.data.range || ''),
        },
        {
          type: 'combobox',
          title: _tcStr('Choose a color'),
          value:
            typeof block.data.blockColor !== 'undefined'
              ? block.data.blockColor
              : 'fuchsia',
          options: colorOpts,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var oldSize,
          newSize,
          offset = 0;
        var label = values[0];
        var color = values[1];
        var virtual = !values[2];
        var clock = values[2];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var portInfo = utils.parsePortLabel(
          label,
          common.PATTERN_GLOBAL_PORT_LABEL
        );
        if (portInfo) {
          evt.cancel = false;
          if (portInfo.rangestr && clock) {
            evt.cancel = true;
            resultAlert = alertify.warning(
              _tcStr('Clock not allowed for data buses')
            );
            return;
          }
          if ((block.data.range || '') !== (portInfo.rangestr || '')) {
            var pins = getPins(portInfo);
            oldSize = block.data.pins ? block.data.pins.length : 1;
            oldSize = block.data.virtual ? 1 : oldSize;
            newSize = virtual ? 1 : pins ? pins.length : 1;
            // Update block position when size changes
            offset = 16 * (oldSize - newSize);
            // Create new block
            var blockInstance = {
              id: null,
              data: {
                name: portInfo.name,
                range: portInfo.rangestr,
                pins: pins,
                virtual: virtual,
                clock: clock,
              },
              type: block.blockType,
              position: {
                x: block.position.x,
                y: block.position.y + offset,
              },
            };
            if (callback) {
              graph.startBatch('change');
              callback(loadBasic(blockInstance));
              cellView.model.remove();
              graph.stopBatch('change');
              resultAlert = alertify.success(_tcStr('Block updated'));
            }
          } else if (
            block.data.name !== portInfo.name ||
            block.data.virtual !== virtual ||
            block.data.clock !== clock ||
            block.data.blockColor !== color
          ) {
            var size = block.data.pins ? block.data.pins.length : 1;
            oldSize = block.data.virtual ? 1 : size;
            newSize = virtual ? 1 : size;
            // Update block position when size changes
            offset = 16 * (oldSize - newSize);
            // Edit block
            graph.startBatch('change');
            var data = utils.clone(block.data);
            data.name = portInfo.name;
            data.oldBlockColor = data.blockColor;
            data.blockColor = color;
            data.virtual = virtual;
            data.clock = clock;
            cellView.model.set('data', data, {
              translateBy: cellView.model.id,
              tx: 0,
              ty: -offset,
            });
            cellView.model.translate(0, offset);
            graph.stopBatch('change');
            cellView.apply();
            resultAlert = alertify.success(_tcStr('Block updated'));
          }
        } else {
          evt.cancel = true;
          resultAlert = alertify.warning(
            _tcStr('Wrong block name {{name}}', {name: label})
          );
        }
      });
    }

    function editBasicInputLabel(cellView, callback) {
      var graph = cellView.paper.model;
      var block = cellView.model.attributes;
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Update the block name'),
          value: block.data.name + (block.data.range || ''),
        },
        {
          type: 'combobox',
          title: _tcStr('Choose a color'),
          value:
            typeof block.data.blockColor !== 'undefined'
              ? block.data.blockColor
              : 'fuchsia',
          options: colorOpts,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var oldSize,
          newSize,
          offset = 0;
        var label = values[0];
        var color = values[1];
        var virtual = !values[2];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var portInfo = utils.parsePortLabel(
          label,
          common.PATTERN_GLOBAL_PORT_LABEL
        );
        if (portInfo) {
          evt.cancel = false;
          if ((block.data.range || '') !== (portInfo.rangestr || '')) {
            var pins = getPins(portInfo);
            oldSize = block.data.pins ? block.data.pins.length : 1;
            oldSize = block.data.virtual ? 1 : oldSize;
            newSize = virtual ? 1 : pins ? pins.length : 1;
            // Update block position when size changes
            offset = 16 * (oldSize - newSize);
            // Create new block
            var blockInstance = {
              id: null,
              data: {
                name: portInfo.name,
                range: portInfo.rangestr,
                pins: pins,
                virtual: virtual,
              },
              type: block.blockType,
              position: {
                x: block.position.x,
                y: block.position.y + offset,
              },
            };
            if (callback) {
              graph.startBatch('change');
              callback(loadBasic(blockInstance));
              cellView.model.remove();
              graph.stopBatch('change');
              resultAlert = alertify.success(_tcStr('Block updated'));
            }
          } else if (
            block.data.name !== portInfo.name ||
            block.data.virtual !== virtual ||
            block.data.blockColor !== color
          ) {
            var size = block.data.pins ? block.data.pins.length : 1;
            oldSize = block.data.virtual ? 1 : size;
            newSize = virtual ? 1 : size;
            // Update block position when size changes
            offset = 16 * (oldSize - newSize);
            // Edit block
            graph.startBatch('change');
            var data = utils.clone(block.data);
            data.name = portInfo.name;
            data.oldBlockColor = data.blockColor;
            data.blockColor = color;
            data.virtual = virtual;
            cellView.model.set('data', data, {
              translateBy: cellView.model.id,
              tx: 0,
              ty: -offset,
            });
            cellView.model.translate(0, offset);
            graph.stopBatch('change');
            cellView.apply();
            resultAlert = alertify.success(_tcStr('Block updated'));
          }
        } else {
          evt.cancel = true;
          resultAlert = alertify.warning(
            _tcStr('Wrong block name {{name}}', {name: label})
          );
        }
      });
    }

    function editBasicInput(cellView, callback) {
      var graph = cellView.paper.model;
      var block = cellView.model.attributes;
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Update the block name'),
          value: block.data.name + (block.data.range || ''),
        },
        {
          type: 'checkbox',
          label: _tcStr('FPGA pin'),
          value: !block.data.virtual,
        },
        {
          type: 'checkbox',
          label: _tcStr('Show clock'),
          value: block.data.clock,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var oldSize,
          newSize,
          offset = 0;
        var label = values[0];
        var virtual = !values[1];
        var clock = values[2];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var portInfo = utils.parsePortLabel(
          label,
          common.PATTERN_GLOBAL_PORT_LABEL
        );
        if (portInfo) {
          evt.cancel = false;
          if (portInfo.rangestr && clock) {
            evt.cancel = true;
            resultAlert = alertify.warning(
              _tcStr('Clock not allowed for data buses')
            );
            return;
          }
          if ((block.data.range || '') !== (portInfo.rangestr || '')) {
            var pins = getPins(portInfo);
            oldSize = block.data.pins ? block.data.pins.length : 1;
            oldSize = block.data.virtual ? 1 : oldSize;
            newSize = virtual ? 1 : pins ? pins.length : 1;
            // Update block position when size changes
            offset = 16 * (oldSize - newSize);
            // Create new block
            var blockInstance = {
              id: null,
              data: {
                name: portInfo.name,
                range: portInfo.rangestr,
                pins: pins,
                virtual: virtual,
                clock: clock,
              },
              type: block.blockType,
              position: {
                x: block.position.x,
                y: block.position.y + offset,
              },
            };
            if (callback) {
              graph.startBatch('change');
              callback(loadBasic(blockInstance));
              cellView.model.remove();
              graph.stopBatch('change');
              resultAlert = alertify.success(_tcStr('Block updated'));
            }
          } else if (
            block.data.name !== portInfo.name ||
            block.data.virtual !== virtual ||
            block.data.clock !== clock
          ) {
            var size = block.data.pins ? block.data.pins.length : 1;
            oldSize = block.data.virtual ? 1 : size;
            newSize = virtual ? 1 : size;
            // Update block position when size changes
            offset = 16 * (oldSize - newSize);
            // Edit block
            graph.startBatch('change');
            var data = utils.clone(block.data);
            data.name = portInfo.name;
            data.virtual = virtual;
            data.clock = clock;
            cellView.model.set('data', data, {
              translateBy: cellView.model.id,
              tx: 0,
              ty: -offset,
            });
            cellView.model.translate(0, offset);
            graph.stopBatch('change');
            cellView.apply();
            resultAlert = alertify.success(_tcStr('Block updated'));
          }
        } else {
          evt.cancel = true;
          resultAlert = alertify.warning(
            _tcStr('Wrong block name {{name}}', {name: label})
          );
        }
      });
    }

    function editBasicOutput(cellView, callback) {
      var graph = cellView.paper.model;
      var block = cellView.model.attributes;
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Update the block name'),
          value: block.data.name + (block.data.range || ''),
        },
        {
          type: 'checkbox',
          label: _tcStr('FPGA pin'),
          value: !block.data.virtual,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var oldSize,
          newSize,
          offset = 0;
        var label = values[0];
        var virtual = !values[1];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var portInfo = utils.parsePortLabel(
          label,
          common.PATTERN_GLOBAL_PORT_LABEL
        );
        if (portInfo) {
          evt.cancel = false;
          if ((block.data.range || '') !== (portInfo.rangestr || '')) {
            var pins = getPins(portInfo);
            oldSize = block.data.pins ? block.data.pins.length : 1;
            oldSize = block.data.virtual ? 1 : oldSize;
            newSize = virtual ? 1 : pins ? pins.length : 1;
            // Update block position when size changes
            offset = 16 * (oldSize - newSize);
            // Create new block
            var blockInstance = {
              id: null,
              data: {
                name: portInfo.name,
                range: portInfo.rangestr,
                pins: pins,
                virtual: virtual,
              },
              type: block.blockType,
              position: {
                x: block.position.x,
                y: block.position.y + offset,
              },
            };
            if (callback) {
              graph.startBatch('change');
              callback(loadBasic(blockInstance));
              cellView.model.remove();
              graph.stopBatch('change');
              resultAlert = alertify.success(_tcStr('Block updated'));
            }
          } else if (
            block.data.name !== portInfo.name ||
            block.data.virtual !== virtual
          ) {
            var size = block.data.pins ? block.data.pins.length : 1;
            oldSize = block.data.virtual ? 1 : size;
            newSize = virtual ? 1 : size;
            // Update block position when size changes
            offset = 16 * (oldSize - newSize);
            // Edit block
            graph.startBatch('change');
            var data = utils.clone(block.data);
            data.name = portInfo.name;
            data.virtual = virtual;
            cellView.model.set('data', data, {
              translateBy: cellView.model.id,
              tx: 0,
              ty: -offset,
            });
            cellView.model.translate(0, offset);
            graph.stopBatch('change');
            cellView.apply();
            resultAlert = alertify.success(_tcStr('Block updated'));
          }
        } else {
          evt.cancel = true;
          resultAlert = alertify.warning(
            _tcStr('Wrong block name {{name}}', {name: label})
          );
        }
      });
    }

    function editBasicConstant(cellView) {
      var block = cellView.model.attributes;
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Update the block name'),
          value: block.data.name,
        },
        {
          type: 'checkbox',
          label: _tcStr('Local parameter'),
          value: block.data.local,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var label = values[0];
        var local = values[1];
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var paramInfo = utils.parseParamLabel(
          label,
          common.PATTERN_GLOBAL_PARAM_LABEL
        );
        if (paramInfo) {
          var name = paramInfo.name;
          evt.cancel = false;
          if (block.data.name !== name || block.data.local !== local) {
            // Edit block
            var data = utils.clone(block.data);
            data.name = name;
            data.local = local;
            cellView.model.set('data', data);
            cellView.apply();
            resultAlert = alertify.success(_tcStr('Block updated'));
          }
        } else {
          evt.cancel = true;
          resultAlert = alertify.warning(
            _tcStr('Wrong block name {{name}}', {name: label})
          );
          return;
        }
      });
    }

    function editBasicMemory(cellView) {
      var block = cellView.model.attributes;
      var formSpecs = [
        {
          type: 'text',
          title: _tcStr('Update the block name'),
          value: block.data.name,
        },
        {
          type: 'combobox',
          label: _tcStr('Address format'),
          value: block.data.format,
          options: [
            {value: 2, label: _tcStr('Binary')},
            {value: 10, label: _tcStr('Decimal')},
            {value: 16, label: _tcStr('Hexadecimal')},
          ],
        },
        {
          type: 'checkbox',
          label: _tcStr('Local parameter'),
          value: block.data.local,
        },
      ];
      utils.renderForm(formSpecs, function (evt, values) {
        var label = values[0];
        var local = values[2];
        var format = parseInt(values[1]);
        if (resultAlert) {
          resultAlert.dismiss(false);
        }
        // Validate values
        var paramInfo = utils.parseParamLabel(
          label,
          common.PATTERN_GLOBAL_PARAM_LABEL
        );
        if (paramInfo) {
          var name = paramInfo.name;
          evt.cancel = false;
          if (
            block.data.name !== name ||
            block.data.local !== local ||
            block.data.format !== format
          ) {
            // Edit block
            var data = utils.clone(block.data);
            data.name = name;
            data.local = local;
            data.format = format;
            cellView.model.set('data', data);
            cellView.apply();
            resultAlert = alertify.success(_tcStr('Block updated'));
          }
        } else {
          evt.cancel = true;
          resultAlert = alertify.warning(
            _tcStr('Wrong block name {{name}}', {name: label})
          );
          return;
        }
      });
    }

    function editBasicCode(cellView, callback) {
      var graph = cellView.paper.model;
      var block = cellView.model.attributes;
      var blockInstance = {
        id: block.id,
        data: utils.clone(block.data),
        type: 'basic.code',
        position: block.position,
        size: block.size,
      };
      if (resultAlert) {
        resultAlert.dismiss(false);
      }
      newBasicCode(function (cells) {
        if (callback) {
          var cell = cells[0];
          if (cell) {
            var connectedWires = graph.getConnectedLinks(cellView.model);
            graph.startBatch('change');
            cellView.model.remove();
            callback(cell);
            // Restore previous connections
            for (var w in connectedWires) {
              var wire = connectedWires[w];
              var size = wire.get('size');
              var source = wire.get('source');
              var target = wire.get('target');
              if (
                (source.id === cell.id &&
                  containsPort(source.port, size, cell.get('rightPorts'))) ||
                (target.id === cell.id &&
                  containsPort(target.port, size, cell.get('leftPorts')) &&
                  source.port !== 'constant-out' &&
                  source.port !== 'memory-out') ||
                (target.id === cell.id &&
                  containsPort(target.port, size, cell.get('topPorts')) &&
                  (source.port === 'constant-out' ||
                    source.port === 'memory-out'))
              ) {
                graph.addCell(wire);
              }
            }
            graph.stopBatch('change');
            resultAlert = alertify.success(_tcStr('Block updated'));
          }
        }
      }, blockInstance);
    }

    function containsPort(port, size, ports) {
      var found = false;
      for (var i in ports) {
        if (port === ports[i].name && size === ports[i].size) {
          found = true;
          break;
        }
      }
      return found;
    }

    function editBasicInfo(cellView) {
      var block = cellView.model.attributes;
      var data = utils.clone(block.data);
      // Toggle readonly
      data.readonly = !data.readonly;
      // Translate info content
      if (data.info && data.readonly) {
        data.text = _tcStr(data.info);
      }
      cellView.model.set('data', data);
      cellView.apply();
    }
  });