<commit_before>(function() {
  ////////////////////////////////////////////////////////////////////////////////////
  // Input elements
  ////////////////////////////////////////////////////////////////////////////////////
  /**
   * Base class of input elements.
   */
  function InputElement(option) {
    this.name = option.name;
    this.id = option.parentName + '_' + this.name;
    this.listener = option.listener;
    this.element = byId(this.id);

    this.element.addEventListener('change', this.listener);
  }

  /**
   * Collect this input value into target object.
   */
  InputElement.prototype.collect = function(target) {
    var value = this.element.value;

    if (value !== '') {
      if (this.element.type === 'number') {
        value = Number(value);
      }
      target[this.name] = this.mapValue(value);
    }
  };

  /**
   * Storategy to map value.
   */
  InputElement.prototype.mapValue = function(value) {
    return value;
  };

  /**
   * Restore value from source object.
   */
  InputElement.prototype.restore = function(source) {
    if (this.name in source && source[this.name] !== '') {
      this.element.value = source[this.name];
    }
  };

  // define inheritance relationship for InputElement
  _inherits(InputElement, TextBox);
  _inherits(InputElement, SelectBox);
  _inherits(InputElement, BooleanSelectBox);
  _inherits(InputElement, TemplateColorsOption);

  /**
   * <input type="text"> element.
   */
  function TextBox(option) {
    InputElement.call(this, option);
  }

  /**
   * <select> element.
   */
  function SelectBox(option) {
    InputElement.call(this, option);
  }

  /**
   * <select> element as primitive boolean value.
   */
  function BooleanSelectBox(option) {
    InputElement.call(this, option);
  }

  /**
   * Map value from String to boolean.
   */
  BooleanSelectBox.prototype.mapValue = function(value) {
    return value === 'true';
  };

  /**
   * For template.colors options element.
   */
  function TemplateColorsOption(option) {
    InputElement.call(this, option);
  }

  /**
   * Map value to color code array.
   */
  TemplateColorsOption.prototype.mapValue = function(value) {
    return value.replace(/ */g, '').split(',');
  };

  ////////////////////////////////////////////////////////////////////////////////////
  // Options
  ////////////////////////////////////////////////////////////////////////////////////
  /**
   * Root option.
   */
  function GitGraphOption(option) {
    var _self = this;
    var _listener = option.listener;
    var _name = 'option';
    var _base = new BaseOption({parentName: _name, listener: _listener});
    var _template = new TemplateOption({parentName: _name, listener: _listener});
    var _branchOptions = {};

    /**
     * Dump option values as is.
     */
    this.dump = function() {
      // collect base options
      var option = {};
      _base.collect(option);

      // collect template options
      _template.collect(option);

      // collect branch options
      option.branch = {};
      for (var branchName in _branchOptions) {
        _branchOptions[branchName].collect(option.branch);
      }

      return option;
    };

    /**
     * Collect option values for GitGraph parameter.
     */
    this.collectOpiton = function() {
      // collect base options
      var baseOption = {};
      _base.collect(baseOption);

      // collect template options
      var option = baseOption.base;
      option.template = new GitGraph.Template().get(baseOption.base.template);
      _template.collect(option);

      // collect branch options
      option.branch = {};
      for (var branchName in _branchOptions) {
        _branchOptions[branchName].collect(option.branch);
      }

      return option;
    };

    /**
     * Restore option values from source object.
     */
    this.restore = function(source) {
      _base.restore(source);
      _template.restore(source);
      for (var branchName in source.branch) {
        this.addBranch(branchName);
        _branchOptions[branchName].restore(source.branch);
      }
    };

    /**
     * Add new branch option.
     */
    this.addBranch = function(branchName) {
      // skip if aleady exists
      if (byId(branchName)) {
        return;
      }

      // create html
      var html = _createHtml(branchName);
      _appendHtml(branchName, html);
      _initRemoveButton(branchName);

      // create Option instance
      _branchOptions[branchName] = new BranchOption({
        parentName: _name + '_branch',
        listener: _listener,
        branchName: branchName
      });
    };

    function _createHtml(branchName) {
      var html = byId('newBranchOptionTemplate').innerText;
      return html.replace(/\${branchName}/g, branchName);
    }

    function _appendHtml(branchName, html) {
      var div = document.createElement('div');
      div.innerHTML = html;
      div.id = branchName + '_options';

      byId('newBranchesTarget').appendChild(div);
    }

    function _initRemoveButton(branchName) {
      onClick(branchName + '_remove', function() {
        _removeBranch(branchName);
        _listener();
      });
    }

    function _removeBranch(branchName) {
      var branchArea = byId(branchName + "_options");
      byId('newBranchesTarget').removeChild(branchArea);

      delete _branchOptions[branchName];
    };
  }

  /**
   * Base of option classes.
   *
   * Option class has some children.
   * Children include other AbstractOptions or InputElements.
   */
  function AbstractOption(option) {
    this.parentName = option.parentName;
    this.listener = option.listener;
    this.children = [];
  }

  /**
   * Collect option values from children and set these into target object.
   */
  AbstractOption.prototype.collect = function(target) {
    var _self = this;

    if (!(_self.name in target)) {
      target[_self.name] = {};
    }

    _self.children.forEach(function(child) {
      child.collect(target[_self.name]);
    });
  };

  /**
   * Restore values into children from source object.
   */
  AbstractOption.prototype.restore = function(source) {
    var _self = this;

    if (!(_self.name in source)) {
      return;
    }

    _self.children.forEach(function(child) {
      child.restore(source[_self.name]);
    });
  };

  /**
   * Initialize name and idPrefix property.
   * idPrefix is important to create child instances.
   */
  AbstractOption.prototype.initName = function(name) {
    this.name = name;
    this.idPrefix = this.parentName + '_' + this.name;
  };

  /**
   * Initialize children by child element builders.
   */
  AbstractOption.prototype.initChildren = function(childBuilders) {
    for (var i=0; i<childBuilders.length; i++) {
      var child = childBuilders[i].newInstance({
        parentName: this.idPrefix,
        listener: this.listener
      });

      this.children.push(child);
    }
  };

  /**
   * Create child builder object.
   * Child is InputElement or Option class.
   */
  function child(constructorFunction, name) {
    return {
      newInstance: function(option) {
        option.name = name; // only child is InputElement instance.
        return new constructorFunction(option);
      }
    };
  }

  // define inheritance relationship for AbstractOption
  _inherits(AbstractOption, BaseOption);
  _inherits(AbstractOption, TemplateOption);
  _inherits(AbstractOption, TemplateArrowOption);
  _inherits(AbstractOption, TemplateBranchOption);
  _inherits(AbstractOption, TemplateCommitOption);
  _inherits(AbstractOption, TemplateDotOption);
  _inherits(AbstractOption, TemplateMessageOption);
  _inherits(AbstractOption, BranchOption);
  _inherits(AbstractOption, BranchCommitDefaultOption);

  /**
   * base option definition.
   */
  function BaseOption(option) {
    AbstractOption.call(this, option);

    this.initName('base');
    this.initChildren([
      child(SelectBox, 'template'),
      child(BooleanSelectBox, 'reverseArrow'),
      child(SelectBox, 'orientation'),
      child(SelectBox, 'mode'),
      child(TextBox, 'author')
    ]);
  }

  /**
   * template option definition.
   */
  function TemplateOption(option) {
    AbstractOption.call(this, option);

    this.initName('template');
    this.initChildren([
      child(TemplateColorsOption, 'colors'),
      child(TemplateArrowOption),
      child(TemplateBranchOption),
      child(TemplateCommitOption)
    ]);
  }

  /**
   * template.arrow option definition.
   */
  function TemplateArrowOption(option) {
    AbstractOption.call(this, option);

    this.initName('arrow');
    this.initChildren([
      child(TextBox, 'color'),
      child(TextBox, 'size'),
      child(TextBox, 'offset')
    ]);
  }

  /**
   * template.branch option definition.
   */
  function TemplateBranchOption(option) {
    AbstractOption.call(this, option);

    this.initName('branch');
    this.initChildren([
      child(TextBox, 'color'),
      child(TextBox, 'lineWidth'),
      child(SelectBox, 'mergeStyle'),
      child(TextBox, 'spacingX'),
      child(TextBox, 'spacingY'),
      child(BooleanSelectBox, 'showLabel'),
      child(TextBox, 'labelColor'),
      child(TextBox, 'labelFont')
    ]);
  }

  /**
   * template.commit option definition.
   */
  function TemplateCommitOption(option) {
    AbstractOption.call(this, option);

    this.initName('commit');
    this.initChildren([
      child(TextBox, 'spacingX'),
      child(TextBox, 'spacingY'),
      child(TextBox, 'widthExtension'),
      child(TextBox, 'color'),
      child(TemplateDotOption),
      child(TemplateMessageOption)
    ]);
  }

  /**
   * template.dot option definition.
   */
  function TemplateDotOption(option) {
    AbstractOption.call(this, option);

    this.initName('dot');
    this.initChildren([
      child(TextBox, 'color'),
      child(TextBox, 'size'),
      child(TextBox, 'strokeWidth'),
      child(TextBox, 'strokeColor')
    ]);
  }

  /**
   * template.message option definition.
   */
  function TemplateMessageOption(option) {
    AbstractOption.call(this, option);

    this.initName('message');
    this.initChildren([
      child(TextBox, 'color'),
      child(BooleanSelectBox, 'display'),
      child(BooleanSelectBox, 'displayAuthor'),
      child(BooleanSelectBox, 'displayBranch'),
      child(BooleanSelectBox, 'displayHash'),
      child(TextBox, 'font'),
      child(BooleanSelectBox, 'shouldDisplayTooltipsInCompactMode')
    ]);
  }

  /**
   * branch option definition.
   */
  function BranchOption(option) {
    AbstractOption.call(this, option);
    this.branchName = option.branchName;

    this.initName(this.branchName);
    this.initChildren([
      child(TextBox, 'color'),
      child(TextBox, 'lineWidth'),
      child(BooleanSelectBox, 'showLabel'),
      child(BranchCommitDefaultOption)
    ]);
  }

  /**
   * branch.commitDefaultOptions option definition.
   */
  function BranchCommitDefaultOption(option) {
    AbstractOption.call(this, option);

    this.initName('commitDefaultOptions');
    this.initChildren([
      child(TextBox, 'color'),
      child(TextBox, 'messageColor'),
      child(TextBox, 'labelColor'),
      child(TextBox, 'dotColor')
    ]);
  }

  ////////////////////////////////////////////////////////////////////////////////////
  // Other classes
  ////////////////////////////////////////////////////////////////////////////////////
  /**
   * Control graph canvas.
   */
  function Graph(gitGraphOption) {
    var _target = byId('target');

    this.draw = function(commands) {
      _refreshCanvas();

      var option = gitGraphOption.collectOpiton();
      var git = new GitGraphWrapperExtention(option);

      git.defaultOptions({
        branch: option.branch
      });

      for (var i=0; i<commands.length; i++) {
        var command = commands[i];
        command.invoke(git);
      }
    };

    function _refreshCanvas() {
      _removeCurrentCanvas();
      var canvas = _createCanvas();
      _target.appendChild(canvas);
    }

    function _removeCurrentCanvas() {
      if (_target.childNodes.length !== 0) {
        _target.removeChild(_target.firstChild);
      }
    }

    function _createCanvas() {
      var canvas = document.createElement('canvas');
      canvas.id = 'gitGraph';
      return canvas;
    }
  }

  /**
   * Parse user input as git command.
   */
  function Command(line) {
    var _elements = _normalize(line);
    var _method = _elements[0];
    var _arguments = _elements.slice(1);

    this.invoke = function(git) {
      if (!(typeof git[_method] === 'function')) {
        return;
      }

      try {
        git[_method].apply(git, _arguments);
      } catch (e) {
        console.warn(e);
      }
    };

    function _normalize(line) {
      var elements = line.split(' ');
      var result = [];

      for (var i=0; i<elements.length; i++) {
        var element = elements[i]

        // remove empty strings
        if (element !== '') {
          result.push(element);
        }
      }

      return result;
    }
  }

  /**
   * Control textarea editor.
   */
  function Editor(option) {
    var _editor = byId('editor');
    var _timeoutKey;

    _setListener(option.listener);

    this.getCommands = function() {
      var commands = [];

      var lines = _editor.value.split('\n');
      for (var i=0; i<lines.length; i++) {
        var line = lines[i].trim();

        if (line.length !== 0) {
          commands.push(new Command(line));
        }
      }

      return commands;
    };

    this.getRawText = function() {
      return _editor.value;
    };

    this.setRawText = function(value) {
      _editor.value = value;
    };

    function _setListener(listener) {
      _editor.addEventListener('keyup', function() {
        if (_timeoutKey) {
          clearTimeout(_timeoutKey);
        }

        _timeoutKey = setTimeout(function() {
          try {
            listener();
          } finally {
            _timeoutKey = null;
          }
        }, 500);
      });
    }
  }

  /**
   * To save and restore options.
   */
  function Storage() {
    var KEY = "gitgraph.storage";
    var _text;
    var _option;

    this.save = function(option, editor) {
      var obj = {
        text: editor.getRawText(),
        option: option.dump()
      };

      localStorage.setItem(KEY, JSON.stringify(obj));
    };

    this.load = function() {
      var json = localStorage.getItem(KEY);
      if (typeof json === 'string') {
        var obj = JSON.parse(json);
        _text = obj.text;
        _option = obj.option;
      } else {
        _text = '';
        _option = {};
      }
    };

    this.getText = function() {
      return _text;
    };

    this.getOption = function() {
      return _option;
    };

    this.remove = function() {
      localStorage.removeItem(KEY);
    };
  }

  /**
   * Controller of GitGraphEditor.
   */
  function MainController() {
    var _gitGraphOption = new GitGraphOption({listener: _draw});
    var _editor = new Editor({listener: _draw});
    var _storage = new Storage();
    var _graph = new Graph(_gitGraphOption);

    function _draw() {
      var commands = _editor.getCommands();
      _graph.draw(commands);

      _storage.save(_gitGraphOption, _editor);
    }

    /**
     * start program.
     */
    this.start = function() {
      _storage.load();
      _gitGraphOption.restore(_storage.getOption());
      _editor.setRawText(_storage.getText());

      onClick('addNewBranchOptionButton', function() {
        var branchName = byId('newBranchName').value;
        _gitGraphOption.addBranch(branchName);
      });

      _draw();
    };
  }

  ////////////////////////////////////////////////////////////////////////////////////
  // Global functions
  ////////////////////////////////////////////////////////////////////////////////////
  /**
   * Define class inheritance.
   */
  function _inherits(SuperClass, SubClass) {
      var f = function() {};
      f.prototype = SuperClass.prototype;
      SubClass.prototype = new f();
      SubClass.prototype.constructor = SubClass;
  }

  /**
   * Synonym of document.getElementById()
   */
  function byId(id) {
    return document.getElementById(id);
  }

  /**
   * Add click event listener.
   */
  function onClick(id, callback) {
    byId(id).addEventListener('click', callback);
  }

  ////////////////////////////////////////////////////////////////////////////////////
  // Start
  ////////////////////////////////////////////////////////////////////////////////////
  new MainController().start();
})();
<commit_msg>remove underscore not private instance method<commit_after>(function() {
  ////////////////////////////////////////////////////////////////////////////////////
  // Input elements
  ////////////////////////////////////////////////////////////////////////////////////
  /**
   * Base class of input elements.
   */
  function InputElement(option) {
    this.name = option.name;
    this.id = option.parentName + '_' + this.name;
    this.listener = option.listener;
    this.element = byId(this.id);

    this.element.addEventListener('change', this.listener);
  }

  /**
   * Collect this input value into target object.
   */
  InputElement.prototype.collect = function(target) {
    var value = this.element.value;

    if (value !== '') {
      if (this.element.type === 'number') {
        value = Number(value);
      }
      target[this.name] = this.mapValue(value);
    }
  };

  /**
   * Storategy to map value.
   */
  InputElement.prototype.mapValue = function(value) {
    return value;
  };

  /**
   * Restore value from source object.
   */
  InputElement.prototype.restore = function(source) {
    if (this.name in source && source[this.name] !== '') {
      this.element.value = source[this.name];
    }
  };

  // define inheritance relationship for InputElement
  inherits(InputElement, TextBox);
  inherits(InputElement, SelectBox);
  inherits(InputElement, BooleanSelectBox);
  inherits(InputElement, TemplateColorsOption);

  /**
   * <input type="text"> element.
   */
  function TextBox(option) {
    InputElement.call(this, option);
  }

  /**
   * <select> element.
   */
  function SelectBox(option) {
    InputElement.call(this, option);
  }

  /**
   * <select> element as primitive boolean value.
   */
  function BooleanSelectBox(option) {
    InputElement.call(this, option);
  }

  /**
   * Map value from String to boolean.
   */
  BooleanSelectBox.prototype.mapValue = function(value) {
    return value === 'true';
  };

  /**
   * For template.colors options element.
   */
  function TemplateColorsOption(option) {
    InputElement.call(this, option);
  }

  /**
   * Map value to color code array.
   */
  TemplateColorsOption.prototype.mapValue = function(value) {
    return value.replace(/ */g, '').split(',');
  };

  ////////////////////////////////////////////////////////////////////////////////////
  // Options
  ////////////////////////////////////////////////////////////////////////////////////
  /**
   * Root option.
   */
  function GitGraphOption(option) {
    var _self = this;
    var _listener = option.listener;
    var _name = 'option';
    var _base = new BaseOption({parentName: _name, listener: _listener});
    var _template = new TemplateOption({parentName: _name, listener: _listener});
    var _branchOptions = {};

    /**
     * Dump option values as is.
     */
    this.dump = function() {
      // collect base options
      var option = {};
      _base.collect(option);

      // collect template options
      _template.collect(option);

      // collect branch options
      option.branch = {};
      for (var branchName in _branchOptions) {
        _branchOptions[branchName].collect(option.branch);
      }

      return option;
    };

    /**
     * Collect option values for GitGraph parameter.
     */
    this.collectOpiton = function() {
      // collect base options
      var baseOption = {};
      _base.collect(baseOption);

      // collect template options
      var option = baseOption.base;
      option.template = new GitGraph.Template().get(baseOption.base.template);
      _template.collect(option);

      // collect branch options
      option.branch = {};
      for (var branchName in _branchOptions) {
        _branchOptions[branchName].collect(option.branch);
      }

      return option;
    };

    /**
     * Restore option values from source object.
     */
    this.restore = function(source) {
      _base.restore(source);
      _template.restore(source);
      for (var branchName in source.branch) {
        this.addBranch(branchName);
        _branchOptions[branchName].restore(source.branch);
      }
    };

    /**
     * Add new branch option.
     */
    this.addBranch = function(branchName) {
      // skip if aleady exists
      if (byId(branchName)) {
        return;
      }

      // create html
      var html = _createHtml(branchName);
      _appendHtml(branchName, html);
      _initRemoveButton(branchName);

      // create Option instance
      _branchOptions[branchName] = new BranchOption({
        parentName: _name + '_branch',
        listener: _listener,
        branchName: branchName
      });
    };

    function _createHtml(branchName) {
      var html = byId('newBranchOptionTemplate').innerText;
      return html.replace(/\${branchName}/g, branchName);
    }

    function _appendHtml(branchName, html) {
      var div = document.createElement('div');
      div.innerHTML = html;
      div.id = branchName + '_options';

      byId('newBranchesTarget').appendChild(div);
    }

    function _initRemoveButton(branchName) {
      onClick(branchName + '_remove', function() {
        _removeBranch(branchName);
        _listener();
      });
    }

    function _removeBranch(branchName) {
      var branchArea = byId(branchName + "_options");
      byId('newBranchesTarget').removeChild(branchArea);

      delete _branchOptions[branchName];
    };
  }

  /**
   * Base of option classes.
   *
   * Option class has some children.
   * Children include other AbstractOptions or InputElements.
   */
  function AbstractOption(option) {
    this.parentName = option.parentName;
    this.listener = option.listener;
    this.children = [];
  }

  /**
   * Collect option values from children and set these into target object.
   */
  AbstractOption.prototype.collect = function(target) {
    var _self = this;

    if (!(_self.name in target)) {
      target[_self.name] = {};
    }

    _self.children.forEach(function(child) {
      child.collect(target[_self.name]);
    });
  };

  /**
   * Restore values into children from source object.
   */
  AbstractOption.prototype.restore = function(source) {
    var _self = this;

    if (!(_self.name in source)) {
      return;
    }

    _self.children.forEach(function(child) {
      child.restore(source[_self.name]);
    });
  };

  /**
   * Initialize name and idPrefix property.
   * idPrefix is important to create child instances.
   */
  AbstractOption.prototype.initName = function(name) {
    this.name = name;
    this.idPrefix = this.parentName + '_' + this.name;
  };

  /**
   * Initialize children by child element builders.
   */
  AbstractOption.prototype.initChildren = function(childBuilders) {
    for (var i=0; i<childBuilders.length; i++) {
      var child = childBuilders[i].newInstance({
        parentName: this.idPrefix,
        listener: this.listener
      });

      this.children.push(child);
    }
  };

  /**
   * Create child builder object.
   * Child is InputElement or Option class.
   */
  function child(constructorFunction, name) {
    return {
      newInstance: function(option) {
        option.name = name; // only child is InputElement instance.
        return new constructorFunction(option);
      }
    };
  }

  // define inheritance relationship for AbstractOption
  inherits(AbstractOption, BaseOption);
  inherits(AbstractOption, TemplateOption);
  inherits(AbstractOption, TemplateArrowOption);
  inherits(AbstractOption, TemplateBranchOption);
  inherits(AbstractOption, TemplateCommitOption);
  inherits(AbstractOption, TemplateDotOption);
  inherits(AbstractOption, TemplateMessageOption);
  inherits(AbstractOption, BranchOption);
  inherits(AbstractOption, BranchCommitDefaultOption);

  /**
   * base option definition.
   */
  function BaseOption(option) {
    AbstractOption.call(this, option);

    this.initName('base');
    this.initChildren([
      child(SelectBox, 'template'),
      child(BooleanSelectBox, 'reverseArrow'),
      child(SelectBox, 'orientation'),
      child(SelectBox, 'mode'),
      child(TextBox, 'author')
    ]);
  }

  /**
   * template option definition.
   */
  function TemplateOption(option) {
    AbstractOption.call(this, option);

    this.initName('template');
    this.initChildren([
      child(TemplateColorsOption, 'colors'),
      child(TemplateArrowOption),
      child(TemplateBranchOption),
      child(TemplateCommitOption)
    ]);
  }

  /**
   * template.arrow option definition.
   */
  function TemplateArrowOption(option) {
    AbstractOption.call(this, option);

    this.initName('arrow');
    this.initChildren([
      child(TextBox, 'color'),
      child(TextBox, 'size'),
      child(TextBox, 'offset')
    ]);
  }

  /**
   * template.branch option definition.
   */
  function TemplateBranchOption(option) {
    AbstractOption.call(this, option);

    this.initName('branch');
    this.initChildren([
      child(TextBox, 'color'),
      child(TextBox, 'lineWidth'),
      child(SelectBox, 'mergeStyle'),
      child(TextBox, 'spacingX'),
      child(TextBox, 'spacingY'),
      child(BooleanSelectBox, 'showLabel'),
      child(TextBox, 'labelColor'),
      child(TextBox, 'labelFont')
    ]);
  }

  /**
   * template.commit option definition.
   */
  function TemplateCommitOption(option) {
    AbstractOption.call(this, option);

    this.initName('commit');
    this.initChildren([
      child(TextBox, 'spacingX'),
      child(TextBox, 'spacingY'),
      child(TextBox, 'widthExtension'),
      child(TextBox, 'color'),
      child(TemplateDotOption),
      child(TemplateMessageOption)
    ]);
  }

  /**
   * template.dot option definition.
   */
  function TemplateDotOption(option) {
    AbstractOption.call(this, option);

    this.initName('dot');
    this.initChildren([
      child(TextBox, 'color'),
      child(TextBox, 'size'),
      child(TextBox, 'strokeWidth'),
      child(TextBox, 'strokeColor')
    ]);
  }

  /**
   * template.message option definition.
   */
  function TemplateMessageOption(option) {
    AbstractOption.call(this, option);

    this.initName('message');
    this.initChildren([
      child(TextBox, 'color'),
      child(BooleanSelectBox, 'display'),
      child(BooleanSelectBox, 'displayAuthor'),
      child(BooleanSelectBox, 'displayBranch'),
      child(BooleanSelectBox, 'displayHash'),
      child(TextBox, 'font'),
      child(BooleanSelectBox, 'shouldDisplayTooltipsInCompactMode')
    ]);
  }

  /**
   * branch option definition.
   */
  function BranchOption(option) {
    AbstractOption.call(this, option);
    this.branchName = option.branchName;

    this.initName(this.branchName);
    this.initChildren([
      child(TextBox, 'color'),
      child(TextBox, 'lineWidth'),
      child(BooleanSelectBox, 'showLabel'),
      child(BranchCommitDefaultOption)
    ]);
  }

  /**
   * branch.commitDefaultOptions option definition.
   */
  function BranchCommitDefaultOption(option) {
    AbstractOption.call(this, option);

    this.initName('commitDefaultOptions');
    this.initChildren([
      child(TextBox, 'color'),
      child(TextBox, 'messageColor'),
      child(TextBox, 'labelColor'),
      child(TextBox, 'dotColor')
    ]);
  }

  ////////////////////////////////////////////////////////////////////////////////////
  // Other classes
  ////////////////////////////////////////////////////////////////////////////////////
  /**
   * Control graph canvas.
   */
  function Graph(gitGraphOption) {
    var _target = byId('target');

    this.draw = function(commands) {
      _refreshCanvas();

      var option = gitGraphOption.collectOpiton();
      var git = new GitGraphWrapperExtention(option);

      git.defaultOptions({
        branch: option.branch
      });

      for (var i=0; i<commands.length; i++) {
        var command = commands[i];
        command.invoke(git);
      }
    };

    function _refreshCanvas() {
      _removeCurrentCanvas();
      var canvas = _createCanvas();
      _target.appendChild(canvas);
    }

    function _removeCurrentCanvas() {
      if (_target.childNodes.length !== 0) {
        _target.removeChild(_target.firstChild);
      }
    }

    function _createCanvas() {
      var canvas = document.createElement('canvas');
      canvas.id = 'gitGraph';
      return canvas;
    }
  }

  /**
   * Parse user input as git command.
   */
  function Command(line) {
    var _elements = _normalize(line);
    var _method = _elements[0];
    var _arguments = _elements.slice(1);

    this.invoke = function(git) {
      if (!(typeof git[_method] === 'function')) {
        return;
      }

      try {
        git[_method].apply(git, _arguments);
      } catch (e) {
        console.warn(e);
      }
    };

    function _normalize(line) {
      var elements = line.split(' ');
      var result = [];

      for (var i=0; i<elements.length; i++) {
        var element = elements[i]

        // remove empty strings
        if (element !== '') {
          result.push(element);
        }
      }

      return result;
    }
  }

  /**
   * Control textarea editor.
   */
  function Editor(option) {
    var _editor = byId('editor');
    var _timeoutKey;

    _setListener(option.listener);

    this.getCommands = function() {
      var commands = [];

      var lines = _editor.value.split('\n');
      for (var i=0; i<lines.length; i++) {
        var line = lines[i].trim();

        if (line.length !== 0) {
          commands.push(new Command(line));
        }
      }

      return commands;
    };

    this.getRawText = function() {
      return _editor.value;
    };

    this.setRawText = function(value) {
      _editor.value = value;
    };

    function _setListener(listener) {
      _editor.addEventListener('keyup', function() {
        if (_timeoutKey) {
          clearTimeout(_timeoutKey);
        }

        _timeoutKey = setTimeout(function() {
          try {
            listener();
          } finally {
            _timeoutKey = null;
          }
        }, 500);
      });
    }
  }

  /**
   * To save and restore options.
   */
  function Storage() {
    var KEY = "gitgraph.storage";
    var _text;
    var _option;

    this.save = function(option, editor) {
      var obj = {
        text: editor.getRawText(),
        option: option.dump()
      };

      localStorage.setItem(KEY, JSON.stringify(obj));
    };

    this.load = function() {
      var json = localStorage.getItem(KEY);
      if (typeof json === 'string') {
        var obj = JSON.parse(json);
        _text = obj.text;
        _option = obj.option;
      } else {
        _text = '';
        _option = {};
      }
    };

    this.getText = function() {
      return _text;
    };

    this.getOption = function() {
      return _option;
    };

    this.remove = function() {
      localStorage.removeItem(KEY);
    };
  }

  /**
   * Controller of GitGraphEditor.
   */
  function MainController() {
    var _gitGraphOption = new GitGraphOption({listener: _draw});
    var _editor = new Editor({listener: _draw});
    var _storage = new Storage();
    var _graph = new Graph(_gitGraphOption);

    function _draw() {
      var commands = _editor.getCommands();
      _graph.draw(commands);

      _storage.save(_gitGraphOption, _editor);
    }

    /**
     * start program.
     */
    this.start = function() {
      _storage.load();
      _gitGraphOption.restore(_storage.getOption());
      _editor.setRawText(_storage.getText());

      onClick('addNewBranchOptionButton', function() {
        var branchName = byId('newBranchName').value;
        _gitGraphOption.addBranch(branchName);
      });

      _draw();
    };
  }

  ////////////////////////////////////////////////////////////////////////////////////
  // Global functions
  ////////////////////////////////////////////////////////////////////////////////////
  /**
   * Define class inheritance.
   */
  function inherits(SuperClass, SubClass) {
      var f = function() {};
      f.prototype = SuperClass.prototype;
      SubClass.prototype = new f();
      SubClass.prototype.constructor = SubClass;
  }

  /**
   * Synonym of document.getElementById()
   */
  function byId(id) {
    return document.getElementById(id);
  }

  /**
   * Add click event listener.
   */
  function onClick(id, callback) {
    byId(id).addEventListener('click', callback);
  }

  ////////////////////////////////////////////////////////////////////////////////////
  // Start
  ////////////////////////////////////////////////////////////////////////////////////
  new MainController().start();
})();
<|endoftext|>


