<commit_before>import Morph from 'src/components/widgets/lively-morph.js';
import { uuid, without, getTempKeyFor, getObjectFor, flatMap } from 'utils';
import VivideObject from 'src/client/vivide/vivideobject.js';
import Script from 'src/client/vivide/vividescript.js';

/**
 * Smart widget choosing
 */
class WidgetChooser {
  static getPreferredWidgetType(forest, viewConfig) {
    if (viewConfig.has('widget')) {
      return viewConfig.get('widget');
    }

    // #TODO: this is too dependent on internal structure of the model/VivideObject
    // PROPOSAL: Models should not know about views, therefore they cannot return
    //   a suggested view, but they could return a data type suggestion like:
    //     model.dataType == "data-points" || "list" || "text"
    //   Additionally, this data type could be set manually or via an "intelligent"
    //   algorithm.
    if (forest && forest.length > 0) {
      // #Question: this model has an objects array, what is the data structure of this model?
      const model = forest[0];

      if(model.properties.has('dataPoints') && typeof model.properties.get('dataPoints')[0] === 'number') {
        return 'boxplot';
      } else if (model.properties.has('text')) {
        return 'text';
      }
    }
    return 'tree';
  }
  static findAppropriateWidget(forest, viewConfig) {
    const type = this.getPreferredWidgetType(forest, viewConfig);

    // full type specified
    if(type.includes('-')) {
      return type;
    }

    // shorthand notation used
    return `vivide-${type}-widget`;
  }
}

export default class VivideView extends Morph {
  static findViewWithId(id, context=document.body) {
    return lively.query(context, `vivide-view[vivide-view-id=${id}]`);
  }

  static getIdForView(view) {
    return view.id;
  }

  static get idAttribute() { return 'vivide-view-id'; }

  static get outportAttribute() { return 'outport-target'; }

  static get scriptAttribute() { return 'vivide-script'; }

  static get widgetId() { return 'widget'; }

  static get widgetSelector() { return '#' + this.widgetId; }

  static forestToData(model) {
    return model.map(m => m.object);
  }

  // unused?
  static dataToForest(data) {
    return data.map(d => new VivideObject(d));
  }

  get widget() { return this.get(VivideView.widgetSelector); }

  get input() { return this._input || (this._input = []); }
  set input(val) { return this._input = val; }

  get id() {
    let id = this.getAttribute(VivideView.idAttribute);
    if(id) { return id; }

    // ensure uuid begins with a letter to match the requirements for a css selector
    let newId = 'vivide-view-' + uuid();
    this.setAttribute(VivideView.idAttribute, newId);
    return newId;
  }

  get outportTargets() {
    let ids = this.getJSONAttribute(VivideView.outportAttribute);
    if(ids) {
      return flatMap.call(ids, id => {
        let view = VivideView.findViewWithId(id, lively.findWorldContext(this)) ;
        if(view === null) {
          lively.error('could not find view: ' + id);
          return [];
        }
        return [view];
      });
    }

    return this.outportTargets = [];
  }

  set outportTargets(targets) {
    return this.setJSONAttribute(
      VivideView.outportAttribute,
      targets.map(VivideView.getIdForView)
    );
  }

  addOutportTarget(target) {
    return this.outportTargets = this.outportTargets.concat(target);
  }

  removeOutportTarget(target) {
    return this.outportTargets = without.call(this.outportTargets, target);
  }

  get inportSources() {
    return Array.from(document.body.querySelectorAll(`vivide-view[${VivideView.outportAttribute}*=${this.id}]`));
  }

  get targetHull() {
    let hull = new Set();

    function addToHull(view) {
      if(view && !hull.has(view)) {
        hull.add(view);
        view.outportTargets.forEach(addToHull);
      }
    }
    addToHull(this);

    return Array.from(hull);
  }

  connectTo(target) {
    // #TODO: cycle detection, here?
    this.addOutportTarget(target);
  }
  removeConnectionTo(target) {
    this.removeOutportTarget(target);
  }

  transmitDataToOutportTargets(dataToTransmit) {
    this.outportTargets.forEach(target => target.newDataFromUpstream(dataToTransmit));
  }
  reallyNotifyOutportTargets(stuffToTransmit) {
    this.transmitDataToOutportTargets(VivideView.forestToData(stuffToTransmit));
  }
  notifyOutportTargets() {
    lively.warn('VIEW::NOTIFY2', this.forestToDisplay[0])
    this.reallyNotifyOutportTargets(this.forestToDisplay);
  }

  updateOutportTargets() {
    const dataToTransmit = this.getDataToTransmit();
    if(dataToTransmit) {
      lively.warn('VIEW::UPDATE', dataToTransmit[0])
      this.transmitDataToOutportTargets(dataToTransmit);
    }
  }

  getDataToTransmit() {
      lively.warn('display all data')
    if(this.widget && this.widget.multiSelectionEnabled) {
      return this.getSelectedData();
    } else if (this.widget && this.widget.localName === 'vivide-text-widget') {
      return this.getSelectedData();
    } else {
      // use all data
      return VivideObject.forestToData(this.forestToDisplay);
    }
  }

  getSelectedData() {
    const widget = this.widget;
    if(widget) {
      return widget.getSelectedData();
    }
    return undefined;
  }

  selectionChanged() {
    this.updateOutportTargets();
  }

  addDragInfoTo(evt) {
    const dt = evt.dataTransfer;
    // #TODO: An improved fix would be to change what is returned by the widget selection
    let selection = this.getDataToTransmit();
    if(selection) {
      lively.warn('VivideView::addDragInfoTo', selection[0])
      dt.setData("javascript/object", getTempKeyFor(selection));
    } else {
      lively.error('could not add drag data');
    }

    dt.setData("vivide", "");
    dt.setData("vivide/source-view", getTempKeyFor(this));
  }

  async initialize() {
    this.windowTitle = "VivideView";
    this.addEventListener('extent-changed', evt => this.onExtentChanged(evt));

    this.addEventListener('dragenter', evt => this.dragenter(evt), false);
    this.addEventListener('dragover', evt => this.dragover(evt), false);
    this.addEventListener('dragleave', evt => this.dragleave(evt), false);
    this.addEventListener('drop', evt => this.drop(evt), false);

    // restore data...

    // not migrating, but initializing from source
    if (!this.myCurrentScript) {
      debugger
      var scriptJSON = this.getAttribute("vivide-script")
      if (scriptJSON) {
        this.myCurrentScript = await Script.fromJSON(scriptJSON, this)
      } else {
        //This got commented because it is not reliable. It will often overwrite or take longer to setup and overwrite later on
        // await this.initDefaultScript();
      }

      var dataJSON = this.getAttribute("vivide-data")
      if (dataJSON) {
        // await this.createScriptEditor();
        var data = JSON.parse(dataJSON)
        this.newDataFromUpstream(data)
      }
    }
  }

  onExtentChanged() {
    this.childNodes.forEach(childNode => {
      if(childNode.dispatchEvent) {
        childNode.dispatchEvent(new CustomEvent("extent-changed"));
      }
    })
  }

  dragenter(evt) {}
  dragover(evt) {
    evt.preventDefault();

    this._resetDropOverEffects();
    this.classList.add('over');

    const dt = evt.dataTransfer;

    let hasSourceView = dt.types.includes("vivide") && dt.types.includes("vivide/source-view");
    if(hasSourceView) {
      // unfortunately, we cannot check for a circular dependency here,
      // because we cannot get data from the dataTransfer outside dragStart and drop
      // see: https://stackoverflow.com/a/31922258/1152174
      this.classList.add('accept-drop');
      dt.dropEffect = "link";

      return;
    }

    let hasData = dt.types.includes("javascript/object");
    if(hasData) {
      this.classList.add('accept-drop');
      dt.dropEffect = "copy";

      return;
    }

    this.classList.add('reject-drop');
  }
  dragleave(evt) {
    this._resetDropOverEffects();
  }
  _resetDropOverEffects() {
    this.classList.remove('over');
    this.classList.remove('reject-drop');
    this.classList.remove('accept-drop');
  }
  drop(evt) {
    this._resetDropOverEffects();

    let shouldPreventPropagation = false;

    const dt = evt.dataTransfer;
    if (dt.types.includes("javascript/object")) {
      lively.success('drop data');

      const data = getObjectFor(dt.getData("javascript/object"));
      this.newDataFromUpstream(data);

      shouldPreventPropagation = true;
    }

    if (dt.types.includes("vivide") && dt.types.includes("vivide/source-view")) {
      lively.success('drop vivide');

      const sourceView = getObjectFor(dt.getData("vivide/source-view"));

      if(this.targetHull.includes(sourceView)) {
        lively.warn('cannot connect views', 'preventing cyclic dependencies')
      } else {
        sourceView.connectTo(this);
      }

      shouldPreventPropagation = true;
    }

    if(shouldPreventPropagation) {
      evt.stopPropagation();
    }
  }

  get myCurrentScript() { return this._myCurrentScript; }
  set myCurrentScript(script) {
    this._myCurrentScript = script;
    this.setAttribute(VivideView.scriptAttribute, script.toJSON());
  }

  async initDefaultScript() {
    const script = await Script.createDefaultScript(this);
    this.myCurrentScript = script;
    return script;
    // this.setJSONAttribute(VivideView.scriptAttribute, this.myCurrentScript.toJSON());
  }

  async newDataFromUpstream(data) {
    this.input = data;

    await this.calculateOutputModel();
    await this.updateWidget();
    this.updateOutportTargets();
  }

  getInputData() {
    return this.input;
  }

  async calculateOutputModel() {
    const firstStep = this.myCurrentScript.getInitialStep();
    const data = this.input.slice(0);

    this.forestToDisplay = await firstStep.processData(data);
  }

  // #TODO: nearly a duplicate with newDataFromUpstream; remove duplication
  async scriptGotUpdated() {
    // #TODO: save script to web-component
    // #TODO: later support multiple profiles
    lively.notify('VivideView::scriptGotUpdated');
    await this.calculateOutputModel();
    await this.updateWidget();
    // Update outport views
    // #TODO: nearly a duplicate with notifyOutportTargets, remove duplication
    this.updateOutportTargets();
  }

  async updateWidget() {
    this.innerHTML = '';

    const viewConfig = await this.myCurrentScript.getViewConfig();
    const chosenWidgetType = WidgetChooser.findAppropriateWidget(this.forestToDisplay, viewConfig);

    const widget = await lively.create(chosenWidgetType);
    widget.setView(this);
    widget.setAttribute('id', VivideView.widgetId);
    this.appendChild(widget);
    await widget.display(this.forestToDisplay, viewConfig);
  }