<commit_before>/** @namespace **/
var XML3D = XML3D || {};

/** @define {string} */
XML3D.version = '%VERSION%';
/** @const */
XML3D.xml3dNS = 'http://www.xml3d.org/2009/xml3d';
/** @const */
XML3D.xhtmlNS = 'http://www.w3.org/1999/xhtml';
/** @const */
XML3D.webglNS = 'http://www.xml3d.org/2009/xml3d/webgl';
XML3D._xml3d = document.createElementNS(XML3D.xml3dNS, "xml3d");
XML3D._native = !!XML3D._xml3d.style;
XML3D._parallel = true;

XML3D.extend = function (a, b) {
    for ( var prop in b ) {
        if ( b[prop] === undefined ) {
            delete a[prop];
        } else if ( prop !== "constructor" || a !== window ) {
            a[prop] = b[prop];
        }
    }
    return a;
};

XML3D.createClass = function(ctor, parent, methods) {
    methods = methods || {};
    if (parent) {
        var F = function() {};
        F.prototype = parent.prototype;
        ctor.prototype = new F();
        ctor.prototype.constructor = ctor;
        ctor.superclass = parent.prototype;
    }
    for (var m in methods) {
        ctor.prototype[m] = methods[m];
    }
    return ctor;
};
(function() {
    var onload = function() {

        var debug = XML3D.debug.setup();
        debug && XML3D.debug.logInfo("xml3d.js version: " + XML3D.version);

        // Find all the XML3D tags in the document
        var xml3ds = document.getElementsByTagNameNS(XML3D.xml3dNS, 'xml3d');
        xml3ds = Array.map(xml3ds, function(n) { return n; });

        debug && XML3D.debug.logInfo("Found " + xml3ds.length + " xml3d nodes...");

        if (xml3ds.length) {
            if (XML3D._native) {
                debug && XML3D.debug.logInfo("Using native implementation.");
                return;
            }
        }

        if (!(XML3D.webgl && XML3D.webgl.supported()))
        {
            debug && XML3D.debug.logWarning("Could not initialise WebGL, sorry :-(");

            for(var i = 0; i < xml3ds.length; i++)
            {
                // Place xml3dElement inside an invisible div
                var hideDiv      = document.createElementNS(XML3D.xhtmlNS, 'div');
                var xml3dElement = xml3ds[i];

                xml3dElement.parentNode.insertBefore(hideDiv, xml3dElement);
                hideDiv.appendChild(xml3dElement);
                hideDiv.style.display = "none";

                var infoDiv = document.createElementNS(XML3D.xhtmlNS, 'div');
                infoDiv.setAttribute("class", xml3dElement.getAttribute("class"));
                infoDiv.setAttribute("style", xml3dElement.getAttribute("style"));
                infoDiv.style.border = "2px solid red";
                infoDiv.style.color  = "red";
                infoDiv.style.padding = "10px";
                infoDiv.style.backgroundColor = "rgba(255, 0, 0, 0.3)";

                var width = xml3dElement.getAttribute("width");
                if( width !== null)
                {
                    infoDiv.style.width = width;
                }

                var height = xml3dElement.getAttribute("height");
                if( height !== null)
                {
                    infoDiv.style.height = height;
                }

                var hElement = document.createElement("h3");
                var hTxt     = document.createTextNode("Your browser doesn't appear to support XML3D.");
                hElement.appendChild (hTxt);

                var pElement = document.createElement("p");
                pElement.appendChild(document.createTextNode("Please visit "));
                var link = document.createElement("a");
                link.setAttribute("href", "http://www.xml3d.org");
                link.appendChild(document.createTextNode("http://www.xml3d.org"));
                pElement.appendChild(link);
                pElement.appendChild(document.createTextNode(" to get information about browsers supporting XML3D."));
                infoDiv.appendChild (hElement);
                infoDiv.appendChild (pElement);

                hideDiv.parentNode.insertBefore(infoDiv, hideDiv);
            }

            return;
        }

        try {
            XML3D.config.configure(xml3ds);
        } catch (e) {
            debug && XML3D.debug.logError("Error initalizing interfaces: " + e);
        }
        try {
            XML3D.webgl.configure(xml3ds);
        } catch (e) {
            debug && XML3D.debug.logError("Error initalizing webgl: " + e);
        }

        var ready = (function(eventType) {
            var evt = null;
            if (document.createEvent) {
                evt = document.createEvent("Events");
                evt.initEvent(eventType, true, true);
                document.dispatchEvent(evt);
            } else if (document.createEventObject) {
                evt = document.createEventObject();
                document.fireEvent('on' + eventType, evt);
            }
        })('load');
    };
    var onunload = function() {
        if (XML3D.document)
            XML3D.document.onunload();
    };
    window.addEventListener('load', onload, false);
    window.addEventListener('unload', onunload, false);
    window.addEventListener('reload', onunload, false);

})();