  async createScriptEditor() {
    const viewWindow = lively.findWindow(this);
    const reference = viewWindow && viewWindow.tagName === "LIVELY-WINDOW" ?
        viewWindow : this;
    const pos = lively.getGlobalBounds(reference).topRight();

    const scriptEditor = await lively.openComponentInWindow('vivide-script-editor', pos);
    scriptEditor.setView(this);
    // #TODO: only do setView with this as argument, the following line should not be required
    await scriptEditor.setScript(this.myCurrentScript);
    return scriptEditor;
  }

  livelyPrepareSave() {
    this.setAttribute("vivide-script", this.myCurrentScript.toJSON());
    try {
      var json = JSON.stringify(this.getInputData())
      this.setAttribute("vivide-data", json)
    } catch(e) {
      console.warn("could not persist vivide data", this.getInputData() )
    }
  }

  async livelyExample() {
    const exampleData = [
      {
        name: "object",
        subclasses:[
          {name: "morph"}
        ]},
      {
        name: "list",
        subclasses:[
          {
            name: "linkedlist",
            subclasses:[
              {name: "stack"}
            ]},
          {
            name: "arraylist"
          }]
      },
      {
        name: "usercontrol",
        subclasses:[
          {name: "textbox"},
          {name: "button"},
          {name: "label"}
        ]},
    ];

    await this.initDefaultScript();
    await this.createScriptEditor();
    await this.newDataFromUpstream(exampleData);
  }

  livelyMigrate(other) {
    lively.notify('VivideView::migrate')
    this.myCurrentScript = other.myCurrentScript;
    this.myCurrentScript.setView(this);

    this.newDataFromUpstream(other.input);
  }

  livelyInspect(contentNode, inspector) {
    if (this.myCurrentScript) {
      contentNode.appendChild(inspector.display(this.myCurrentScript, false, "#script", this));
    }
    if (this.input) {
      contentNode.appendChild(inspector.display(this.input, false, "#input", this));
    }

    var outportTargets = this.outportTargets
    if (outportTargets) {
      contentNode.appendChild(inspector.display(outportTargets, false, "#outportTargets", this));
    }

  }

  livelyHalo() {
    return {
      configureHalo(halo) {
        halo.get('#default-items').style.display = 'none';
        halo.get('#vivide-items').style.display = 'flex';

        // dynamically create outport connection visualizations
        const outportContainer = halo.get('#vivide-outport-connection-items');
        this.outportTargets.forEach(target => {
          const item = document.createElement('lively-halo-vivide-outport-connection-item')
          item.classList.add('halo');
          item.setTarget(target);
          outportContainer.appendChild(item);
        });

        const inportContainer = halo.get('#vivide-inport-connection-items');
        this.inportSources.forEach(source => {
          const item = document.createElement('lively-halo-vivide-inport-connection-item')
          item.classList.add('halo');
          item.setSource(source);
          inportContainer.appendChild(item);
        });
      }
    };
  }
}
<commit_msg>AUTO-COMMIT src/client/vivide/components/vivide-view.js<commit_after>import Morph from 'src/components/widgets/lively-morph.js';
import { uuid, without, getTempKeyFor, getObjectFor, flatMap } from 'utils';
import VivideObject from 'src/client/vivide/vivideobject.js';
import Script from 'src/client/vivide/vividescript.js';

/**
 * Smart widget choosing
 */
class WidgetChooser {
  static getPreferredWidgetType(forest, viewConfig) {
    if (viewConfig.has('widget')) {
      return viewConfig.get('widget');
    }

    // #TODO: this is too dependent on internal structure of the model/VivideObject
    // PROPOSAL: Models should not know about views, therefore they cannot return
    //   a suggested view, but they could return a data type suggestion like:
    //     model.dataType == "data-points" || "list" || "text"
    //   Additionally, this data type could be set manually or via an "intelligent"
    //   algorithm.
    if (forest && forest.length > 0) {
      // #Question: this model has an objects array, what is the data structure of this model?
      const model = forest[0];

      if(model.properties.has('dataPoints') && typeof model.properties.get('dataPoints')[0] === 'number') {
        return 'boxplot';
      } else if (model.properties.has('text')) {
        return 'text';
      }
    }
    return 'tree';
  }
  static findAppropriateWidget(forest, viewConfig) {
    const type = this.getPreferredWidgetType(forest, viewConfig);

    // full type specified
    if(type.includes('-')) {
      return type;
    }

    // shorthand notation used
    return `vivide-${type}-widget`;
  }
}

export default class VivideView extends Morph {
  static findViewWithId(id, context=document.body) {
    return lively.query(context, `vivide-view[vivide-view-id=${id}]`);
  }

  static getIdForView(view) {
    return view.id;
  }

  static get idAttribute() { return 'vivide-view-id'; }

  static get outportAttribute() { return 'outport-target'; }

  static get scriptAttribute() { return 'vivide-script'; }

  static get widgetId() { return 'widget'; }

  static get widgetSelector() { return '#' + this.widgetId; }

  static forestToData(model) {
    return model.map(m => m.object);
  }

  // unused?
  static dataToForest(data) {
    return data.map(d => new VivideObject(d));
  }

  get widget() { return this.get(VivideView.widgetSelector); }

  get input() { return this._input || (this._input = []); }
  set input(val) { return this._input = val; }

  get id() {
    let id = this.getAttribute(VivideView.idAttribute);
    if(id) { return id; }

    // ensure uuid begins with a letter to match the requirements for a css selector
    let newId = 'vivide-view-' + uuid();
    this.setAttribute(VivideView.idAttribute, newId);
    return newId;
  }

  get outportTargets() {
    let ids = this.getJSONAttribute(VivideView.outportAttribute);
    if(ids) {
      return flatMap.call(ids, id => {
        let view = VivideView.findViewWithId(id, lively.findWorldContext(this)) ;
        if(view === null) {
          lively.error('could not find view: ' + id);
          return [];
        }
        return [view];
      });
    }

    return this.outportTargets = [];
  }

  set outportTargets(targets) {
    return this.setJSONAttribute(
      VivideView.outportAttribute,
      targets.map(VivideView.getIdForView)
    );
  }

  addOutportTarget(target) {
    return this.outportTargets = this.outportTargets.concat(target);
  }

  removeOutportTarget(target) {
    return this.outportTargets = without.call(this.outportTargets, target);
  }

  get inportSources() {
    return Array.from(document.body.querySelectorAll(`vivide-view[${VivideView.outportAttribute}*=${this.id}]`));
  }

  get targetHull() {
    let hull = new Set();

    function addToHull(view) {
      if(view && !hull.has(view)) {
        hull.add(view);
        view.outportTargets.forEach(addToHull);
      }
    }
    addToHull(this);

    return Array.from(hull);
  }

  connectTo(target) {
    // #TODO: cycle detection, here?
    this.addOutportTarget(target);
  }
  removeConnectionTo(target) {
    this.removeOutportTarget(target);
  }

  transmitDataToOutportTargets(dataToTransmit) {
    this.outportTargets.forEach(target => target.newDataFromUpstream(dataToTransmit));
  }
  reallyNotifyOutportTargets(stuffToTransmit) {
    this.transmitDataToOutportTargets(VivideView.forestToData(stuffToTransmit));
  }
  notifyOutportTargets() {
    lively.warn('VIEW::NOTIFY2', this.forestToDisplay[0])
    this.reallyNotifyOutportTargets(this.forestToDisplay);
  }

  updateOutportTargets() {
    const dataToTransmit = this.getDataToTransmit();
    if(dataToTransmit) {
      lively.warn('VIEW::UPDATE', dataToTransmit[0])
      this.transmitDataToOutportTargets(dataToTransmit);
    }
  }

  getDataToTransmit() {
      lively.warn('display all data')
    if(this.widget && this.widget.multiSelectionEnabled) {
      return this.getSelectedData();
    } else if (this.widget && this.widget.localName === 'vivide-text-widget') {
      return this.getSelectedData();
    } else {
      // use all data
      return VivideObject.forestToData(this.forestToDisplay);
    }
  }

  getSelectedData() {
    const widget = this.widget;
    if(widget) {
      return widget.getSelectedData();
    }
    return undefined;
  }

  selectionChanged() {
    this.updateOutportTargets();
  }

  addDragInfoTo(evt) {
    const dt = evt.dataTransfer;
    // #TODO: An improved fix would be to change what is returned by the widget selection
    let selection = this.getDataToTransmit();
    if(selection) {
      lively.warn('VivideView::addDragInfoTo', selection[0])
      dt.setData("javascript/object", getTempKeyFor(selection));
    } else {
      lively.error('could not add drag data');
    }

    dt.setData("vivide", "");
    dt.setData("vivide/source-view", getTempKeyFor(this));
  }

  async initialize() {
    this.windowTitle = "VivideView";
    this.addEventListener('extent-changed', evt => this.onExtentChanged(evt));

    this.addEventListener('dragenter', evt => this.dragenter(evt), false);
    this.addEventListener('dragover', evt => this.dragover(evt), false);
    this.addEventListener('dragleave', evt => this.dragleave(evt), false);
    this.addEventListener('drop', evt => this.drop(evt), false);

    // restore data...

    // not migrating, but initializing from source
    if (!this.myCurrentScript) {
      debugger
      var scriptJSON = this.getAttribute("vivide-script")
      if (scriptJSON) {
        this.myCurrentScript = await Script.fromJSON(scriptJSON, this)
      } else {
        //This got commented because it is not reliable. It will often overwrite or take longer to setup and overwrite later on
        // await this.initDefaultScript();
      }

      var dataJSON = this.getAttribute("vivide-data")
      if (dataJSON) {
        // await this.createScriptEditor();
        var data = JSON.parse(dataJSON)
        this.newDataFromUpstream(data)
      }
    }
  }

  onExtentChanged() {
    this.childNodes.forEach(childNode => {
      if(childNode.dispatchEvent) {
        childNode.dispatchEvent(new CustomEvent("extent-changed"));
      }
    })
  }

  dragenter(evt) {}
  dragover(evt) {
    evt.preventDefault();

    this._resetDropOverEffects();
    this.classList.add('over');

    const dt = evt.dataTransfer;

    let hasSourceView = dt.types.includes("vivide") && dt.types.includes("vivide/source-view");
    if(hasSourceView) {
      // unfortunately, we cannot check for a circular dependency here,
      // because we cannot get data from the dataTransfer outside dragStart and drop
      // see: https://stackoverflow.com/a/31922258/1152174
      this.classList.add('accept-drop');
      dt.dropEffect = "link";

      return;
    }

    let hasData = dt.types.includes("javascript/object");
    if(hasData) {
      this.classList.add('accept-drop');
      dt.dropEffect = "copy";

      return;
    }

    this.classList.add('reject-drop');
  }
  dragleave(evt) {
    this._resetDropOverEffects();
  }
  _resetDropOverEffects() {
    this.classList.remove('over');
    this.classList.remove('reject-drop');
    this.classList.remove('accept-drop');
  }
  drop(evt) {
    this._resetDropOverEffects();

    let shouldPreventPropagation = false;

    const dt = evt.dataTransfer;
    if (dt.types.includes("javascript/object")) {
      lively.success('drop data');

      const data = getObjectFor(dt.getData("javascript/object"));
      this.newDataFromUpstream(data);

      shouldPreventPropagation = true;
    }

    if (dt.types.includes("vivide") && dt.types.includes("vivide/source-view")) {
      lively.success('drop vivide');

      const sourceView = getObjectFor(dt.getData("vivide/source-view"));

      if(this.targetHull.includes(sourceView)) {
        lively.warn('cannot connect views', 'preventing cyclic dependencies')
      } else {
        sourceView.connectTo(this);
      }

      shouldPreventPropagation = true;
    }

    if(shouldPreventPropagation) {
      evt.stopPropagation();
    }
  }