<commit_msg>Made XML3D._parallel configureable from outside.<commit_after>/** @namespace **/
var XML3D = XML3D || {};

/** @define {string} */
XML3D.version = '%VERSION%';
/** @const */
XML3D.xml3dNS = 'http://www.xml3d.org/2009/xml3d';
/** @const */
XML3D.xhtmlNS = 'http://www.w3.org/1999/xhtml';
/** @const */
XML3D.webglNS = 'http://www.xml3d.org/2009/xml3d/webgl';
XML3D._xml3d = document.createElementNS(XML3D.xml3dNS, "xml3d");
XML3D._native = !!XML3D._xml3d.style;
XML3D._parallel = XML3D._parallel != undefined ? XML3D._parallel : false;

XML3D.extend = function (a, b) {
    for ( var prop in b ) {
        if ( b[prop] === undefined ) {
            delete a[prop];
        } else if ( prop !== "constructor" || a !== window ) {
            a[prop] = b[prop];
        }
    }
    return a;
};

XML3D.createClass = function(ctor, parent, methods) {
    methods = methods || {};
    if (parent) {
        var F = function() {};
        F.prototype = parent.prototype;
        ctor.prototype = new F();
        ctor.prototype.constructor = ctor;
        ctor.superclass = parent.prototype;
    }
    for (var m in methods) {
        ctor.prototype[m] = methods[m];
    }
    return ctor;
};
(function() {
    var onload = function() {

        var debug = XML3D.debug.setup();
        debug && XML3D.debug.logInfo("xml3d.js version: " + XML3D.version);

        // Find all the XML3D tags in the document
        var xml3ds = document.getElementsByTagNameNS(XML3D.xml3dNS, 'xml3d');
        xml3ds = Array.map(xml3ds, function(n) { return n; });

        debug && XML3D.debug.logInfo("Found " + xml3ds.length + " xml3d nodes...");

        if (xml3ds.length) {
            if (XML3D._native) {
                debug && XML3D.debug.logInfo("Using native implementation.");
                return;
            }
        }

        if (!(XML3D.webgl && XML3D.webgl.supported()))
        {
            debug && XML3D.debug.logWarning("Could not initialise WebGL, sorry :-(");

            for(var i = 0; i < xml3ds.length; i++)
            {
                // Place xml3dElement inside an invisible div
                var hideDiv      = document.createElementNS(XML3D.xhtmlNS, 'div');
                var xml3dElement = xml3ds[i];

                xml3dElement.parentNode.insertBefore(hideDiv, xml3dElement);
                hideDiv.appendChild(xml3dElement);
                hideDiv.style.display = "none";

                var infoDiv = document.createElementNS(XML3D.xhtmlNS, 'div');
                infoDiv.setAttribute("class", xml3dElement.getAttribute("class"));
                infoDiv.setAttribute("style", xml3dElement.getAttribute("style"));
                infoDiv.style.border = "2px solid red";
                infoDiv.style.color  = "red";
                infoDiv.style.padding = "10px";
                infoDiv.style.backgroundColor = "rgba(255, 0, 0, 0.3)";

                var width = xml3dElement.getAttribute("width");
                if( width !== null)
                {
                    infoDiv.style.width = width;
                }

                var height = xml3dElement.getAttribute("height");
                if( height !== null)
                {
                    infoDiv.style.height = height;
                }

                var hElement = document.createElement("h3");
                var hTxt     = document.createTextNode("Your browser doesn't appear to support XML3D.");
                hElement.appendChild (hTxt);

                var pElement = document.createElement("p");
                pElement.appendChild(document.createTextNode("Please visit "));
                var link = document.createElement("a");
                link.setAttribute("href", "http://www.xml3d.org");
                link.appendChild(document.createTextNode("http://www.xml3d.org"));
                pElement.appendChild(link);
                pElement.appendChild(document.createTextNode(" to get information about browsers supporting XML3D."));
                infoDiv.appendChild (hElement);
                infoDiv.appendChild (pElement);

                hideDiv.parentNode.insertBefore(infoDiv, hideDiv);
            }

            return;
        }

        try {
            XML3D.config.configure(xml3ds);
        } catch (e) {
            debug && XML3D.debug.logError("Error initalizing interfaces: " + e);
        }
        try {
            XML3D.webgl.configure(xml3ds);
        } catch (e) {
            debug && XML3D.debug.logError("Error initalizing webgl: " + e);
        }

        var ready = (function(eventType) {
            var evt = null;
            if (document.createEvent) {
                evt = document.createEvent("Events");
                evt.initEvent(eventType, true, true);
                document.dispatchEvent(evt);
            } else if (document.createEventObject) {
                evt = document.createEventObject();
                document.fireEvent('on' + eventType, evt);
            }
        })('load');
    };
    var onunload = function() {
        if (XML3D.document)
            XML3D.document.onunload();
    };
    window.addEventListener('load', onload, false);
    window.addEventListener('unload', onunload, false);
    window.addEventListener('reload', onunload, false);

})();