  get myCurrentScript() { return this._myCurrentScript; }
  set myCurrentScript(script) {
    debugger
    this._myCurrentScript = script;
    this.setAttribute(VivideView.scriptAttribute, script.toJSON());
  }

  async initDefaultScript() {
    const script = await Script.createDefaultScript(this);
    this.myCurrentScript = script;
    return script;
    // this.setJSONAttribute(VivideView.scriptAttribute, this.myCurrentScript.toJSON());
  }

  async newDataFromUpstream(data) {
    this.input = data;

    await this.calculateOutputModel();
    await this.updateWidget();
    this.updateOutportTargets();
  }

  getInputData() {
    return this.input;
  }

  async calculateOutputModel() {
    const firstStep = this.myCurrentScript.getInitialStep();
    const data = this.input.slice(0);

    this.forestToDisplay = await firstStep.processData(data);
  }

  // #TODO: nearly a duplicate with newDataFromUpstream; remove duplication
  async scriptGotUpdated() {
    // #TODO: save script to web-component
    // #TODO: later support multiple profiles
    lively.notify('VivideView::scriptGotUpdated');
    await this.calculateOutputModel();
    await this.updateWidget();
    // Update outport views
    // #TODO: nearly a duplicate with notifyOutportTargets, remove duplication
    this.updateOutportTargets();
  }

  async updateWidget() {
    this.innerHTML = '';

    const viewConfig = await this.myCurrentScript.getViewConfig();
    const chosenWidgetType = WidgetChooser.findAppropriateWidget(this.forestToDisplay, viewConfig);

    const widget = await lively.create(chosenWidgetType);
    widget.setView(this);
    widget.setAttribute('id', VivideView.widgetId);
    this.appendChild(widget);
    await widget.display(this.forestToDisplay, viewConfig);
  }

  async createScriptEditor() {
    const viewWindow = lively.findWindow(this);
    const reference = viewWindow && viewWindow.tagName === "LIVELY-WINDOW" ?
        viewWindow : this;
    const pos = lively.getGlobalBounds(reference).topRight();

    const scriptEditor = await lively.openComponentInWindow('vivide-script-editor', pos);
    scriptEditor.setView(this);
    // #TODO: only do setView with this as argument, the following line should not be required
    await scriptEditor.setScript(this.myCurrentScript);
    return scriptEditor;
  }

  livelyPrepareSave() {
    this.setAttribute("vivide-script", this.myCurrentScript.toJSON());
    try {
      var json = JSON.stringify(this.getInputData())
      this.setAttribute("vivide-data", json)
    } catch(e) {
      console.warn("could not persist vivide data", this.getInputData() )
    }
  }

  async livelyExample() {
    const exampleData = [
      {
        name: "object",
        subclasses:[
          {name: "morph"}
        ]},
      {
        name: "list",
        subclasses:[
          {
            name: "linkedlist",
            subclasses:[
              {name: "stack"}
            ]},
          {
            name: "arraylist"
          }]
      },
      {
        name: "usercontrol",
        subclasses:[
          {name: "textbox"},
          {name: "button"},
          {name: "label"}
        ]},
    ];

    await this.initDefaultScript();
    await this.createScriptEditor();
    await this.newDataFromUpstream(exampleData);
  }

  livelyMigrate(other) {
    lively.notify('VivideView::migrate')
    this.myCurrentScript = other.myCurrentScript;
    this.myCurrentScript.setView(this);

    this.newDataFromUpstream(other.input);
  }

  livelyInspect(contentNode, inspector) {
    if (this.myCurrentScript) {
      contentNode.appendChild(inspector.display(this.myCurrentScript, false, "#script", this));
    }
    if (this.input) {
      contentNode.appendChild(inspector.display(this.input, false, "#input", this));
    }

    var outportTargets = this.outportTargets
    if (outportTargets) {
      contentNode.appendChild(inspector.display(outportTargets, false, "#outportTargets", this));
    }

  }

  livelyHalo() {
    return {
      configureHalo(halo) {
        halo.get('#default-items').style.display = 'none';
        halo.get('#vivide-items').style.display = 'flex';

        // dynamically create outport connection visualizations
        const outportContainer = halo.get('#vivide-outport-connection-items');
        this.outportTargets.forEach(target => {
          const item = document.createElement('lively-halo-vivide-outport-connection-item')
          item.classList.add('halo');
          item.setTarget(target);
          outportContainer.appendChild(item);
        });

        const inportContainer = halo.get('#vivide-inport-connection-items');
        this.inportSources.forEach(source => {
          const item = document.createElement('lively-halo-vivide-inport-connection-item')
          item.classList.add('halo');
          item.setSource(source);
          inportContainer.appendChild(item);
        });
      }
    };
  }
}
<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 * Copyright (c) 2016 Krypto Fin ry and the FIMK Developers
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * */
(function () {
'use strict';
var module = angular.module('fim.base');
module.factory('diceWords', function ($q, $http, $rootScope) {
  var cached = {};
  var diceWords = {
    getAvailableSets: function () {
      return ['en','fi'/*,'de', 'fr','it','jp','nl','pl','sv'*/];
    },
    resolveURL: function (fileName) {
      if (isNodeJS) { return 'dice-words/' + fileName; }
      return window.location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/dice-words/' + fileName
    },
    getWords: function (key) {
      var deferred = $q.defer();
      if (cached[key]) {
        deferred.resolve(cached[key]);
      }
      else {
        var sets = this.getAvailableSets();
        if (sets.indexOf(key)==-1) {
          key = 'en';
        }
        var file = key+'.txt';
        $http({
          url: this.resolveURL(file),
          method: 'GET'
        }).
        success(function(data) {
          var words = data.match(/[^\r\n]+/g);
          if (hasDuplicates(words)) {
            console.log('Word list ['+file+'] contains duplicates');
            deferred.reject();
          }
          else {
            cached[key] = words;
            deferred.resolve(cached[key]);
          }
        }.bind(this)).
        error(function(err) {
          console.log(err);
          deferred.reject();
        });
      }
      return deferred.promise;
    }
  };

  function hasDuplicates(array) {
    var duplicates = {}, key;
    for (var i=0; i<array.length; i++) {
      key = array[i];
      if (duplicates[key]) {
        console.log('Duplicate: '+key);
        return true;
      }
      duplicates[key] = 1;
    }
  }

  return diceWords;
});
})();<commit_msg>Fix bug, firefox thinks "watch" property is a duplicate<commit_after>/**
 * The MIT License (MIT)
 * Copyright (c) 2016 Krypto Fin ry and the FIMK Developers
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * */
(function () {
'use strict';
var module = angular.module('fim.base');
module.factory('diceWords', function ($q, $http, $rootScope) {
  var cached = {};
  var diceWords = {
    getAvailableSets: function () {
      return ['en','fi'/*,'de', 'fr','it','jp','nl','pl','sv'*/];
    },
    resolveURL: function (fileName) {
      if (isNodeJS) { return 'dice-words/' + fileName; }
      return window.location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/dice-words/' + fileName
    },
    getWords: function (key) {
      var deferred = $q.defer();
      if (cached[key]) {
        deferred.resolve(cached[key]);
      }
      else {
        var sets = this.getAvailableSets();
        if (sets.indexOf(key)==-1) {
          key = 'en';
        }
        var file = key+'.txt';
        $http({
          url: this.resolveURL(file),
          method: 'GET'
        }).
        success(function(data) {
          var words = data.match(/[^\r\n]+/g);
          if (hasDuplicates(words)) {
            console.log('Word list ['+file+'] contains duplicates');
            deferred.reject();
          }
          else {
            cached[key] = words;
            deferred.resolve(cached[key]);
          }
        }.bind(this)).
        error(function(err) {
          console.log(err);
          deferred.reject();
        });
      }
      return deferred.promise;
    }
  };

  function hasDuplicates(array) {
    var duplicates = {}, key;
    for (var i=0; i<array.length; i++) {
      key = array[i];
      if (duplicates.hasOwnProperty(key)) {
        console.log('Duplicate: '+key);
        return true;
      }
      duplicates[key] = 1;
    }
  }

  return diceWords;
});
})();<|endoftext|>


<commit_before>const config = require('../../../knexfile').intweb
const knex = require('knex')(config)
const dateFormatter = require('../date-formatter')
const claimDecisionEnum = require('../../constants/claim-decision-enum')
const tasksEnum = require('../../constants/tasks-enum')
const paymentMethodEnum = require('../../constants/payment-method-enum')
const insertClaimEvent = require('./insert-claim-event')
const insertTaskSendClaimNotification = require('./insert-task-send-claim-notification')
const updateRelatedClaimRemainingOverpaymentAmount = require('./update-related-claims-remaining-overpayment-amount')
const log = require('../log')

module.exports = function (claimId, claimDecision) {
  return knex('Claim').where('ClaimId', claimId)
    .join('Eligibility', 'Claim.EligibilityId', '=', 'Eligibility.EligibilityId')
    .first('Eligibility.EligibilityId', 'Eligibility.Reference')
    .then(function (result) {
      var eligibilityId = result.EligibilityId
      var reference = result.Reference
      var caseworker = claimDecision.caseworker
      var decision = claimDecision.decision
      var note = claimDecision.note
      var nomisCheck = claimDecision.nomisCheck
      var dwpCheck = claimDecision.dwpCheck
      var visitConfirmationCheck = claimDecision.visitConfirmationCheck
      var allExpensesManuallyProcessed = areAllExpensesManuallyProcessed(claimDecision.claimExpenseResponses)

      return Promise.all([updateEligibility(eligibilityId, decision),
        updateClaim(claimId, caseworker, decision, note, visitConfirmationCheck, allExpensesManuallyProcessed),
        updateVisitor(eligibilityId, dwpCheck),
        updatePrisoner(eligibilityId, nomisCheck),
        updateClaimExpenses(claimDecision.claimExpenseResponses),
        insertClaimEventForDecision(reference, eligibilityId, claimId, decision, note, caseworker),
        updateRemainingOverpaymentAmounts(claimId, reference, decision),
        sendClaimNotification(reference, eligibilityId, claimId, decision)])
    })
}