<|endoftext|>


<commit_before>'use strict'

var Seneca = require('seneca')
var Entities = require('seneca-entity')
var Mesh = require('seneca-mesh')
var Travis = require('../lib/travis')
var RedisStore = require('seneca-redis-store')

var envs = process.env
var opts = {
  seneca: {
    tag: envs.TRAVIS_TAG || 'nodezoo-travis'
  },
  travis: {
    token: envs.TRAVIS_TOKEN || 'NO_TOKEN',
    registry: envs.TRAVIS_REGISTRY || 'http://registry.npmjs.org/'
  },
  mesh: {
    auto: true,
    host: envs.TRAVIS_HOST || '127.0.0.1',
    bases: [envs.BASE_HOST || '127.0.0.1:39999'],
    listen: [
      {pin: 'role:travis,cmd:get', model: 'consume'},
      {pin: 'role:info,req:part', model: 'observe'}
    ]
  },
  isolated: {
    host: envs.TRAVIS_HOST || 'localhost',
    port: envs.TRAVIS_PORT || '8053'
  },
  redis: {
    host: envs.TRAVIS_REDIS_HOST || 'localhost',
    port: envs.TRAVIS_REDIS_PORT || '6379'
  }
}

var Service = Seneca(opts.seneca)

Service.use(Entities)

if (envs.TRAVIS_ISOLATED) {
  Service.listen(opts.isolated)
}
else {
  Service.use(Mesh, opts.mesh)
  Service.use(RedisStore, opts.redis)
}

Service.use(Travis, opts.travis)
<commit_msg>fix for env<commit_after>'use strict'

var Seneca = require('seneca')
var Entities = require('seneca-entity')
var Mesh = require('seneca-mesh')
var Travis = require('../lib/travis')
var RedisStore = require('seneca-redis-store')

var envs = process.env
var opts = {
  seneca: {
    tag: envs.TRAVIS_TAG || 'nodezoo-travis'
  },
  travis: {
    token: envs.TRAVIS_TOKEN || 'NO_TOKEN',
    registry: envs.TRAVIS_REGISTRY || 'http://registry.npmjs.org/'
  },
  mesh: {
    auto: true,
    host: envs.TRAVIS_HOST || '127.0.0.1',
    bases: [envs.BASE_HOST || '127.0.0.1:39999'],
    listen: [
      {pin: 'role:travis,cmd:get', model: 'consume', host: envs.TRAVIS_HOST || '127.0.0.1'},
      {pin: 'role:info,req:part', model: 'observe', host: envs.TRAVIS_HOST || '127.0.0.1'}
    ]
  },
  isolated: {
    host: envs.TRAVIS_HOST || 'localhost',
    port: envs.TRAVIS_PORT || '8053'
  },
  redis: {
    host: envs.TRAVIS_REDIS_HOST || 'localhost',
    port: envs.TRAVIS_REDIS_PORT || '6379'
  }
}

var Service = Seneca(opts.seneca)

Service.use(Entities)

if (envs.TRAVIS_ISOLATED) {
  Service.listen(opts.isolated)
}
else {
  Service.use(Mesh, opts.mesh)
  Service.use(RedisStore, opts.redis)
}

Service.use(Travis, opts.travis)
<|endoftext|>


<commit_before>import Ember from 'ember';
import layout from '../templates/components/paper-chips';

const { Component, isEmpty, isPresent, computed, observer, run } = Ember;