function updateEligibility (eligibilityId, decision) {
  return knex('Eligibility').where('EligibilityId', eligibilityId).update('Status', decision)
}

function updateClaim (claimId, caseworker, decision, note, visitConfirmationCheck, allExpensesManuallyProcessed) {
  var updateObject = {
    'Caseworker': caseworker,
    'Status': decision,
    'Note': note,
    'VisitConfirmationCheck': visitConfirmationCheck,
    'DateReviewed': dateFormatter.now().toDate(),
    'AssignedTo': null, // clear assignment
    'AssignmentExpiry': null,
    'LastUpdated': dateFormatter.now().toDate()
  }

  if (allExpensesManuallyProcessed) {
    updateObject.PaymentMethod = paymentMethodEnum.MANUALLY_PROCESSED.value
  }

  return knex('Claim').where('ClaimId', claimId).update(updateObject).then(
    log.info('Claim ID ' + claimId + ' Closed with Status: ' + updateObject.Status)
  )
}

function updateVisitor (eligibilityId, dwpCheck) {
  return knex('Visitor').where('EligibilityId', eligibilityId).update('DWPCheck', dwpCheck)
}

function updatePrisoner (eligibilityId, nomisCheck) {
  return knex('Prisoner').where('EligibilityId', eligibilityId).update('NomisCheck', nomisCheck)
}

function updateClaimExpenses (claimExpenseResponses) {
  var updates = []
  if (claimExpenseResponses) {
    claimExpenseResponses.forEach(function (claimExpenseResponse) {
      updates.push(updateClaimExpense(claimExpenseResponse))
    })
  }
  return Promise.all(updates)
}

function updateClaimExpense (claimExpenseResponse) {
  return knex('ClaimExpense').where('ClaimExpenseId', claimExpenseResponse.claimExpenseId).update({
    'ApprovedCost': claimExpenseResponse.approvedCost,
    'Status': claimExpenseResponse.status
  })
}

function insertClaimEventForDecision (reference, eligibilityId, claimId, decision, note, caseworker) {
  const event = `CLAIM-${decision}`
  return insertClaimEvent(reference, eligibilityId, claimId, event, null, note, caseworker, false)
}

function updateRemainingOverpaymentAmounts (claimId, reference, decision) {
  if (decision === claimDecisionEnum.APPROVED) {
    return updateRelatedClaimRemainingOverpaymentAmount(claimId, reference)
  } else {
    return Promise.resolve(null)
  }
}

function sendClaimNotification (reference, eligibilityId, claimId, decision) {
  var notificationType
  if (decision === claimDecisionEnum.APPROVED) {
    notificationType = tasksEnum.ACCEPT_CLAIM_NOTIFICATION
  } else if (decision === claimDecisionEnum.REJECTED) {
    notificationType = tasksEnum.REJECT_CLAIM_NOTIFICATION
  } else if (decision === claimDecisionEnum.REQUEST_INFORMATION) {
    notificationType = tasksEnum.REQUEST_INFORMATION_CLAIM_NOTIFICATION
  }

  return insertTaskSendClaimNotification(notificationType, reference, eligibilityId, claimId)
}

function areAllExpensesManuallyProcessed (claimExpenseResponses) {
  var result = true

  claimExpenseResponses.forEach(function (claimExpenseResponse) {
    if (claimExpenseResponse.status !== claimDecisionEnum.MANUALLY_PROCESSED) {
      result = false
    }
  })

  return result
}
<commit_msg>Added logic to set DateApproved to current timestamp when claim is approved and to reset it to null on rejection<commit_after>const config = require('../../../knexfile').intweb
const knex = require('knex')(config)
const dateFormatter = require('../date-formatter')
const claimDecisionEnum = require('../../constants/claim-decision-enum')
const tasksEnum = require('../../constants/tasks-enum')
const paymentMethodEnum = require('../../constants/payment-method-enum')
const insertClaimEvent = require('./insert-claim-event')
const insertTaskSendClaimNotification = require('./insert-task-send-claim-notification')
const updateRelatedClaimRemainingOverpaymentAmount = require('./update-related-claims-remaining-overpayment-amount')
const log = require('../log')

module.exports = function (claimId, claimDecision) {
  return knex('Claim').where('ClaimId', claimId)
    .join('Eligibility', 'Claim.EligibilityId', '=', 'Eligibility.EligibilityId')
    .first('Eligibility.EligibilityId', 'Eligibility.Reference')
    .then(function (result) {
      var eligibilityId = result.EligibilityId
      var reference = result.Reference
      var caseworker = claimDecision.caseworker
      var decision = claimDecision.decision
      var note = claimDecision.note
      var nomisCheck = claimDecision.nomisCheck
      var dwpCheck = claimDecision.dwpCheck
      var visitConfirmationCheck = claimDecision.visitConfirmationCheck
      var allExpensesManuallyProcessed = areAllExpensesManuallyProcessed(claimDecision.claimExpenseResponses)

      return Promise.all([updateEligibility(eligibilityId, decision),
        updateClaim(claimId, caseworker, decision, note, visitConfirmationCheck, allExpensesManuallyProcessed),
        updateVisitor(eligibilityId, dwpCheck),
        updatePrisoner(eligibilityId, nomisCheck),
        updateClaimExpenses(claimDecision.claimExpenseResponses),
        insertClaimEventForDecision(reference, eligibilityId, claimId, decision, note, caseworker),
        updateRemainingOverpaymentAmounts(claimId, reference, decision),
        sendClaimNotification(reference, eligibilityId, claimId, decision)])
    })
}

function updateEligibility (eligibilityId, decision) {
  return knex('Eligibility').where('EligibilityId', eligibilityId).update('Status', decision)
}

function updateClaim (claimId, caseworker, decision, note, visitConfirmationCheck, allExpensesManuallyProcessed) {
  var updateObject = {}
  if (decision === claimDecisionEnum.APPROVED) {
    updateObject = {
      'Caseworker': caseworker,
      'Status': decision,
      'Note': note,
      'VisitConfirmationCheck': visitConfirmationCheck,
      'DateReviewed': dateFormatter.now().toDate(),
      'AssignedTo': null, // clear assignment
      'AssignmentExpiry': null,
      'LastUpdated': dateFormatter.now().toDate(),
      'DateApproved': dateFormatter.now().toDate()
    }
  } else if (decision === claimDecisionEnum.REJECTED) {
      updateObject = {
        'Caseworker': caseworker,
        'Status': decision,
        'Note': note,
        'VisitConfirmationCheck': visitConfirmationCheck,
        'DateReviewed': dateFormatter.now().toDate(),
        'AssignedTo': null, // clear assignment
        'AssignmentExpiry': null,
        'LastUpdated': dateFormatter.now().toDate(),
        'DateApproved': null
      }
  } else {
      updateObject = {
        'Caseworker': caseworker,
        'Status': decision,
        'Note': note,
        'VisitConfirmationCheck': visitConfirmationCheck,
        'DateReviewed': dateFormatter.now().toDate(),
        'AssignedTo': null, // clear assignment
        'AssignmentExpiry': null,
        'LastUpdated': dateFormatter.now().toDate()
      }
  }

  if (allExpensesManuallyProcessed) {
    updateObject.PaymentMethod = paymentMethodEnum.MANUALLY_PROCESSED.value
  }

  return knex('Claim').where('ClaimId', claimId).update(updateObject).then(
    log.info('Claim ID ' + claimId + ' Closed with Status: ' + updateObject.Status)
  )
}

function updateVisitor (eligibilityId, dwpCheck) {
  return knex('Visitor').where('EligibilityId', eligibilityId).update('DWPCheck', dwpCheck)
}

function updatePrisoner (eligibilityId, nomisCheck) {
  return knex('Prisoner').where('EligibilityId', eligibilityId).update('NomisCheck', nomisCheck)
}

function updateClaimExpenses (claimExpenseResponses) {
  var updates = []
  if (claimExpenseResponses) {
    claimExpenseResponses.forEach(function (claimExpenseResponse) {
      updates.push(updateClaimExpense(claimExpenseResponse))
    })
  }
  return Promise.all(updates)
}

function updateClaimExpense (claimExpenseResponse) {
  return knex('ClaimExpense').where('ClaimExpenseId', claimExpenseResponse.claimExpenseId).update({
    'ApprovedCost': claimExpenseResponse.approvedCost,
    'Status': claimExpenseResponse.status
  })
}

function insertClaimEventForDecision (reference, eligibilityId, claimId, decision, note, caseworker) {
  const event = `CLAIM-${decision}`
  return insertClaimEvent(reference, eligibilityId, claimId, event, null, note, caseworker, false)
}

function updateRemainingOverpaymentAmounts (claimId, reference, decision) {
  if (decision === claimDecisionEnum.APPROVED) {
    return updateRelatedClaimRemainingOverpaymentAmount(claimId, reference)
  } else {
    return Promise.resolve(null)
  }
}

function sendClaimNotification (reference, eligibilityId, claimId, decision) {
  var notificationType
  if (decision === claimDecisionEnum.APPROVED) {
    notificationType = tasksEnum.ACCEPT_CLAIM_NOTIFICATION
  } else if (decision === claimDecisionEnum.REJECTED) {
    notificationType = tasksEnum.REJECT_CLAIM_NOTIFICATION
  } else if (decision === claimDecisionEnum.REQUEST_INFORMATION) {
    notificationType = tasksEnum.REQUEST_INFORMATION_CLAIM_NOTIFICATION
  }

  return insertTaskSendClaimNotification(notificationType, reference, eligibilityId, claimId)
}

function areAllExpensesManuallyProcessed (claimExpenseResponses) {
  var result = true

  claimExpenseResponses.forEach(function (claimExpenseResponse) {
    if (claimExpenseResponse.status !== claimDecisionEnum.MANUALLY_PROCESSED) {
      result = false
    }
  })

  return result
}
<|endoftext|>


<commit_before>import Ember from 'ember';

const {Service, Evented} = Ember;

/**
* TODO
*
* @class Landscape-Repository-Service
* @extends Ember.Service
*/
export default Service.extend(Evented, {

  latestLandscape: null,

  latestApplication: null,

  replayLandscape: null,

  replayApplication:null,

  triggerUpdate(){
    this.trigger("updated", this.get("latestLandscape"));
  }

});
<commit_msg>add trigger for replay<commit_after>import Ember from 'ember';

const {Service, Evented} = Ember;

/**
* TODO
*
* @class Landscape-Repository-Service
* @extends Ember.Service
*/
export default Service.extend(Evented, {

  latestLandscape: null,

  latestApplication: null,

  replayLandscape: null,

  replayApplication:null,

  triggerUpdate(){
    this.trigger("updated", this.get("latestLandscape"));
    this.trigger("updated", this.get("replayLandscape"));
  }

});
<|endoftext|>