export default Component.extend({
  layout,
  tagName: 'md-chips',
  classNames: ['md-default-theme'],
  activeChip: -1,
  focusedElement: 'none',
  isFocused: computed('focusedElement', function() {
    if (this.get('focusedElement') === 'none') {
      return false;
    }

    return true;
  }),
  resetTimer: null,
  lastItemChosen: false,

  handleFocusChange: observer('focusedElement', 'activeChip', function() {
    let element = this.get('focusedElement');

    if (!this.get('isFocused')) {
      this.set('activeChip', -1);
    }

    if ((element === 'chips' && (this.get('activeChip') !== -1)) || element === 'input') {
      this.sendAction('focusIn', window.event);
    } else {
      this.sendAction('focusOut', window.event);
    }
  }),

  actions: {
    addItem(newItem) {
      if (this.get('requireMatch')) {
        // Don't add a new item - we're set to require a match.
        return;
      }

      if (isPresent(newItem)) {
        let item = newItem;

        if (isPresent(this.get('searchField'))) {
          item = {};
          item[this.get('searchField')] = newItem;
        }

        this.sendAction('addItem', item);
        this.set('newChipValue', '');

        if (isPresent(this.get('autocomplete'))) {
          // We have an autocomplete - reset it once it's closed itself.
          this.queueReset();
        }
      }
    },

    inputFocus(autocomplete) {
      let input = this.getInput();

      this.set('focusedElement', 'input');

      if (!this.get('content').length && !input.is(':focus')) {
        input.focus();
      } else {
        this.set('activeChip', -1);
      }

      // Keep track of the autocomplete, so we can force it to close when navigating to chips.
      if (isEmpty(this.get('autocomplete')) && input.is('.ember-paper-autocomplete-search-input')) {
        this.set('autocomplete', autocomplete);
      }

      // We don't want the autocomplete to open on focus - it'll open when the user starts typing.
      if (isPresent(autocomplete)) {
        autocomplete.actions.close();
      }
    },

    inputBlur() {
      if (this.focusMovingTo('.ember-power-select-option')) {
        // Focus has shifted to an item - don't mess with this event.
        return true;
      }

      if (this.get('lastItemChosen')) {
        // Last item has been chosen; select will be replaced with an input - ignore blur event.
        this.set('lastItemChosen', false);
        return true;
      }

      this.closeAutocomplete();

      if (!this.focusMovingTo('md-chips-wrap')) {
        this.set('focusedElement', 'none');
      }
    },

    chipsFocus() {
      this.set('focusedElement', 'chips');
    },

    chipsBlur() {
      if (!this.focusMovingTo(this.getInput())) {
        this.set('focusedElement', 'none');
      }
    },

    autocompleteChange(item) {
      if (item) {
        // Trigger onChange for the new item.
        this.sendAction('addItem', item);

        this.queueReset();

        // Track selection of last item if no match required.
        if (this.get('options').length === 1 && !this.get('requireMatch')) {
          this.set('lastItemChosen', true);
          this.set('autocomplete', null);
        }

        return true;
      }
    },

    searchTextChange(searchText, select) {
      // Close dropdown if search text is cleared by the user.
      if (isEmpty(searchText)) {
        select.actions.close();
      }
    },

    keyDown(event) {
      let input = this.getInput().get(0);
      if (!this.get('readOnly') && isEmpty(input.value) && isPresent(this.get('content'))) {
        this.keyboardNavigation(event);
        if (this.get('activeChip') >= 0) {
          this.closeAutocomplete();
        }
      } else {
        // Make sure we don't leave a chip focused while typing.
        this.set('activeChip', -1);
        this.set('focusedElement', 'input');
      }
    },

    noUnselected(old, event) {
      if (['Backspace', 'Delete', 'Del', 'ArrowLeft', 'Left', 'ArrowRight', 'Right'].includes(event.key)) {
        this.sendAction('keyDown', event);
      } else if (event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey) {
        // Reject printable key presses
        event.preventDefault();
        event.stopPropagation();
        return false;
      }

    }
  },

  keyboardNavigation({ key }) {
    // No text has been entered, but we have chips; cursor keys should select chips.
    let current = this.get('activeChip');
    let chips = this.get('content');
    let input = this.getInput();

    if (['ArrowLeft', 'Left'].includes(key) || (key === 'Backspace' && current === -1)) {
      if (current === -1) {
        input.blur();
        this.$('md-chips-wrap', this.element).focus();
        this.set('activeChip', chips.length - 1);
      } else if (current > 0) {
        this.decrementProperty('activeChip');
      }
    } else if (['ArrowRight', 'Right'].includes(key)) {
      if (current >= 0) {
        this.incrementProperty('activeChip');
      }

      if (this.get('activeChip') >= chips.length) {
        this.set('activeChip', -1);
        input.focus();
      }
    } else if (current >= 0 && ['Backspace', 'Delete', 'Del'].includes(key)) {
      this.sendAction('removeItem', chips[current]);
      if (current >= chips.length) {
        this.queueReset();
        this.set('activeChip', -1);
      }
    }
  },

  resetInput() {
    let select = this.get('autocomplete');
    let input = this.getInput();

    if (input.is('.ember-paper-autocomplete-search-input') && isPresent(select)) {
      // Reset the underlying ember-power-select so that it's ready for another selection.
      input.val('');
      select.actions.search('');

      // Close the dropdown after focusing the field.
      input.focus();
      select.actions.close();
    } else {
      input.focus();
    }

    this.set('focusedElement', 'input');
    this.set('resetTimer', null);
  },

  queueReset() {
    if (this.get('resetTimer')) {
      run.cancel(this.get('resetTimer'));
    }

    this.set('resetTimer', run.next(this, this.resetInput));
  },

  closeAutocomplete() {
    if (!isEmpty(this.get('autocomplete')) && !isEmpty(this.get('autocomplete').actions)) {
      this.get('autocomplete').actions.close();
    }
  },

  getInput() {
    return this.$('.md-chip-input-container input');
  },

  focusMovingTo(selector) {
    if (!isEmpty(event) && !isEmpty(event.relatedTarget) && this.$(event.relatedTarget).is(selector)) {
      return true;
    }

    return false;
  }
});
<commit_msg>Chips: pass event into focusMovingTo<commit_after>import Ember from 'ember';
import layout from '../templates/components/paper-chips';

const { Component, isEmpty, isPresent, computed, observer, run } = Ember;

export default Component.extend({
  layout,
  tagName: 'md-chips',
  classNames: ['md-default-theme'],
  activeChip: -1,
  focusedElement: 'none',
  isFocused: computed('focusedElement', function() {
    if (this.get('focusedElement') === 'none') {
      return false;
    }

    return true;
  }),
  resetTimer: null,
  lastItemChosen: false,

  handleFocusChange: observer('focusedElement', 'activeChip', function() {
    let element = this.get('focusedElement');

    if (!this.get('isFocused')) {
      this.set('activeChip', -1);
    }

    if ((element === 'chips' && (this.get('activeChip') !== -1)) || element === 'input') {
      this.sendAction('focusIn', window.event);
    } else {
      this.sendAction('focusOut', window.event);
    }
  }),

  actions: {
    addItem(newItem) {
      if (this.get('requireMatch')) {
        // Don't add a new item - we're set to require a match.
        return;
      }

      if (isPresent(newItem)) {
        let item = newItem;

        if (isPresent(this.get('searchField'))) {
          item = {};
          item[this.get('searchField')] = newItem;
        }

        this.sendAction('addItem', item);
        this.set('newChipValue', '');

        if (isPresent(this.get('autocomplete'))) {
          // We have an autocomplete - reset it once it's closed itself.
          this.queueReset();
        }
      }
    },

    inputFocus(autocomplete) {
      let input = this.getInput();

      this.set('focusedElement', 'input');

      if (!this.get('content').length && !input.is(':focus')) {
        input.focus();
      } else {
        this.set('activeChip', -1);
      }

      // Keep track of the autocomplete, so we can force it to close when navigating to chips.
      if (isEmpty(this.get('autocomplete')) && input.is('.ember-paper-autocomplete-search-input')) {
        this.set('autocomplete', autocomplete);
      }

      // We don't want the autocomplete to open on focus - it'll open when the user starts typing.
      if (isPresent(autocomplete)) {
        autocomplete.actions.close();
      }
    },

    inputBlur(_, event) {
      if (this.focusMovingTo('.ember-power-select-option', event)) {
        // Focus has shifted to an item - don't mess with this event.
        return true;
      }

      if (this.get('lastItemChosen')) {
        // Last item has been chosen; select will be replaced with an input - ignore blur event.
        this.set('lastItemChosen', false);
        return true;
      }

      this.closeAutocomplete();

      if (!this.focusMovingTo('md-chips-wrap', event)) {
        this.set('focusedElement', 'none');
      }
    },

    chipsFocus() {
      this.set('focusedElement', 'chips');
    },

    chipsBlur(event) {
      if (!this.focusMovingTo(this.getInput(), event)) {
        this.set('focusedElement', 'none');
      }
    },

    autocompleteChange(item) {
      if (item) {
        // Trigger onChange for the new item.
        this.sendAction('addItem', item);

        this.queueReset();

        // Track selection of last item if no match required.
        if (this.get('options').length === 1 && !this.get('requireMatch')) {
          this.set('lastItemChosen', true);
          this.set('autocomplete', null);
        }

        return true;
      }
    },

    searchTextChange(searchText, select) {
      // Close dropdown if search text is cleared by the user.
      if (isEmpty(searchText)) {
        select.actions.close();
      }
    },

    keyDown(event) {
      let input = this.getInput().get(0);
      if (!this.get('readOnly') && isEmpty(input.value) && isPresent(this.get('content'))) {
        this.keyboardNavigation(event);
        if (this.get('activeChip') >= 0) {
          this.closeAutocomplete();
        }
      } else {
        // Make sure we don't leave a chip focused while typing.
        this.set('activeChip', -1);
        this.set('focusedElement', 'input');
      }
    },

    noUnselected(old, event) {
      if (['Backspace', 'Delete', 'Del', 'ArrowLeft', 'Left', 'ArrowRight', 'Right'].includes(event.key)) {
        this.sendAction('keyDown', event);
      } else if (event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey) {
        // Reject printable key presses
        event.preventDefault();
        event.stopPropagation();
        return false;
      }

    }
  },

  keyboardNavigation({ key }) {
    // No text has been entered, but we have chips; cursor keys should select chips.
    let current = this.get('activeChip');
    let chips = this.get('content');
    let input = this.getInput();

    if (['ArrowLeft', 'Left'].includes(key) || (key === 'Backspace' && current === -1)) {
      if (current === -1) {
        input.blur();
        this.$('md-chips-wrap', this.element).focus();
        this.set('activeChip', chips.length - 1);
      } else if (current > 0) {
        this.decrementProperty('activeChip');
      }
    } else if (['ArrowRight', 'Right'].includes(key)) {
      if (current >= 0) {
        this.incrementProperty('activeChip');
      }

      if (this.get('activeChip') >= chips.length) {
        this.set('activeChip', -1);
        input.focus();
      }
    } else if (current >= 0 && ['Backspace', 'Delete', 'Del'].includes(key)) {
      this.sendAction('removeItem', chips[current]);
      if (current >= chips.length) {
        this.queueReset();
        this.set('activeChip', -1);
      }
    }
  },

  resetInput() {
    let select = this.get('autocomplete');
    let input = this.getInput();

    if (input.is('.ember-paper-autocomplete-search-input') && isPresent(select)) {
      // Reset the underlying ember-power-select so that it's ready for another selection.
      input.val('');
      select.actions.search('');

      // Close the dropdown after focusing the field.
      input.focus();
      select.actions.close();
    } else {
      input.focus();
    }

    this.set('focusedElement', 'input');
    this.set('resetTimer', null);
  },

  queueReset() {
    if (this.get('resetTimer')) {
      run.cancel(this.get('resetTimer'));
    }

    this.set('resetTimer', run.next(this, this.resetInput));
  },

  closeAutocomplete() {
    if (!isEmpty(this.get('autocomplete')) && !isEmpty(this.get('autocomplete').actions)) {
      this.get('autocomplete').actions.close();
    }
  },

  getInput() {
    return this.$('.md-chip-input-container input');
  },

  focusMovingTo(selector, event) {
    if (!isEmpty(event) && !isEmpty(event.relatedTarget) && this.$(event.relatedTarget).is(selector)) {
      return true;
    }

    return false;
  }
});
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';
import Helmet from 'react-helmet';
//
import InvertColors from '@material-ui/icons/InvertColors';
import ColorPicker from 'material-ui-color-picker';
import Slider from '@material-ui/core/Slider';
import { makeStyles } from '@material-ui/core/styles';
//
import * as Basic from '../../components/basic';
import * as Advanced from '../../components/advanced';
import * as Utils from '../../utils';
import { ConfigurationManager, DataManager } from '../../redux';

const configurationManager = new ConfigurationManager();

const useStyles = makeStyles(theme => ({
  root: props => ({
    width: '100%',
    '& .MuiInput-input': { // @TODO: uses classes instead
      backgroundColor: props.parentValue ? props.parentValue : 'transparent',
      borderRadius: theme.shape.borderRadius,
      marginBottom: theme.spacing(0.5),
      paddingLeft: theme.spacing(1),
      paddingRight: theme.spacing(1),
    }
  }),
}));

/**
 * TODO: Move color picker to form component (AbstractFormComponent), if it will be used on other places.
 */