<commit_before>/** @babel */

import SelectListView from 'atom-select-list'
import {humanizeKeystroke} from 'underscore-plus'
import fuzzaldrin from 'fuzzaldrin'
import fuzzaldrinPlus from 'fuzzaldrin-plus'

export default class CommandPaletteView {
  constructor () {
    this.keyBindingsForActiveElement = []
    this.selectListView = new SelectListView({
      items: [],
      filter: this.filter,
      emptyMessage: 'No matches found',
      elementForItem: (item, {index, selected}) => {
        const li = document.createElement('li')
        li.classList.add('event', 'two-lines')
        li.dataset.eventName = item.name

        const rightBlock = document.createElement('div')
        rightBlock.classList.add('pull-right')
        let i = 0
        for (const keyBinding of this.keyBindingsForActiveElement) {
          if (keyBinding.command === item.name) {
            const kbd = document.createElement('kbd')
            kbd.classList.add('key-binding')
            kbd.textContent = humanizeKeystroke(keyBinding.keystrokes)
            rightBlock.appendChild(kbd)
          }
        }
        li.appendChild(rightBlock)

        const leftBlock = document.createElement('div')
        const titleEl = document.createElement('div')
        titleEl.classList.add('primary-line')
        titleEl.title = item.name
        leftBlock.appendChild(titleEl)

        const query = this.selectListView.getQuery()
        this.highlightMatchesInElement(item.displayName, query, titleEl)

        if (selected) {
          let secondaryEl = document.createElement('div')
          secondaryEl.classList.add('secondary-line')
          secondaryEl.style.display = 'flex'

          if (typeof item.description === 'string') {
            secondaryEl.appendChild(this.createDescription(item.description, query))
          }

          if (Array.isArray(item.tags)) {
            const matchingTags = item.tags
              .map(t => [t, this.fuzz.score(t, query)])
              .filter(([t, s]) => s > 0)
              .sort((a, b) => a.s - b.s)
              .map(([t, s]) => t)

            if (matchingTags.length > 0) {
              secondaryEl.appendChild(this.createTags(matchingTags, query))
            }
          }

          leftBlock.appendChild(secondaryEl)
        }

        li.appendChild(leftBlock)
        return li
      },
      didConfirmSelection: (keyBinding) => {
        this.hide()
        const event = new CustomEvent(keyBinding.name, {bubbles: true, cancelable: true})
        this.activeElement.dispatchEvent(event)
      },
      didCancelSelection: () => {
        this.hide()
      }
    })
    this.selectListView.element.classList.add('command-palette')
  }

  async destroy () {
    await this.selectListView.destroy()
  }

  toggle () {
    if (this.panel && this.panel.isVisible()) {
      this.hide()
      return Promise.resolve()
    } else {
      return this.show()
    }
  }

  async show (showHiddenCommands = false) {
    if (!this.panel) {
      this.panel = atom.workspace.addModalPanel({item: this.selectListView})
    }

    if (!this.preserveLastSearch) {
      this.selectListView.reset()
    } else {
      this.selectListView.refs.queryEditor.selectAll()
    }

    this.activeElement = (document.activeElement === document.body) ? atom.views.getView(atom.workspace) : document.activeElement
    this.keyBindingsForActiveElement = atom.keymaps.findKeyBindings({target: this.activeElement})
    const commandsForActiveElement = atom.commands
        .findCommands({target: this.activeElement})
        .filter(command => showHiddenCommands === !!command.hiddenInCommandPalette)
    commandsForActiveElement.sort((a, b) => a.displayName.localeCompare(b.displayName))
    await this.selectListView.update({items: commandsForActiveElement})

    this.previouslyFocusedElement = document.activeElement
    this.panel.show()
    this.selectListView.focus()
  }

  hide () {
    this.panel.hide()
    if (this.previouslyFocusedElement) {
      this.previouslyFocusedElement.focus()
      this.previouslyFocusedElement = null
    }
  }

  async update (props) {
    if (props.hasOwnProperty('preserveLastSearch')) {
      this.preserveLastSearch = props.preserveLastSearch
    }

    if (props.hasOwnProperty('useAlternateScoring')) {
      this.useAlternateScoring = props.useAlternateScoring
    }
  }

  get fuzz () {
    return this.useAlternateScoring ? fuzzaldrinPlus : fuzzaldrin
  }

  highlightMatchesInElement (text, query, el) {
    const matches = this.fuzz.match(text, query)
    let matchedChars = []
    let lastIndex = 0
    for (const matchIndex of matches) {
      const unmatched = text.substring(lastIndex, matchIndex)
      if (unmatched) {
        if (matchedChars.length > 0) {
          const matchSpan = document.createElement('span')
          matchSpan.classList.add('character-match')
          matchSpan.textContent = matchedChars.join('')
          el.appendChild(matchSpan)
          matchedChars = []
        }

        el.appendChild(document.createTextNode(unmatched))
      }

      matchedChars.push(text[matchIndex])
      lastIndex = matchIndex + 1
    }

    if (matchedChars.length > 0) {
      const matchSpan = document.createElement('span')
      matchSpan.classList.add('character-match')
      matchSpan.textContent = matchedChars.join('')
      el.appendChild(matchSpan)
    }

    const unmatched = text.substring(lastIndex)
    if (unmatched) {
      el.appendChild(document.createTextNode(unmatched))
    }
  }

  filter = (items, query) => {
    if (query.length === 0) {
      return items
    }

    const scoredItems = []
    for (const item of items) {
      let score = this.fuzz.score(item.displayName, query)
      if (item.tags) {
        score += item.tags.reduce(
          (currentScore, tag) => currentScore + this.fuzz.score(tag, query),
          0
        )
      }
      if (item.description) {
        score += this.fuzz.score(item.description, query)
      }

      if (score > 0) {
        scoredItems.push({item, score})
      }
    }
    scoredItems.sort((a, b) => b.score - a.score)
    return scoredItems.map((i) => i.item)
  }

  createDescription (description, query) {
    const descriptionEl = document.createElement('div')

    // in case of overflow, give full contents on long hover
    descriptionEl.title = description

    Object.assign(descriptionEl.style, {
      flexGrow: 1,
      flexShrink: 1,
      textOverflow: 'ellipsis',
      whiteSpace: 'nowrap',
      overflow: 'hidden'
    })
    this.highlightMatchesInElement(description, query, descriptionEl)
    return descriptionEl
  }

  createTag (tagText, query) {
    const tagEl = document.createElement('li')
    Object.assign(tagEl.style, {
      borderBottom: 0,
      display: 'inline',
      padding: 0
    })
    this.highlightMatchesInElement(tagText, query, tagEl)
    return tagEl
  }

  createTags (matchingTags, query) {
    const tagsEl = document.createElement('ol')
    Object.assign(tagsEl.style, {
      display: 'inline',
      marginLeft: '4px',
      flexShrink: 0,
      padding: 0
    })

    const introEl = document.createElement('strong')
    introEl.textContent = 'matching tags: '

    tagsEl.appendChild(introEl)
    matchingTags.map(t => this.createTag(t, query)).forEach((tagEl, i) => {
      tagsEl.appendChild(tagEl)
      if (i < matchingTags.length - 1) {
        const commaSpace = document.createElement('span')
        commaSpace.textContent = ', '
        tagsEl.appendChild(commaSpace)
      }
    })
    return tagsEl
  }
}
<commit_msg>cache element items on creation<commit_after>/** @babel */

import SelectListView from 'atom-select-list'
import {humanizeKeystroke} from 'underscore-plus'
import fuzzaldrin from 'fuzzaldrin'
import fuzzaldrinPlus from 'fuzzaldrin-plus'

export default class CommandPaletteView {
  constructor () {
    this.keyBindingsForActiveElement = []
    this.selectedElementCache = new WeakMap()
    this.plainElementCache = new WeakMap()
    this.selectListView = new SelectListView({
      items: [],
      filter: this.filter,
      emptyMessage: 'No matches found',
      elementForItem: (item, {index, selected}) => {
        const query = this.selectListView.getQuery()
        if(query.length === 0) {
          if(selected && this.selectedElementCache.has(item)) {
            return this.selectedElementCache.get(item)
          } else if(!selected && this.plainElementCache.has(item)) {
            return this.plainElementCache.get(item)
          }
        }

        const li = document.createElement('li')
        li.classList.add('event', 'two-lines')
        li.dataset.eventName = item.name

        const rightBlock = document.createElement('div')
        rightBlock.classList.add('pull-right')
        let i = 0
        for (const keyBinding of this.keyBindingsForActiveElement) {
          if (keyBinding.command === item.name) {
            const kbd = document.createElement('kbd')
            kbd.classList.add('key-binding')
            kbd.textContent = humanizeKeystroke(keyBinding.keystrokes)
            rightBlock.appendChild(kbd)
          }
        }
        li.appendChild(rightBlock)

        const leftBlock = document.createElement('div')
        const titleEl = document.createElement('div')
        titleEl.classList.add('primary-line')
        titleEl.title = item.name
        leftBlock.appendChild(titleEl)

        this.highlightMatchesInElement(item.displayName, query, titleEl)

        if (selected) {
          let secondaryEl = document.createElement('div')
          secondaryEl.classList.add('secondary-line')
          secondaryEl.style.display = 'flex'

          if (typeof item.description === 'string') {
            secondaryEl.appendChild(this.createDescription(item.description, query))
          }

          if (Array.isArray(item.tags)) {
            const matchingTags = item.tags
              .map(t => [t, this.fuzz.score(t, query)])
              .filter(([t, s]) => s > 0)
              .sort((a, b) => a.s - b.s)
              .map(([t, s]) => t)

            if (matchingTags.length > 0) {
              secondaryEl.appendChild(this.createTags(matchingTags, query))
            }
          }

          leftBlock.appendChild(secondaryEl)
        }

        li.appendChild(leftBlock)
        if(query.length === 0) {
          if(selected) {
            this.selectedElementCache.set(item, li)
          } else {
            this.plainElementCache.set(item, li)
          }
        }
        return li
      },
      didConfirmSelection: (keyBinding) => {
        this.hide()
        const event = new CustomEvent(keyBinding.name, {bubbles: true, cancelable: true})
        this.activeElement.dispatchEvent(event)
      },
      didCancelSelection: () => {
        this.hide()
      }
    })
    this.selectListView.element.classList.add('command-palette')
  }

  async destroy () {
    await this.selectListView.destroy()
  }

  toggle () {
    if (this.panel && this.panel.isVisible()) {
      this.hide()
      return Promise.resolve()
    } else {
      return this.show()
    }
  }

  async show (showHiddenCommands = false) {
    if (!this.panel) {
      this.panel = atom.workspace.addModalPanel({item: this.selectListView})
    }

    if (!this.preserveLastSearch) {
      this.selectListView.reset()
    } else {
      this.selectListView.refs.queryEditor.selectAll()
    }

    this.activeElement = (document.activeElement === document.body) ? atom.views.getView(atom.workspace) : document.activeElement
    this.keyBindingsForActiveElement = atom.keymaps.findKeyBindings({target: this.activeElement})
    const commandsForActiveElement = atom.commands
        .findCommands({target: this.activeElement})
        .filter(command => showHiddenCommands === !!command.hiddenInCommandPalette)
    commandsForActiveElement.sort((a, b) => a.displayName.localeCompare(b.displayName))
    await this.selectListView.update({items: commandsForActiveElement})

    this.previouslyFocusedElement = document.activeElement
    this.panel.show()
    this.selectListView.focus()
  }

  hide () {
    this.panel.hide()
    if (this.previouslyFocusedElement) {
      this.previouslyFocusedElement.focus()
      this.previouslyFocusedElement = null
    }
  }

  async update (props) {
    if (props.hasOwnProperty('preserveLastSearch')) {
      this.preserveLastSearch = props.preserveLastSearch
    }

    if (props.hasOwnProperty('useAlternateScoring')) {
      this.useAlternateScoring = props.useAlternateScoring
    }
  }

  get fuzz () {
    return this.useAlternateScoring ? fuzzaldrinPlus : fuzzaldrin
  }

  highlightMatchesInElement (text, query, el) {
    const matches = this.fuzz.match(text, query)
    let matchedChars = []
    let lastIndex = 0
    for (const matchIndex of matches) {
      const unmatched = text.substring(lastIndex, matchIndex)
      if (unmatched) {
        if (matchedChars.length > 0) {
          const matchSpan = document.createElement('span')
          matchSpan.classList.add('character-match')
          matchSpan.textContent = matchedChars.join('')
          el.appendChild(matchSpan)
          matchedChars = []
        }

        el.appendChild(document.createTextNode(unmatched))
      }

      matchedChars.push(text[matchIndex])
      lastIndex = matchIndex + 1
    }

    if (matchedChars.length > 0) {
      const matchSpan = document.createElement('span')
      matchSpan.classList.add('character-match')
      matchSpan.textContent = matchedChars.join('')
      el.appendChild(matchSpan)
    }

    const unmatched = text.substring(lastIndex)
    if (unmatched) {
      el.appendChild(document.createTextNode(unmatched))
    }
  }

  filter = (items, query) => {
    if (query.length === 0) {
      return items
    }

    const scoredItems = []
    for (const item of items) {
      let score = this.fuzz.score(item.displayName, query)
      if (item.tags) {
        score += item.tags.reduce(
          (currentScore, tag) => currentScore + this.fuzz.score(tag, query),
          0
        )
      }
      if (item.description) {
        score += this.fuzz.score(item.description, query)
      }

      if (score > 0) {
        scoredItems.push({item, score})
      }
    }
    scoredItems.sort((a, b) => b.score - a.score)
    return scoredItems.map((i) => i.item)
  }

  createDescription (description, query) {
    const descriptionEl = document.createElement('div')

    // in case of overflow, give full contents on long hover
    descriptionEl.title = description

    Object.assign(descriptionEl.style, {
      flexGrow: 1,
      flexShrink: 1,
      textOverflow: 'ellipsis',
      whiteSpace: 'nowrap',
      overflow: 'hidden'
    })
    this.highlightMatchesInElement(description, query, descriptionEl)
    return descriptionEl
  }

  createTag (tagText, query) {
    const tagEl = document.createElement('li')
    Object.assign(tagEl.style, {
      borderBottom: 0,
      display: 'inline',
      padding: 0
    })
    this.highlightMatchesInElement(tagText, query, tagEl)
    return tagEl
  }

  createTags (matchingTags, query) {
    const tagsEl = document.createElement('ol')
    Object.assign(tagsEl.style, {
      display: 'inline',
      marginLeft: '4px',
      flexShrink: 0,
      padding: 0
    })

    const introEl = document.createElement('strong')
    introEl.textContent = 'matching tags: '

    tagsEl.appendChild(introEl)
    matchingTags.map(t => this.createTag(t, query)).forEach((tagEl, i) => {
      tagsEl.appendChild(tagEl)
      if (i < matchingTags.length - 1) {
        const commaSpace = document.createElement('span')
        commaSpace.textContent = ', '
        tagsEl.appendChild(commaSpace)
      }
    })
    return tagsEl
  }
}
<|endoftext|>


<commit_before>/* global text */

/**
 * Determine if a given string contains unicode characters, specified in options
 *
 * @method hasUnicode
 * @memberof axe.commons.text
 * @instance
 * @param {String} str string to verify
 * @param {Object} options config containing which unicode character sets to verify
 * @property {Boolean} options.emoji verify emoji unicode
 * @property {Boolean} options.nonBmp verify nonBmp unicode
 * @property {Boolean} options.punctuations verify punctuations unicode
 * @returns {Boolean}
 */
text.hasUnicode = function hasUnicode(str, options) {
	const { emoji, nonBmp, punctuations } = options;
	if (emoji) {
		return axe.imports.emojiRegexText().test(str);
	}
	if (nonBmp) {
		return (
			getUnicodeNonBmpRegExp().test(str) ||
			getSupplementaryPrivateUseRegExp().test(str)
		);
	}
	if (punctuations) {
		return getPunctuationRegExp().test(str);
	}
	return false;
};

/**
 * Remove specified type(s) unicode characters
 *
 * @method removeUnicode
 * @memberof axe.commons.text
 * @instance
 * @param {String} str string to operate on
 * @param {Object} options config containing which unicode character sets to remove
 * @property {Boolean} options.emoji remove emoji unicode
 * @property {Boolean} options.nonBmp remove nonBmp unicode
 * @property {Boolean} options.punctuations remove punctuations unicode
 * @returns {String}
 */
text.removeUnicode = function removeUnicode(str, options) {
	const { emoji, nonBmp, punctuations } = options;

	if (emoji) {
		str = str.replace(axe.imports.emojiRegexText(), '');
	}
	if (nonBmp) {
		str = str.replace(getUnicodeNonBmpRegExp(), '');
		str = str.replace(getSupplementaryPrivateUseRegExp(), '');
	}
	if (punctuations) {
		str = str.replace(getPunctuationRegExp(), '');
	}

	return str;
};

/**
 * Regex for matching unicode values out of Basic Multilingual Plane (BMP)
 * Reference:
 * - https://github.com/mathiasbynens/regenerate
 * - https://unicode-table.com/
 * - https://mathiasbynens.be/notes/javascript-unicode
 *
 * @returns {RegExp}
 */
function getUnicodeNonBmpRegExp() {
	/**
	 * Regex for matching astral plane unicode
	 * - http://kourge.net/projects/regexp-unicode-block
	 */

	/**
	 * Notes on various unicode planes being used in the regex below:
	 * '\u1D00-\u1D7F'  Phonetic Extensions
	 * '\u1D80-\u1DBF'  Phonetic Extensions Supplement
	 * '\u1DC0-\u1DFF'  Combining Diacritical Marks Supplement
	 * '\u20A0-\u20CF'  Currency symbols
	 * '\u20D0-\u20FF'  Combining Diacritical Marks for Symbols
	 * '\u2100-\u214F'  Letter like symbols
	 * '\u2150-\u218F'  Number forms (eg: Roman numbers)
	 * '\u2190-\u21FF'  Arrows
	 * '\u2200-\u22FF'  Mathematical operators
	 * '\u2300-\u23FF'  Misc Technical
	 * '\u2400-\u243F'  Control pictures
	 * '\u2440-\u245F'  OCR
	 * '\u2460-\u24FF'  Enclosed alpha numerics
	 * '\u2500-\u257F'  Box Drawing
	 * '\u2580-\u259F'  Block Elements
	 * '\u25A0-\u25FF'  Geometric Shapes
	 * '\u2600-\u26FF'  Misc Symbols
	 * '\u2700-\u27BF'  Dingbats
	 * '\uE000-\uF8FF'  Private Use
	 *
	 * Note: plane '\u2000-\u206F' used for General punctuation is excluded as it is handled in -> getPunctuationRegExp
	 */

	return /[\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u20A0-\u20CF\u20D0-\u20FF\u2100-\u214F\u2150-\u218F\u2190-\u21FF\u2200-\u22FF\u2300-\u23FF\u2400-\u243F\u2440-\u245F\u2460-\u24FF\u2500-\u257F\u2580-\u259F\u25A0-\u25FF\u2600-\u26FF\u2700-\u27BF\uE000-\uF8FF]/g;
}

/**
 * Get regular expression for matching punctuations
 *
 * @returns {RegExp}
 */
function getPunctuationRegExp() {
	/**
	 * Reference: http://kunststube.net/encoding/
	 * US-ASCII
	 * -> !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
	 *
	 * General Punctuation block
	 * -> \u2000-\u206F
	 *
	 * Supplemental Punctuation block
	 * Reference: https://en.wikipedia.org/wiki/Supplemental_Punctuation
	 * -> \u2E00-\u2E7F Reference
	 */
	return /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]/g;
}

/**
 * Get regular expression for supplementary private use
 *
 * @returns {RegExp}
 */
function getSupplementaryPrivateUseRegExp() {
	/**
	 * Reference: https://www.unicode.org/charts/PDF/UD800.pdf
	 * https://www.unicode.org/charts/PDF/UDC00.pdf
	 * https://www.unicode.org/charts/PDF/UF0000.pdf
	 */
	return /[\uDB80-\uDBBF][\uDC00-\uDFFD]|(?:[\uDB80-\uDBBE][\uDC00-\uDFFF]|\uDBBF[\uDC00-\uDFFD])/g;
}
<commit_msg>Document which parts of the `getSupplementaryPrivateUseRegExp` regex refer to which unicode range<commit_after>/* global text */

/**
 * Determine if a given string contains unicode characters, specified in options
 *
 * @method hasUnicode
 * @memberof axe.commons.text
 * @instance
 * @param {String} str string to verify
 * @param {Object} options config containing which unicode character sets to verify
 * @property {Boolean} options.emoji verify emoji unicode
 * @property {Boolean} options.nonBmp verify nonBmp unicode
 * @property {Boolean} options.punctuations verify punctuations unicode
 * @returns {Boolean}
 */
text.hasUnicode = function hasUnicode(str, options) {
	const { emoji, nonBmp, punctuations } = options;
	if (emoji) {
		return axe.imports.emojiRegexText().test(str);
	}
	if (nonBmp) {
		return (
			getUnicodeNonBmpRegExp().test(str) ||
			getSupplementaryPrivateUseRegExp().test(str)
		);
	}
	if (punctuations) {
		return getPunctuationRegExp().test(str);
	}
	return false;
};