function BasicColorPicker(props) {
  const { name, label, placeholder, value, onChange, parentValue } = props;
  const classes = useStyles({ parentValue });
  //
  return (
    <ColorPicker
      name={ name }
      label={ `${ label } (${ value })` }
      defaultValue={ placeholder }
      value={ value }
      onChange={ onChange }
      className={ parentValue ? classes.root : '' }/>
  );
}

/**
 * Theme configuration.
 *
 * TODO: Move color picker to form component (AbstractFormComponent), if it will be used on other places.
 * TODO: Move slider to form component (AbstractFormComponent), if it will be used on other places.
 *
 * @author Radek Tomiška
 */
class ThemeConfiguration extends Basic.AbstractContent {

  constructor(props, context) {
    super(props, context);
    //
    this.state = {
    };
  }

  componentDidMount() {
    super.componentDidMount();
    //
    this.context.store.dispatch(
      this.getManager().fetchEntity('idm.pub.app.show.logo', this.getManager().getApplicationLogoKey(), (entity, error) => {
        if (error) {
          if (error.statusCode === 404) {
            // logo is not configured yet
          } else {
            this.addError(error);
          }
        }
      })
    );
    this.context.store.dispatch(
      this.getManager().fetchEntity('idm.pub.app.show.theme', this.getManager().getApplicationThemeKey(), (entity, error) => {
        if (error && error.statusCode !== 404) { // theme is not configured yet
          this.addError(error);
        }
        // set defaults by effective theme => not related to saved configuration
        const { theme } = this.props;
        this._initDefaultValues(theme);
      })
    );
  }

  getNavigationKey() {
    return 'system-configuration';
  }

  getContentKey() {
    return 'content.configuration-theme';
  }

  getManager() {
    return configurationManager;
  }

  _initDefaultValues(theme) {
    this.setState({
      primary: theme.palette.primary.main,
      secondary: theme.palette.secondary.main,
      primaryText: theme.palette.primary.contrastText,
      secondaryText: theme.palette.secondary.contrastText,
      borderRadius: theme.shape.borderRadius
    });
  }

  /**
   * Dropzone component function called after select file
   * @param file selected file (multiple is not allowed)
   */
  _onDrop(files) {
    if (this.refs.dropzone.state.isDragReject) {
      this.addMessage({
        message: this.i18n('message.fileRejected'),
        level: 'warning'
      });
      return;
    }
    files.forEach((file) => {
      const fileName = file.name.toLowerCase();
      if (!fileName.endsWith('.jpg') && !fileName.endsWith('.jpeg') && !fileName.endsWith('.png') && !fileName.endsWith('.gif')) {
        this.addMessage({
          message: this.i18n('message.fileRejected', {name: file.name}),
          level: 'warning'
        });
        return;
      }
      const objectURL = URL.createObjectURL(file);
      this.setState({
        cropperSrc: objectURL,
        showCropper: true,
        fileName: file.name
      });
    });
  }

  _closeCropper() {
    this.setState({
      showCropper: false
    });
  }

  _crop() {
    this.refs.cropper.crop((formData) => {
      // append selected fileName
      formData.fileName = this.state.fileName;
      formData.name = this.state.fileName;
      formData.append('fileName', this.state.fileName);
      //
      this.context.store.dispatch(this.getManager().uploadApplicationLogo(formData, (imageUrl, error) => {
        if (error) {
          this.addError(error);
          return;
        }
        this.addMessage({});
      }));
    });
    this._closeCropper();
  }

  _deleteLogo() {
    this.refs['confirm-delete-logo'].show(
      this.i18n(`action.delete-logo.message`),
      this.i18n(`action.delete-logo.title`)
    ).then(() => {
      this.context.store.dispatch(this.getManager().deleteApplicationLogo());
      this.addMessage({});
    }, () => {
      // Rejected
    });
  }

  _resetTheme() {
    this.refs['confirm-reset-theme'].show(
      this.i18n(`action.reset-theme.message`),
      this.i18n(`action.reset-theme.title`)
    ).then(() => {
      this.context.store.dispatch(
        this.getManager().deleteEntity({ id: 'idm.pub.app.show.theme' }, configurationManager.getApplicationThemeKey(), (entity, error) => {
          if (error) {
            if (error.statusCode === 404) { // default theme is already in use
              this.addMessage({});
            } else {
              this.addError(error);
            }
          } else {
            this.context.store.dispatch(this.getManager().fetchApplicationTheme('light', (theme => {
              this._initDefaultValues(theme);
              this.addMessage({});
            })));
          }
        })
      );
    }, () => {
      // Rejected
    });
  }

  _saveTheme() {
    const { theme, themeConfigured, themeEntity } = this.props;
    const { borderRadius, primary, secondary, primaryText, secondaryText } = this.state;
    //
    let _theme = {};
    try {
      _theme = JSON.parse(themeConfigured);
    } catch (syntaxError) {
      _theme = {};
    }
    if (!theme.shape) {
      theme.shape = {};
    }
    _theme.shape.borderRadius = borderRadius;
    //
    if (!theme.palette) {
      theme.palette = {};
    }
    _theme.palette.primary = {};
    _theme.palette.secondary = {};
    _theme.palette.primary.main = primary;
    _theme.palette.secondary.main = secondary;
    _theme.palette.primary.contrastText = primaryText;
    _theme.palette.secondary.contrastText = secondaryText;
    //
    if (Utils.Entity.isNew(themeEntity)) {
      this.context.store.dispatch(
        this.getManager().createEntity(
          {
            name: 'idm.pub.app.show.theme',
            value: JSON.stringify(_theme)
          },
          configurationManager.getApplicationThemeKey(),
          this._afterSaveTheme.bind(this)
        )
      );
    } else {
      themeEntity.value = JSON.stringify(_theme);
      //
      this.context.store.dispatch(
        this.getManager().updateEntity(
          themeEntity,
          configurationManager.getApplicationThemeKey(),
          this._afterSaveTheme.bind(this)
        )
      );
    }
  }

  _afterSaveTheme(entity, error) {
    if (error) {
      this.addError(error);
    } else {
      this.context.store.dispatch(this.getManager().fetchApplicationTheme('light', (theme => {
        this.context.store.dispatch(this.getManager().fetchPermissions(entity.name, configurationManager.getApplicationThemeKey()));
        this._initDefaultValues(theme);
        this.addMessage({});
      })));
    }
  }

  render() {
    const { _logoUrl, _logoLoading, _themeLoading, themeEntity, _themePermissions, _logoPermissions, logoEntity, theme } = this.props;
    const {
      primary,
      secondary,
      primaryText,
      secondaryText,
      borderRadius,
      showCropper,
      cropperSrc
    } = this.state;
    //
    return (
      <Basic.Container component="main" maxWidth="sm">
        <Helmet title={ this.i18n('title') } />
        <Basic.Confirm ref="confirm-delete-logo" level="danger"/>
        <Basic.Confirm ref="confirm-reset-theme" level="secondary"/>
        <Basic.PageHeader icon={<InvertColors/>} text={ this.i18n('header') }/>
        <Basic.ContentHeader text={ this.i18n('logo.header') }/>
        <Basic.LabelWrapper
          helpBlock={ this.i18n('logo.help', { escape: false }) }>

            <Advanced.ImageDropzone
              ref="dropzone"
              accept="image/*"
              multiple={ false }
              onDrop={ this._onDrop.bind(this) }
              showLoading={ _logoLoading }
              readOnly={ !this.getManager().canSave(logoEntity, _logoPermissions) }>

            </Advanced.ImageDropzone>
            <Basic.Fab
              level="danger"
              className={ _logoUrl ? 'btn-remove' : 'hidden' }
              size="small">
              <Basic.Button
                type="button"
                level="danger"
                buttonSize="xs"
                style={{ color: 'white' }}
                rendered={ !!_logoUrl && this.getManager().canDelete(logoEntity, _logoPermissions) }
                titlePlacement="left"
                icon="fa:trash"
                onClick={ this._deleteLogo.bind(this) }/>
            </Basic.Fab>

          <Basic.Modal
            bsSize="default"
            show={ showCropper }
            onHide={ this._closeCropper.bind(this) }
            backdrop="static" >
            <Basic.Modal.Body>
              <Advanced.ImageCropper
                ref="cropper"
                src={ cropperSrc }
                autoCropArea={ 1 }
                fixedAspectRatio={ false }
                width={ 165 }
                height={ 40 }/>
            </Basic.Modal.Body>
            <Basic.Modal.Footer>
              <Basic.Button
                level="link"
                onClick={ this._closeCropper.bind(this) }
                showLoading={ _logoLoading }>
                { this.i18n('button.close') }
              </Basic.Button>
              <Basic.Button
                level="info"
                onClick={ this._crop.bind(this) }
                showLoading={ _logoLoading }>
                { this.i18n('button.crop') }
              </Basic.Button>
            </Basic.Modal.Footer>
          </Basic.Modal>
        </Basic.LabelWrapper>

        {
          _themeLoading
          ||
          <>
            <Basic.Alert
              level="warning"
              text={ this.i18n('colors.disabled') }
              rendered={ theme && theme.palette.type !== 'light' }/>
            <Basic.Panel className="last" rendered={ theme && theme.palette.type === 'light' }>
              <Basic.PanelBody>
                <Basic.ContentHeader text={ this.i18n('colors.header') } style={{ paddingTop: 0 }}/>
                <Basic.Row>
                  <Basic.Col sm={ 6 }>
                    <Basic.LabelWrapper>
                      <BasicColorPicker
                        name="primary"
                        label={ this.i18n('colors.primary.color.label') }
                        placeholder={ this.i18n('colors.primary.color.placeholder') }
                        value={ primary }
                        onChange={ color => this.setState({ primary: color }) }/>
                    </Basic.LabelWrapper>
                  </Basic.Col>
                  <Basic.Col sm={ 6 }>
                    <Basic.LabelWrapper>
                      <BasicColorPicker
                        name="secondary"
                        label={ this.i18n('colors.secondary.color.label') }
                        placeholder={ this.i18n('colors.secondary.color.placeholder') }
                        value={ secondary }
                        onChange={ color => this.setState({ secondary: color }) }/>
                    </Basic.LabelWrapper>
                  </Basic.Col>
                </Basic.Row>
                <Basic.Row>
                  <Basic.Col sm={ 6 }>
                    <Basic.LabelWrapper>
                      <BasicColorPicker
                        name="primaryText"
                        label={ this.i18n('colors.primary.text.label') }
                        placeholder={ this.i18n('colors.primary.text.placeholder') }
                        value={ primaryText }
                        parentValue={ primary }
                        onChange={ color => this.setState({ primaryText: color }) }/>
                    </Basic.LabelWrapper>
                  </Basic.Col>
                  <Basic.Col sm={ 6 }>
                    <Basic.LabelWrapper>
                      <BasicColorPicker
                        name="secondaryText"
                        label={ this.i18n('colors.secondary.text.label') }
                        placeholder={ this.i18n('colors.secondary.text.placeholder') }
                        value={ secondaryText }
                        parentValue={ secondary }
                        onChange={ color => this.setState({ secondaryText: color }) }/>
                    </Basic.LabelWrapper>
                  </Basic.Col>
                </Basic.Row>

                <Basic.ContentHeader text={ this.i18n('colors.borderRadius.header') }/>
                <Basic.LabelWrapper
                  helpBlock={ this.i18n('colors.borderRadius.help') }>
                  <Slider
                    value={ borderRadius }
                    aria-labelledby="discrete-slider"
                    valueLabelDisplay="auto"
                    step={ 1 }
                    marks
                    min={ 0 }
                    max={ 20 }
                    onChange={ (e, value) => this.setState({ borderRadius: value }) }
                  />
                </Basic.LabelWrapper>
              </Basic.PanelBody>
              <Basic.PanelFooter
                rendered={
                  this.getManager().canDelete(themeEntity, _themePermissions) || this.getManager().canSave(themeEntity, _themePermissions)
                }>
                <Basic.Button
                  type="submit"
                  level="secondary"
                  showLoading={ _themeLoading }
                  style={{ marginRight: 5 }}
                  onClick={ this._resetTheme.bind(this) }
                  disabled={ themeEntity === null }
                  rendered={ this.getManager().canDelete(themeEntity, _themePermissions) }>
                  { this.i18n('button.reset') }
                </Basic.Button>
                <Basic.Button
                  type="submit"
                  level="primary"
                  showLoading={ _themeLoading }
                  showLoadingIcon
                  showLoadingText={ this.i18n('button.saving') }
                  onClick={ this._saveTheme.bind(this) }
                  rendered={ this.getManager().canSave(themeEntity, _themePermissions) }>
                  { this.i18n('button.set') }
                </Basic.Button>
              </Basic.PanelFooter>
            </Basic.Panel>
          </>
        }
      </Basic.Container>
    );
  }
}

function select(state) {
  return {
    i18nReady: state.config.get('i18nReady'),
    userContext: state.security.userContext,
    theme: ConfigurationManager.getApplicationTheme(state),
    themeConfigured: ConfigurationManager.getValue(state, 'idm.pub.app.show.theme'),
    themeEntity: configurationManager.getEntity(state, 'idm.pub.app.show.theme'),
    logoEntity: configurationManager.getEntity(state, 'idm.pub.app.show.logo'),
    _themePermissions: configurationManager.getPermissions(state, configurationManager.getApplicationThemeKey(), 'idm.pub.app.show.theme'),
    _logoPermissions: configurationManager.getPermissions(state, configurationManager.getApplicationLogoKey(), 'idm.pub.app.show.logo'),
    _logoLoading: DataManager.isShowLoading(state, configurationManager.getApplicationLogoKey()),
    _themeLoading: DataManager.isShowLoading(state, configurationManager.getApplicationThemeKey()),
    _logoUrl: ConfigurationManager.getApplicationLogo(state),
  };
}

export default connect(select)(ThemeConfiguration);
<commit_msg>#2885 theme doc link<commit_after>import React from 'react';
import { connect } from 'react-redux';
import Helmet from 'react-helmet';
//
import InvertColors from '@material-ui/icons/InvertColors';
import ColorPicker from 'material-ui-color-picker';
import Slider from '@material-ui/core/Slider';
import { makeStyles } from '@material-ui/core/styles';
//
import * as Basic from '../../components/basic';
import * as Advanced from '../../components/advanced';
import * as Utils from '../../utils';
import { ConfigurationManager, DataManager } from '../../redux';

const configurationManager = new ConfigurationManager();

const useStyles = makeStyles(theme => ({
  root: props => ({
    width: '100%',
    '& .MuiInput-input': { // @TODO: uses classes instead
      backgroundColor: props.parentValue ? props.parentValue : 'transparent',
      borderRadius: theme.shape.borderRadius,
      marginBottom: theme.spacing(0.5),
      paddingLeft: theme.spacing(1),
      paddingRight: theme.spacing(1),
    }
  }),
}));

/**
 * TODO: Move color picker to form component (AbstractFormComponent), if it will be used on other places.
 */
function BasicColorPicker(props) {
  const { name, label, placeholder, value, onChange, parentValue } = props;
  const classes = useStyles({ parentValue });
  //
  return (
    <ColorPicker
      name={ name }
      label={ `${ label } (${ value })` }
      defaultValue={ placeholder }
      value={ value }
      onChange={ onChange }
      className={ parentValue ? classes.root : '' }/>
  );
}

/**
 * Theme configuration.
 *
 * TODO: Move color picker to form component (AbstractFormComponent), if it will be used on other places.
 * TODO: Move slider to form component (AbstractFormComponent), if it will be used on other places.
 *
 * @author Radek Tomiška
 */
class ThemeConfiguration extends Basic.AbstractContent {

  constructor(props, context) {
    super(props, context);
    //
    this.state = {
    };
  }

  componentDidMount() {
    super.componentDidMount();
    //
    this.context.store.dispatch(
      this.getManager().fetchEntity('idm.pub.app.show.logo', this.getManager().getApplicationLogoKey(), (entity, error) => {
        if (error) {
          if (error.statusCode === 404) {
            // logo is not configured yet
          } else {
            this.addError(error);
          }
        }
      })
    );
    this.context.store.dispatch(
      this.getManager().fetchEntity('idm.pub.app.show.theme', this.getManager().getApplicationThemeKey(), (entity, error) => {
        if (error && error.statusCode !== 404) { // theme is not configured yet
          this.addError(error);
        }
        // set defaults by effective theme => not related to saved configuration
        const { theme } = this.props;
        this._initDefaultValues(theme);
      })
    );
  }

  getNavigationKey() {
    return 'system-configuration';
  }

  getContentKey() {
    return 'content.configuration-theme';
  }

  getManager() {
    return configurationManager;
  }

  _initDefaultValues(theme) {
    this.setState({
      primary: theme.palette.primary.main,
      secondary: theme.palette.secondary.main,
      primaryText: theme.palette.primary.contrastText,
      secondaryText: theme.palette.secondary.contrastText,
      borderRadius: theme.shape.borderRadius
    });
  }

  /**
   * Dropzone component function called after select file
   * @param file selected file (multiple is not allowed)
   */
  _onDrop(files) {
    if (this.refs.dropzone.state.isDragReject) {
      this.addMessage({
        message: this.i18n('message.fileRejected'),
        level: 'warning'
      });
      return;
    }
    files.forEach((file) => {
      const fileName = file.name.toLowerCase();
      if (!fileName.endsWith('.jpg') && !fileName.endsWith('.jpeg') && !fileName.endsWith('.png') && !fileName.endsWith('.gif')) {
        this.addMessage({
          message: this.i18n('message.fileRejected', {name: file.name}),
          level: 'warning'
        });
        return;
      }
      const objectURL = URL.createObjectURL(file);
      this.setState({
        cropperSrc: objectURL,
        showCropper: true,
        fileName: file.name
      });
    });
  }

  _closeCropper() {
    this.setState({
      showCropper: false
    });
  }

  _crop() {
    this.refs.cropper.crop((formData) => {
      // append selected fileName
      formData.fileName = this.state.fileName;
      formData.name = this.state.fileName;
      formData.append('fileName', this.state.fileName);
      //
      this.context.store.dispatch(this.getManager().uploadApplicationLogo(formData, (imageUrl, error) => {
        if (error) {
          this.addError(error);
          return;
        }
        this.addMessage({});
      }));
    });
    this._closeCropper();
  }

  _deleteLogo() {
    this.refs['confirm-delete-logo'].show(
      this.i18n(`action.delete-logo.message`),
      this.i18n(`action.delete-logo.title`)
    ).then(() => {
      this.context.store.dispatch(this.getManager().deleteApplicationLogo());
      this.addMessage({});
    }, () => {
      // Rejected
    });
  }

  _resetTheme() {
    this.refs['confirm-reset-theme'].show(
      this.i18n(`action.reset-theme.message`),
      this.i18n(`action.reset-theme.title`)
    ).then(() => {
      this.context.store.dispatch(
        this.getManager().deleteEntity({ id: 'idm.pub.app.show.theme' }, configurationManager.getApplicationThemeKey(), (entity, error) => {
          if (error) {
            if (error.statusCode === 404) { // default theme is already in use
              this.addMessage({});
            } else {
              this.addError(error);
            }
          } else {
            this.context.store.dispatch(this.getManager().fetchApplicationTheme('light', (theme => {
              this._initDefaultValues(theme);
              this.addMessage({});
            })));
          }
        })
      );
    }, () => {
      // Rejected
    });
  }

  _saveTheme() {
    const { theme, themeConfigured, themeEntity } = this.props;
    const { borderRadius, primary, secondary, primaryText, secondaryText } = this.state;
    //
    let _theme = {};
    try {
      _theme = JSON.parse(themeConfigured);
    } catch (syntaxError) {
      _theme = {};
    }
    if (!theme.shape) {
      theme.shape = {};
    }
    _theme.shape.borderRadius = borderRadius;
    //
    if (!theme.palette) {
      theme.palette = {};
    }
    _theme.palette.primary = {};
    _theme.palette.secondary = {};
    _theme.palette.primary.main = primary;
    _theme.palette.secondary.main = secondary;
    _theme.palette.primary.contrastText = primaryText;
    _theme.palette.secondary.contrastText = secondaryText;
    //
    if (Utils.Entity.isNew(themeEntity)) {
      this.context.store.dispatch(
        this.getManager().createEntity(
          {
            name: 'idm.pub.app.show.theme',
            value: JSON.stringify(_theme)
          },
          configurationManager.getApplicationThemeKey(),
          this._afterSaveTheme.bind(this)
        )
      );
    } else {
      themeEntity.value = JSON.stringify(_theme);
      //
      this.context.store.dispatch(
        this.getManager().updateEntity(
          themeEntity,
          configurationManager.getApplicationThemeKey(),
          this._afterSaveTheme.bind(this)
        )
      );
    }
  }

  _afterSaveTheme(entity, error) {
    if (error) {
      this.addError(error);
    } else {
      this.context.store.dispatch(this.getManager().fetchApplicationTheme('light', (theme => {
        this.context.store.dispatch(this.getManager().fetchPermissions(entity.name, configurationManager.getApplicationThemeKey()));
        this._initDefaultValues(theme);
        this.addMessage({});
      })));
    }
  }