/**
 * Remove specified type(s) unicode characters
 *
 * @method removeUnicode
 * @memberof axe.commons.text
 * @instance
 * @param {String} str string to operate on
 * @param {Object} options config containing which unicode character sets to remove
 * @property {Boolean} options.emoji remove emoji unicode
 * @property {Boolean} options.nonBmp remove nonBmp unicode
 * @property {Boolean} options.punctuations remove punctuations unicode
 * @returns {String}
 */
text.removeUnicode = function removeUnicode(str, options) {
	const { emoji, nonBmp, punctuations } = options;

	if (emoji) {
		str = str.replace(axe.imports.emojiRegexText(), '');
	}
	if (nonBmp) {
		str = str.replace(getUnicodeNonBmpRegExp(), '');
		str = str.replace(getSupplementaryPrivateUseRegExp(), '');
	}
	if (punctuations) {
		str = str.replace(getPunctuationRegExp(), '');
	}

	return str;
};

/**
 * Regex for matching unicode values out of Basic Multilingual Plane (BMP)
 * Reference:
 * - https://github.com/mathiasbynens/regenerate
 * - https://unicode-table.com/
 * - https://mathiasbynens.be/notes/javascript-unicode
 *
 * @returns {RegExp}
 */
function getUnicodeNonBmpRegExp() {
	/**
	 * Regex for matching astral plane unicode
	 * - http://kourge.net/projects/regexp-unicode-block
	 */

	/**
	 * Notes on various unicode planes being used in the regex below:
	 * '\u1D00-\u1D7F'  Phonetic Extensions
	 * '\u1D80-\u1DBF'  Phonetic Extensions Supplement
	 * '\u1DC0-\u1DFF'  Combining Diacritical Marks Supplement
	 * '\u20A0-\u20CF'  Currency symbols
	 * '\u20D0-\u20FF'  Combining Diacritical Marks for Symbols
	 * '\u2100-\u214F'  Letter like symbols
	 * '\u2150-\u218F'  Number forms (eg: Roman numbers)
	 * '\u2190-\u21FF'  Arrows
	 * '\u2200-\u22FF'  Mathematical operators
	 * '\u2300-\u23FF'  Misc Technical
	 * '\u2400-\u243F'  Control pictures
	 * '\u2440-\u245F'  OCR
	 * '\u2460-\u24FF'  Enclosed alpha numerics
	 * '\u2500-\u257F'  Box Drawing
	 * '\u2580-\u259F'  Block Elements
	 * '\u25A0-\u25FF'  Geometric Shapes
	 * '\u2600-\u26FF'  Misc Symbols
	 * '\u2700-\u27BF'  Dingbats
	 * '\uE000-\uF8FF'  Private Use
	 *
	 * Note: plane '\u2000-\u206F' used for General punctuation is excluded as it is handled in -> getPunctuationRegExp
	 */

	return /[\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u20A0-\u20CF\u20D0-\u20FF\u2100-\u214F\u2150-\u218F\u2190-\u21FF\u2200-\u22FF\u2300-\u23FF\u2400-\u243F\u2440-\u245F\u2460-\u24FF\u2500-\u257F\u2580-\u259F\u25A0-\u25FF\u2600-\u26FF\u2700-\u27BF\uE000-\uF8FF]/g;
}

/**
 * Get regular expression for matching punctuations
 *
 * @returns {RegExp}
 */
function getPunctuationRegExp() {
	/**
	 * Reference: http://kunststube.net/encoding/
	 * US-ASCII
	 * -> !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
	 *
	 * General Punctuation block
	 * -> \u2000-\u206F
	 *
	 * Supplemental Punctuation block
	 * Reference: https://en.wikipedia.org/wiki/Supplemental_Punctuation
	 * -> \u2E00-\u2E7F Reference
	 */
	return /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]/g;
}

/**
 * Get regular expression for supplementary private use
 *
 * @returns {RegExp}
 */
function getSupplementaryPrivateUseRegExp() {
	// 1. High surrogate area (https://www.unicode.org/charts/PDF/UD800.pdf)
	// 2. Low surrogate area (https://www.unicode.org/charts/PDF/UDC00.pdf)
	// 3. Supplementary private use area A (https://www.unicode.org/charts/PDF/UF0000.pdf)
	//
	//             1              2                                  3
	//      ┏━━━━━━┻━━━━━━┓┏━━━━━━┻━━━━━━┓ ┏━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
	return /[\uDB80-\uDBBF][\uDC00-\uDFFD]|(?:[\uDB80-\uDBBE][\uDC00-\uDFFF]|\uDBBF[\uDC00-\uDFFD])/g;
}
<|endoftext|>


<commit_before>window.ynabToolKit = new function() {

	this.debugNodes = false,

	this.featureOptions = {},

	// setting up a single mutationObserver that can be called by each feature
	this.actOnChange = function() {

		MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

		var observer = new MutationObserver(function(mutations, observer) {
			// Loop through all the mutations and nodes to see if navigation area was added

			if (ynabToolKit.debugNodes) {
				console.log('NEW NODES');
			}

			mutations.forEach(function(mutation) {
				var newNodes = mutation.target;
				if (ynabToolKit.debugNodes) {
					console.log(newNodes);
				}

				var $nodes = $(newNodes); // jQuery set
				$nodes.each(function() {
					var $node = $(this);

						// Changes are detected in the credit balance master category
						if ($node.hasClass("budget-table-cell-available-div")) {

							if ( ynabToolKit.featureOptions.checkCreditBalances ){
								ynabToolKit.checkCreditBalances();
							}
							if ( ynabToolKit.featureOptions.highlightNegativesNegative ){
								ynabToolKit.highlightNegativesNegative();
							}

						} else

						// Maybe we find the screen containing the account balances again
						if ($node.hasClass('budget-table-row')) {

							if ( ynabToolKit.featureOptions.checkCreditBalances ){
								ynabToolKit.checkCreditBalances();
							}
							if ( ynabToolKit.featureOptions.highlightNegativesNegative ){
								ynabToolKit.highlightNegativesNegative();
							}
						}

						// We found a modal pop-up
						if ($node.hasClass( "options-shown")) {

							if ( ynabToolKit.featureOptions.removeZeroCategories ) {
								ynabToolKit.removeZeroCategories(); // do something
							}
						}

				}); // each node mutation event

			}); // each mutation event

			if (ynabToolKit.debugNodes) {
				console.log('###')
			}

		});

		observer.observe($('.ember-view.layout')[0], {
			subtree : true,
			childList : true,
			characterData : true,
			attributeFilter : [ 'class' ]
		});
	};

}; // end new ynabToolKit function

(function poll() {
    if (typeof Em !== 'undefined' && typeof Ember !== 'undefined'
          && typeof $ !== 'undefined' && $('.ember-view.layout').length) {

    	// Run each activated feature once once the DOM is ready
    	if ( ynabToolKit.featureOptions.checkCreditBalances ) {
    	  ynabToolKit.checkCreditBalances(); // check initial state of credit balances
    	}

    	if ( ynabToolKit.featureOptions.highlightNegativesNegative ){
    		ynabToolKit.highlightNegativesNegative(); // set consistent colour for all negative balances
    	}

    	// initialize the mutation observer so we don't need to use setTimeout()
        ynabToolKit.actOnChange();
    } else {
       setTimeout(poll, 250);
    }
 })();

<commit_msg>Polished and updated some basic comments for actOnChange script<commit_after>window.ynabToolKit = new function() {

	// Set 'ynabToolKit.debugNodes = true' to print changes the mutationObserver sees
	// during page interactions and updates to the developer tools console.
	this.debugNodes = false,

	// This variable is populated by each active script loaded inside the ynabToolKit object
	this.featureOptions = {},

	// Setting up a single mutationObserver that can be called by each feature
	this.actOnChange = function() {

		MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

		var observer = new MutationObserver(function(mutations, observer) {

			if (ynabToolKit.debugNodes) {
				console.log('NEW NODES');
			}

			mutations.forEach(function(mutation) {
				var newNodes = mutation.target;
				if (ynabToolKit.debugNodes) {
					console.log(newNodes);
				}

				var $nodes = $(newNodes); // jQuery set
				$nodes.each(function() {
					var $node = $(this);

						// Changes are detected in the category balances
						if ($node.hasClass("budget-table-cell-available-div")) {

							if ( ynabToolKit.featureOptions.checkCreditBalances ){
								ynabToolKit.checkCreditBalances();
							}
							if ( ynabToolKit.featureOptions.highlightNegativesNegative ){
								ynabToolKit.highlightNegativesNegative();
							}

						} else

						// The user has returned back to the budget screen
						if ($node.hasClass('budget-table-row')) {

							if ( ynabToolKit.featureOptions.checkCreditBalances ){
								ynabToolKit.checkCreditBalances();
							}
							if ( ynabToolKit.featureOptions.highlightNegativesNegative ){
								ynabToolKit.highlightNegativesNegative();
							}
						}

						// We found a modal pop-up
						if ($node.hasClass( "options-shown")) {

							if ( ynabToolKit.featureOptions.removeZeroCategories ) {
								ynabToolKit.removeZeroCategories();
							}
						}

				}); // each node mutation event

			}); // each mutation event

			if (ynabToolKit.debugNodes) {
				console.log('###')
			}

		});

		// This finally says 'Watch for changes' and only needs to be called the one time
		observer.observe($('.ember-view.layout')[0], {
			subtree : true,
			childList : true,
			characterData : true,
			attributeFilter : [ 'class' ]
		});
	};

}; // end ynabToolKit object

// This poll() function will only need to run until we find that the DOM is ready
// For certain functions, we may run them once automatically on page load before 'changes' occur
(function poll() {
    if (typeof Em !== 'undefined' && typeof Ember !== 'undefined'
          && typeof $ !== 'undefined' && $('.ember-view.layout').length) {

    	if ( ynabToolKit.featureOptions.checkCreditBalances ) {
    	  ynabToolKit.checkCreditBalances();
    	}

    	if ( ynabToolKit.featureOptions.highlightNegativesNegative ){
    		ynabToolKit.highlightNegativesNegative();
    	}

    	// Activate the mutationObserver so we don't need to use setTimeout() anymore
        ynabToolKit.actOnChange();
    } else {
       setTimeout(poll, 250);
    }
 })();

<|endoftext|>


<commit_before>import ErrorStore from '../stores/ErrorStore'
import React from 'react'

export default class ErrorPage extends React.Component {

  constructor() {
    this.state = ErrorStore.getState()

    this._onChange = () => {
      this.setState(ErrorStore.getState())
    }
  }

  componentDidMount() {
    ErrorStore.listen(this._onChange)
  }

  componentDidUnmount() {
    ErrorStore.unlisten(this._onChange)
  }

  render() {
    return

{this.state.status}

{this.state.message}

      {this.state.stack
        ?
{this.state.stack}

        : null}

  }

}
<commit_msg>Clean up the error pages.<commit_after>import Centered from './Centered'
import ErrorStore from '../stores/ErrorStore'
import React from 'react'
import Spinner from './Spinner'

export default class ErrorPage extends React.Component {

  constructor() {
    this.state = ErrorStore.getState()

    this._onChange = () => {
      this.setState(ErrorStore.getState())
    }
  }

  componentDidMount() {
    ErrorStore.listen(this._onChange)
  }

  componentDidUnmount() {
    ErrorStore.unlisten(this._onChange)
  }

  render() {
    return <Centered ver>

      <Spinner dir="up" />

FilePizza - {this.state.status}

{this.state.message}

      {this.state.stack
        ?
{this.state.stack}

        : null}

    </Centered>
  }

}
<|endoftext|>


<commit_before>import React from'React'
import { View, TextInput, TouchableOpacity, Text } from 'react-native'
import DefaultText from '../DefaultText'
import Colors from '@Colors/colors'
import merge from '../../util/merge'
import style from './UnlockAppAlertStyle'
import { unlockCharNo } from '../../store/reducer/login'

export const maxAttempts = 3;

const passwordValid = (password) => password && password.indexOf(' ') === -1 && password.length === unlockCharNo

class UnlockAppAlert extends React.Component {
    constructor() {
        super()
        this.state = {
            pass: ''
        }
    }

    _onChangeText (value) {
        this.setState({pass: value })
    }

    render() {
        return (
            <View style={style.wrapper}>
                <View style={style.container}>
                    <Text style={style.instructionText}>
                        To protect your privacy, we have locked the login area.
                        To unlock, please enter the last {unlockCharNo} characters of your password. Or chose "Logout" to just browse
                    </Text>
                    { this.props.error &&
                        <View>
                            <Text style={merge(style.errorText, { paddingTop: 10 })}>
                                You have {maxAttempts - this.props.failedAttempts} attempts left.
                            </Text>
                            <Text style={merge(style.errorText, { paddingBottom: 10 })}>
                                The characters entered don't match.
                            </Text>
                        </View>
                    }
                    <View style={style.form}/>
                    <TextInput
                        placeholder={'Unlock code'}
                        autoFocus={true}
                        value={this.state.pass}
                        accessibilityLabel={'Password'}
                        style={style.input}
                        placeholderTextColor={Colors.gray4}
                        secureTextEntry={true}
                        underlineColorAndroid={Colors.transparent}
                        onChangeText={(value) => this._onChangeText(value)}>
                    </TextInput>
                    <View style={{flexDirection: 'row'}}>
                        <TouchableOpacity
                            style={merge(style.buttonContainer, { backgroundColor: Colors.primaryBlue, flex: 1, marginRight: 2 })}
                            onPress={() => this.props.logout && this.props.logout()}>
                            <DefaultText style={merge(style.buttonText, { color: 'white' })}>
                                Logout
                            </DefaultText>
                        </TouchableOpacity>
                        <TouchableOpacity
                            style={merge(style.buttonContainer, { flex: 1, marginLeft: 2, backgroundColor: passwordValid(this.state.pass) ? Colors.primaryBlue : Colors.offWhite})}
                            onPress={() => passwordValid(this.state.pass) && this.props.checkPass(this.state.pass)}>
                            <DefaultText style={merge(style.buttonText, { color: passwordValid(this.state.pass) ? 'white' : 'black' })}>
                                Unlock
                            </DefaultText>
                        </TouchableOpacity>
                    </View>
                </View>
            </View>
        )
    }
}

export default UnlockAppAlert
<commit_msg>improve unlock screen (#33)<commit_after>import React from'React'
import { View, TextInput, TouchableOpacity, Text } from 'react-native'
import DefaultText from '../DefaultText'
import Colors from '@Colors/colors'
import merge from '../../util/merge'
import style from './UnlockAppAlertStyle'
import { unlockCharNo } from '../../store/reducer/login'

export const maxAttempts = 3;

const passwordValid = (password) => password && password.indexOf(' ') === -1 && password.length === unlockCharNo

class UnlockAppAlert extends React.Component {
    constructor() {
        super()
        this.state = {
            pass: ''
        }
    }

    _onChangeText (value) {
        this.setState({pass: value })
    }

    render() {
        return (
            <View style={style.wrapper}>
                <View style={style.container}>
                    <Text style={style.instructionText}>
                        To protect your privacy, we have locked the login area.
                        To unlock, please enter the last {unlockCharNo} characters of your password. Or chose "Logout" to just browse
                    </Text>
                    { this.props.error &&
                        <View>
                            <Text style={merge(style.errorText, { paddingTop: 10 })}>
                                You have {maxAttempts - this.props.failedAttempts} attempts left.
                            </Text>
                            <Text style={merge(style.errorText, { paddingBottom: 10 })}>
                                The characters entered don't match.
                            </Text>
                        </View>
                    }
                    <View style={style.form}/>
                    <TextInput
                        placeholder={'Unlock code'}
                        autoFocus={true}
                        value={this.state.pass}
                        maxLength={unlockCharNo}
                        accessibilityLabel={'Unlock code'}
                        style={style.input}
                        placeholderTextColor={Colors.gray4}
                        secureTextEntry={true}
                        underlineColorAndroid={Colors.transparent}
                        onChangeText={(value) => this._onChangeText(value)}>
                    </TextInput>
                    <View style={{flexDirection: 'row'}}>
                        <TouchableOpacity
                            style={merge(style.buttonContainer, { backgroundColor: Colors.primaryBlue, flex: 1, marginRight: 2 })}
                            onPress={() => this.props.logout && this.props.logout()}>
                            <DefaultText style={merge(style.buttonText, { color: 'white' })}>
                                Logout
                            </DefaultText>
                        </TouchableOpacity>
                        <TouchableOpacity
                            style={merge(style.buttonContainer, { flex: 1, marginLeft: 2, backgroundColor: passwordValid(this.state.pass) ? Colors.primaryBlue : Colors.offWhite})}
                            onPress={() => passwordValid(this.state.pass) && this.props.checkPass(this.state.pass)}>
                            <DefaultText style={merge(style.buttonText, { color: passwordValid(this.state.pass) ? 'white' : 'black' })}>
                                Unlock
                            </DefaultText>
                        </TouchableOpacity>
                    </View>
                </View>
            </View>
        )
    }
}

export default UnlockAppAlert
<|endoftext|>


<commit_before>import React from 'react'
import Place from './Place'

const GlobalFilter = ({
    places,
    mapProperties,
    onPlaceClick,
    onOpacityChange,
    onContourChange,
    onKeyUpSearch,
}) => {

    let input

    // treat opacity tooltip value
    let opacity

    if (!mapProperties) {
        opacity = .5
    } else {
        opacity = mapProperties.opacity
    }
    if(isNaN(opacity)) {
        opacity = .5
    }
    opacity *= 100

    const handleOpacityChange = (e) => {
        onOpacityChange(e.target.value)
    }
    const handleTypeChange = (e) => {
        onContourChange(e.target.value)
    }
    const handleKeyUpSearch = (val) => {
        onKeyUpSearch(val)
    }

    return (

            <form>
                <fieldset className="global-filter-form">

Busca detalhada

                    <fieldset className="global-filter-form--inpusearch">
                        <input
                            id="searchField"
                            type="search"
                            placeholder="Insira o nome da área"
                            ref={node => {input = node;}}
                            onKeyUp={() => {
                                handleKeyUpSearch(input.value)
                            }}
                        />

                    </fieldset>
                    <fieldset className="global-filter-form--selectiontype">
                        Tipo de seleção:

                            <input
                                name="selectionType"
                                type="radio"
                                id="selectionType1"
                                value="borda"
                                defaultChecked="checked"
                                onClick={(e) => handleTypeChange(e)} />
                            Demarcada

                            <input
                                name="selectionType"
                                type="radio"
                                id="selectionType2"
                                value="opaco"
                                onClick={(e) => handleTypeChange(e)} />
                            Isolada

                    </fieldset>

                    <fieldset className="global-filterform--selectopacity">
                         Opacidade da seleção
                            <input className="opacitySelection" type="range" min="0" max="10" defaultValue="5" onChange={(e) => handleOpacityChange(e)}></input>

{opacity}%

                    </fieldset>
                </fieldset>
            </form>

Áreas dos CRAAIs

                    {places? places.map(p => <Place onPlaceClick={onPlaceClick} key={p.id} place={p}/>) : null}

    )
}

export default GlobalFilter
<commit_msg>resolve bug in show opacity slider<commit_after>import React from 'react'
import Place from './Place'

const GlobalFilter = ({
    places,
    mapProperties,
    onPlaceClick,
    onOpacityChange,
    onContourChange,
    onKeyUpSearch,
}) => {

    let input

    // treat opacity tooltip value
    let opacity

    if (!mapProperties) {
        opacity = .5
    } else {
        opacity = mapProperties.opacity
    }
    if(isNaN(opacity)) {
        opacity = .5
    }
    opacity *= 100

    const handleOpacityChange = (e) => {
        onOpacityChange(e.target.value)
    }
    const handleTypeChange = (e) => {
        onContourChange(e.target.value)
    }
    const handleKeyUpSearch = (val) => {
        onKeyUpSearch(val)
    }

    return (

            <form>
                <fieldset className="global-filter-form">

Busca detalhada

                    <fieldset className="global-filter-form--inpusearch">
                        <input
                            id="searchField"
                            type="search"
                            placeholder="Insira o nome da área"
                            ref={node => {input = node;}}
                            onKeyUp={() => {
                                handleKeyUpSearch(input.value)
                            }}
                        />

                    </fieldset>
                    <fieldset className="global-filter-form--selectiontype">
                        Tipo de seleção:

                            <input
                                name="selectionType"
                                type="radio"
                                id="selectionType1"
                                value="borda"
                                defaultChecked="checked"
                                onClick={(e) => handleTypeChange(e)} />
                            Demarcada

                            <input
                                name="selectionType"
                                type="radio"
                                id="selectionType2"
                                value="opaco"
                                onClick={(e) => handleTypeChange(e)} />
                            Isolada

                    </fieldset>

                    <fieldset className="global-filterform--selectopacity">
                         Opacidade da seleção
                            <input className="opacitySelection" type="range" min="0" max="10" defaultValue="5" onChange={(e) => handleOpacityChange(e)}></input>

{opacity}%

                    </fieldset>
                </fieldset>
            </form>

Áreas dos CRAAIs

                    {places? places.map(p => <Place onPlaceClick={onPlaceClick} key={p.id} place={p}/>) : null}

    )
}

export default GlobalFilter
<|endoftext|>