  render() {
    const { _logoUrl, _logoLoading, _themeLoading, themeEntity, _themePermissions, _logoPermissions, logoEntity, theme } = this.props;
    const {
      primary,
      secondary,
      primaryText,
      secondaryText,
      borderRadius,
      showCropper,
      cropperSrc
    } = this.state;
    //
    return (
      <Basic.Container component="main" maxWidth="sm">
        <Helmet title={ this.i18n('title') } />
        <Basic.Confirm ref="confirm-delete-logo" level="danger"/>
        <Basic.Confirm ref="confirm-reset-theme" level="secondary"/>
        <Basic.PageHeader icon={<InvertColors/>} text={ this.i18n('header') }/>
        <Basic.ContentHeader text={ this.i18n('logo.header') }/>
        <Basic.LabelWrapper
          helpBlock={ this.i18n('logo.help', { escape: false }) }>

            <Advanced.ImageDropzone
              ref="dropzone"
              accept="image/*"
              multiple={ false }
              onDrop={ this._onDrop.bind(this) }
              showLoading={ _logoLoading }
              readOnly={ !this.getManager().canSave(logoEntity, _logoPermissions) }>

            </Advanced.ImageDropzone>
            <Basic.Fab
              level="danger"
              className={ _logoUrl ? 'btn-remove' : 'hidden' }
              size="small">
              <Basic.Button
                type="button"
                level="danger"
                buttonSize="xs"
                style={{ color: 'white' }}
                rendered={ !!_logoUrl && this.getManager().canDelete(logoEntity, _logoPermissions) }
                titlePlacement="left"
                icon="fa:trash"
                onClick={ this._deleteLogo.bind(this) }/>
            </Basic.Fab>

          <Basic.Modal
            bsSize="default"
            show={ showCropper }
            onHide={ this._closeCropper.bind(this) }
            backdrop="static" >
            <Basic.Modal.Body>
              <Advanced.ImageCropper
                ref="cropper"
                src={ cropperSrc }
                autoCropArea={ 1 }
                fixedAspectRatio={ false }
                width={ 165 }
                height={ 40 }/>
            </Basic.Modal.Body>
            <Basic.Modal.Footer>
              <Basic.Button
                level="link"
                onClick={ this._closeCropper.bind(this) }
                showLoading={ _logoLoading }>
                { this.i18n('button.close') }
              </Basic.Button>
              <Basic.Button
                level="info"
                onClick={ this._crop.bind(this) }
                showLoading={ _logoLoading }>
                { this.i18n('button.crop') }
              </Basic.Button>
            </Basic.Modal.Footer>
          </Basic.Modal>
        </Basic.LabelWrapper>

        {
          _themeLoading
          ||
          <>
            <Basic.Alert
              level="warning"
              text={ this.i18n('colors.disabled') }
              rendered={ theme && theme.palette.type !== 'light' }/>
            <Basic.Panel className="last" rendered={ theme && theme.palette.type === 'light' }>
              <Basic.PanelBody>
                <Basic.ContentHeader
                  text={ this.i18n('colors.header') }
                  style={{ paddingTop: 0 }}/>
                <Basic.Row>
                  <Basic.Col sm={ 6 }>
                    <Basic.LabelWrapper>
                      <BasicColorPicker
                        name="primary"
                        label={ this.i18n('colors.primary.color.label') }
                        placeholder={ this.i18n('colors.primary.color.placeholder') }
                        value={ primary }
                        onChange={ color => this.setState({ primary: color }) }/>
                    </Basic.LabelWrapper>
                  </Basic.Col>
                  <Basic.Col sm={ 6 }>
                    <Basic.LabelWrapper>
                      <BasicColorPicker
                        name="secondary"
                        label={ this.i18n('colors.secondary.color.label') }
                        placeholder={ this.i18n('colors.secondary.color.placeholder') }
                        value={ secondary }
                        onChange={ color => this.setState({ secondary: color }) }/>
                    </Basic.LabelWrapper>
                  </Basic.Col>
                </Basic.Row>
                <Basic.Row>
                  <Basic.Col sm={ 6 }>
                    <Basic.LabelWrapper
                      helpBlock={
                        !this.isDevelopment()
                        ||
                        <Basic.Link href="https://v4.mui.com/customization/color/#playground">Návod</Basic.Link> jak vybrat barvy.
                      }>
                      <BasicColorPicker
                        name="primaryText"
                        label={ this.i18n('colors.primary.text.label') }
                        placeholder={ this.i18n('colors.primary.text.placeholder') }
                        value={ primaryText }
                        parentValue={ primary }
                        onChange={ color => this.setState({ primaryText: color }) }/>
                    </Basic.LabelWrapper>
                  </Basic.Col>
                  <Basic.Col sm={ 6 }>
                    <Basic.LabelWrapper>
                      <BasicColorPicker
                        name="secondaryText"
                        label={ this.i18n('colors.secondary.text.label') }
                        placeholder={ this.i18n('colors.secondary.text.placeholder') }
                        value={ secondaryText }
                        parentValue={ secondary }
                        onChange={ color => this.setState({ secondaryText: color }) }/>
                    </Basic.LabelWrapper>
                  </Basic.Col>
                </Basic.Row>

                <Basic.ContentHeader text={ this.i18n('colors.borderRadius.header') }/>
                <Basic.LabelWrapper
                  helpBlock={ this.i18n('colors.borderRadius.help') }>
                  <Slider
                    value={ borderRadius }
                    aria-labelledby="discrete-slider"
                    valueLabelDisplay="auto"
                    step={ 1 }
                    marks
                    min={ 0 }
                    max={ 20 }
                    onChange={ (e, value) => this.setState({ borderRadius: value }) }
                  />
                </Basic.LabelWrapper>
              </Basic.PanelBody>
              <Basic.PanelFooter
                rendered={
                  this.getManager().canDelete(themeEntity, _themePermissions) || this.getManager().canSave(themeEntity, _themePermissions)
                }>
                <Basic.Button
                  type="submit"
                  level="secondary"
                  showLoading={ _themeLoading }
                  style={{ marginRight: 5 }}
                  onClick={ this._resetTheme.bind(this) }
                  disabled={ themeEntity === null }
                  rendered={ this.getManager().canDelete(themeEntity, _themePermissions) }>
                  { this.i18n('button.reset') }
                </Basic.Button>
                <Basic.Button
                  type="submit"
                  level="primary"
                  showLoading={ _themeLoading }
                  showLoadingIcon
                  showLoadingText={ this.i18n('button.saving') }
                  onClick={ this._saveTheme.bind(this) }
                  rendered={ this.getManager().canSave(themeEntity, _themePermissions) }>
                  { this.i18n('button.set') }
                </Basic.Button>
              </Basic.PanelFooter>
            </Basic.Panel>
          </>
        }
      </Basic.Container>
    );
  }
}

function select(state) {
  return {
    i18nReady: state.config.get('i18nReady'),
    userContext: state.security.userContext,
    theme: ConfigurationManager.getApplicationTheme(state),
    themeConfigured: ConfigurationManager.getValue(state, 'idm.pub.app.show.theme'),
    themeEntity: configurationManager.getEntity(state, 'idm.pub.app.show.theme'),
    logoEntity: configurationManager.getEntity(state, 'idm.pub.app.show.logo'),
    _themePermissions: configurationManager.getPermissions(state, configurationManager.getApplicationThemeKey(), 'idm.pub.app.show.theme'),
    _logoPermissions: configurationManager.getPermissions(state, configurationManager.getApplicationLogoKey(), 'idm.pub.app.show.logo'),
    _logoLoading: DataManager.isShowLoading(state, configurationManager.getApplicationLogoKey()),
    _themeLoading: DataManager.isShowLoading(state, configurationManager.getApplicationThemeKey()),
    _logoUrl: ConfigurationManager.getApplicationLogo(state),
  };
}

export default connect(select)(ThemeConfiguration);
<|endoftext|>


<commit_before>import Ember from "ember";

var SummerNoteComponent = Ember.Component.extend({

  classNames: ['wysiwyg-editor'],
  btnSize: 'btn-xs',
  height: 120,
  focus: false,
  airMode: false,
  disabled: false,

  willDestroyElement: function() {
    this.$('textarea').destroy();
  },

  didInsertElement: function() {
    var _btnSize = this.get('btnSize');
    var _height = this.get('height');
    var _focus = this.get('focus');
    var _airMode = this.get('airMode');

    // ensure summernote is loaded
    // summernote 0.6.0 is not working as of this code written.
    // 0.5.10 is working version.

    Ember.assert("summernote has to exist on Ember.$.fn.summernote", typeof Ember.$.fn.summernote === "function" );
    Ember.assert("tooltip has to exist on Ember.$.fn.tooltip", typeof Ember.$.fn.tooltip === "function" );

    this.$('textarea').summernote({
      height: _height,
      focus: _focus,
      toolbar: [
        ['style', ['style']],
        ['font', ['bold', 'italic', 'underline', 'superscript', 'subscript', 'strikethrough', 'clear']],
        ['fontname', ['fontname']],
        ['fontsize', ['fontsize']],
        ['color', ['color']],
        ['para', ['ul', 'ol', 'paragraph']],
        ['height', ['height']],
        ['table', ['table']],
        ['insert', ['link', 'picture', 'video', 'hr']],
        ['view', ['fullscreen', 'codeview']],
        ['help', ['help']]
      ],
      airMode: _airMode,
      // airPopover: [
      //   ['color', ['color']],
      //   ['font', ['bold', 'underline', 'clear']],
      //   ['para', ['ul', 'paragraph']],
      //   ['table', ['table']],
      //   ['insert', ['link', 'picture']]
      // ]
    });

    this.$().find('.note-editable').attr('contenteditable', !this.get('disabled'));

    var _content = this.get('content');
    this.$('textarea').code(_content);
    this.$('.btn').addClass(_btnSize);
  },

  keyUp: function() {
    this.doUpdate();
  },

  click: function() {
    this.doUpdate();
  },

  doUpdate: function() {
    var content = this.$('.note-editable').html();
    this.set('content', content);
  },

  setHeight: function() {
    this.$().find('.note-editable').css('height', this.get('height')); //use css height, as jQuery heigth/outerHeight does add the padding+margin
  }.observes('height'),

  setContentEditable: function() {
    this.$().find('.note-editable').attr('contenteditable', !this.get('disabled'));
  }.observes('disabled')
});

export default SummerNoteComponent;
<commit_msg>Enabled content update when in Html View<commit_after>import Ember from "ember";

var SummerNoteComponent = Ember.Component.extend({

  classNames: ['wysiwyg-editor'],
  btnSize: 'btn-xs',
  height: 120,
  focus: false,
  airMode: false,
  disabled: false,

  willDestroyElement: function() {
    this.$('textarea').destroy();
  },

  didInsertElement: function() {
    var _btnSize = this.get('btnSize');
    var _height = this.get('height');
    var _focus = this.get('focus');
    var _airMode = this.get('airMode');

    // ensure summernote is loaded
    // summernote 0.6.0 is not working as of this code written.
    // 0.5.10 is working version.

    Ember.assert("summernote has to exist on Ember.$.fn.summernote", typeof Ember.$.fn.summernote === "function" );
    Ember.assert("tooltip has to exist on Ember.$.fn.tooltip", typeof Ember.$.fn.tooltip === "function" );

    this.$('textarea').summernote({
      height: _height,
      focus: _focus,
      toolbar: [
        ['style', ['style']],
        ['font', ['bold', 'italic', 'underline', 'superscript', 'subscript', 'strikethrough', 'clear']],
        ['fontname', ['fontname']],
        ['fontsize', ['fontsize']],
        ['color', ['color']],
        ['para', ['ul', 'ol', 'paragraph']],
        ['height', ['height']],
        ['table', ['table']],
        ['insert', ['link', 'picture', 'video', 'hr']],
        ['view', ['fullscreen', 'codeview']],
        ['help', ['help']]
      ],
      airMode: _airMode,
      // airPopover: [
      //   ['color', ['color']],
      //   ['font', ['bold', 'underline', 'clear']],
      //   ['para', ['ul', 'paragraph']],
      //   ['table', ['table']],
      //   ['insert', ['link', 'picture']]
      // ]
    });

    this.$().find('.note-editable').attr('contenteditable', !this.get('disabled'));

    var _content = this.get('content');
    this.$('textarea').code(_content);
    this.$('.btn').addClass(_btnSize);
  },

  keyUp: function() {
    this.doUpdate();
  },

  click: function() {
    this.doUpdate();
  },

  doUpdate: function() {
    var content = this.$('textarea').code();
    this.set('content', content);
  },

  setHeight: function() {
    this.$().find('.note-editable').css('height', this.get('height')); //use css height, as jQuery heigth/outerHeight does add the padding+margin
  }.observes('height'),

  setContentEditable: function() {
    this.$().find('.note-editable').attr('contenteditable', !this.get('disabled'));
  }.observes('disabled')
});

export default SummerNoteComponent;
<|endoftext|>