<commit_before>import { BaseComponent } from '../base/Base';
import Choices from 'choices.js';
import Formio from '../../formio';
import _each from 'lodash/each';
import _get from 'lodash/get';
import _isEmpty from 'lodash/isEmpty';
import _isArray from 'lodash/isArray';
export class SelectComponent extends BaseComponent {
  constructor(component, options, data) {
    super(component, options, data);

    // If they wish to refresh on a value, then add that here.
    if (this.component.refreshOn) {
      this.on('change', (event) => {
        if (this.component.refreshOn === 'data') {
          this.refreshItems();
        }
        else if (event.changed.component.key === this.component.refreshOn) {
          this.refreshItems();
        }
      });
    }
  }

  refreshItems() {
    this.updateItems();
    if (this.component.clearOnRefresh) {
      this.setValue(null);
    }
  }

  elementInfo() {
    let info = super.elementInfo();
    info.type = 'select';
    info.changeEvent = 'change';
    if (info.attr.placeholder) {
      delete info.attr.placeholder;
    }
    return info;
  }

  createWrapper() {
    return false;
  }

  itemTemplate(data) {
    return this.component.template ? this.interpolate(this.component.template, {item: data}) : data.label;
  }

  itemValue(data) {
    return this.component.valueProperty ? _get(data, this.component.valueProperty) : data;
  }

  setItems(items) {
    if (!this.choices) {
      return;
    }
    this.choices._clearChoices();

    // If they provided select values, then we need to get them instead.
    if (this.component.selectValues) {
      items = _get(items, this.component.selectValues);
    }

    // Iterate through each of the items.
    _each(items, (item) => {

      // Add the choice to the select list.
      this.choices._addChoice(this.itemValue(item), this.itemTemplate(item));
    });

    // If a value is provided, then select it.
    if (this.value) {
      this.setValue(this.value, true);
    }
  }

  loadItems(url, input, headers, options) {
    let query = (this.component.dataSrc === 'url') ? {} : {
      limit: 100,
      skip: 0
    };

    // Allow for url interpolation.
    url = this.interpolate(url, {
      data: this.data,
      formioBase: Formio.getBaseUrl()
    });

    // Add search capability.
    if (this.component.searchField && input) {
      query[this.component.searchField] = input;
    }

    // Add filter capability
    if (this.component.filter) {
      let filter = this.interpolate(this.component.filter, {data: this.data});
      url += ((url.indexOf('?') === -1) ? '?' : '&') + filter;
    }

    // If they wish to return only some fields.
    if (this.component.selectFields) {
      query.select = this.component.selectFields;
    }

    if (!_isEmpty(query)) {
      // Add the query string.
      url += '?' + Formio.serialize(query);
    }

    // Make the request.
    Formio.request(url, null, null, headers, options)
      .then((response) => this.setItems(response))
      .catch((err) => {
        this.events.emit('formio.error', err);
        console.warn('Unable to load resources for ' + this.component.key);
      })
  }

  updateItems() {
    switch(this.component.dataSrc) {
      case 'values':
        this.component.valueProperty = 'value';
        this.setItems(this.component.data.values);
        break;
      case 'json':
        try {
          if (typeof this.component.data.json == 'string') {
            this.setItems(JSON.parse(this.component.data.json));
          }
          else {
            this.setItems(this.component.data.json);
          }
        }
        catch (err) {
          console.warn('Unable to parse JSON for ' + this.component.key);
        }
        break;
      case 'resource':
        let resourceUrl = this.options.formio ? this.options.formio.formsUrl : Formio.getProjectUrl() + '/form';
        resourceUrl += ('/' + this.component.data.resource + '/submission');

        try {
          this.loadItems(resourceUrl);
        }
        catch (err) {
          console.warn('Unable to load resources for ' + this.component.key);
        }
        break;
      case 'url':
        let url = this.component.data.url;
        if (url.substr(0, 1) === '/') {
          url = Formio.getBaseUrl() + this.component.data.url;
        }

        this.loadItems(url, null, new Headers(), {
          noToken: true
        });
        break;
    }
  }

  addInput(input, container) {
    super.addInput(input, container, true);
    if (this.component.multiple) {
      input.setAttribute('multiple', true);
    }
    this.choices = new Choices(input, {
      removeItemButton: true,
      itemSelectText: '',
      classNames: {
        containerOuter: 'choices form-group formio-choices',
        containerInner: 'form-control'
      },
      shouldSort: false
    });

    // Create a pseudo-placeholder.
    if (
      this.component.placeholder &&
      !this.choices.placeholderElement
    ) {
      this.placeholder = this.ce('span', {
        class: 'formio-placeholder'
      }, [
        this.text(this.component.placeholder)
      ]);

      // Prepend the placeholder.
      this.choices.containerInner.insertBefore(this.placeholder, this.choices.containerInner.firstChild);
      input.addEventListener('addItem', () => {
        this.placeholder.style.visibility = 'hidden';
      }, false);
      input.addEventListener('removeItem', () => {
        let value = this.getValue();
        if (!value || !value.length) {
          this.placeholder.style.visibility = 'visible';
        }
      }, false);
    }

    if (this.disabled) {
      this.choices.disable();
    }
    this.updateItems();
  }

  set disabled(disabled) {
    super.disabled = disabled;
    if (!this.choices) {
      return;
    }
    if (disabled) {
      this.choices.disable();
    }
    else {
      this.choices.enable();
    }
  }

  getValue() {
    if (!this.choices) {
      return;
    }
    return this.choices.getValue(true);
  }

  setValue(value, flags) {
    flags = this.getFlags.apply(this, arguments);
    this.value = value;
    if (this.choices) {
      if (value && this.choices.store) {
        // Search for the choice.
        const choices = this.choices.store.getChoices();
        const foundChoice = choices.find((choice) => {
          // For resources we may have two different instances of the same resource
          // Unify them so we don't have two copies of the same thing in the dropdown
          // and so the correct resource gets selected in the first place
          if (choice.value._id && value._id && choice.value._id === value._id) {
            value = choice.value;
          }
          return choice.value === value;
        });

        // If it is not found, then add it.
        if (!foundChoice) {
          this.choices._addChoice(this.itemValue(value), this.itemTemplate(value));
        }
      }

      // Now set the value.
      if (value) {
        setTimeout(() => this.choices.setValueByChoice(_isArray(value) ? value : [value]), 10);
      }
      else {
        this.choices.removeActiveItems();
      }
    }
    this.updateValue(flags);
  }

  /**
   * Check if a component is eligible for multiple validation
   *
   * @return {boolean}
   */
  validateMultiple(value) {
    // Select component will contain one input when flagged as multiple.
    return false;
  }

  /**
   * Ouput this select dropdown as a string value.
   * @return {*}
   */
  asString(value) {
    value = value || this.getValue();
    value = (typeof value !== 'object') ? {label: value} : value;
    return this.itemTemplate(value);
  }

  destroy() {
    if (this.choices) {
      this.choices.destroy();
    }
  }
}
<commit_msg>Added the headers to the request urls.<commit_after>import { BaseComponent } from '../base/Base';
import Choices from 'choices.js';
import Formio from '../../formio';
import _each from 'lodash/each';
import _get from 'lodash/get';
import _isEmpty from 'lodash/isEmpty';
import _isArray from 'lodash/isArray';
export class SelectComponent extends BaseComponent {
  constructor(component, options, data) {
    super(component, options, data);

    // If they wish to refresh on a value, then add that here.
    if (this.component.refreshOn) {
      this.on('change', (event) => {
        if (this.component.refreshOn === 'data') {
          this.refreshItems();
        }
        else if (event.changed.component.key === this.component.refreshOn) {
          this.refreshItems();
        }
      });
    }
  }

  refreshItems() {
    this.updateItems();
    if (this.component.clearOnRefresh) {
      this.setValue(null);
    }
  }

  elementInfo() {
    let info = super.elementInfo();
    info.type = 'select';
    info.changeEvent = 'change';
    if (info.attr.placeholder) {
      delete info.attr.placeholder;
    }
    return info;
  }

  createWrapper() {
    return false;
  }

  itemTemplate(data) {
    return this.component.template ? this.interpolate(this.component.template, {item: data}) : data.label;
  }

  itemValue(data) {
    return this.component.valueProperty ? _get(data, this.component.valueProperty) : data;
  }

  setItems(items) {
    if (!this.choices) {
      return;
    }
    this.choices._clearChoices();

    // If they provided select values, then we need to get them instead.
    if (this.component.selectValues) {
      items = _get(items, this.component.selectValues);
    }

    // Iterate through each of the items.
    _each(items, (item) => {

      // Add the choice to the select list.
      this.choices._addChoice(this.itemValue(item), this.itemTemplate(item));
    });

    // If a value is provided, then select it.
    if (this.value) {
      this.setValue(this.value, true);
    }
  }

  loadItems(url, input, headers, options) {
    let query = (this.component.dataSrc === 'url') ? {} : {
      limit: 100,
      skip: 0
    };

    // Allow for url interpolation.
    url = this.interpolate(url, {
      data: this.data,
      formioBase: Formio.getBaseUrl()
    });

    // Add search capability.
    if (this.component.searchField && input) {
      query[this.component.searchField] = input;
    }

    // Add filter capability
    if (this.component.filter) {
      let filter = this.interpolate(this.component.filter, {data: this.data});
      url += ((url.indexOf('?') === -1) ? '?' : '&') + filter;
    }

    // If they wish to return only some fields.
    if (this.component.selectFields) {
      query.select = this.component.selectFields;
    }

    if (!_isEmpty(query)) {
      // Add the query string.
      url += '?' + Formio.serialize(query);
    }

    // Make the request.
    Formio.request(url, null, null, headers, options)
      .then((response) => this.setItems(response))
      .catch((err) => {
        this.events.emit('formio.error', err);
        console.warn('Unable to load resources for ' + this.component.key);
      })
  }

  /**
   * Get the request headers for this select dropdown.
   */
  get requestHeaders() {
    // Create the headers object.
    let headers = new Headers();

    // Add custom headers to the url.
    if (this.component.data && this.component.data.headers) {
      try {
        _each(this.component.data.headers, (header) => {
          if (header.key) {
            headers.set(header.key, header.value);
          }
        });
      }
      catch (err) {
        console.warn(err.message);
      }
    }

    return headers;
  }

  updateItems() {
    if (!this.component.data) {
      console.warn('Select component ' + this.component.key + ' does not have data configuration.');
      return;
    }

    switch(this.component.dataSrc) {
      case 'values':
        this.component.valueProperty = 'value';
        this.setItems(this.component.data.values);
        break;
      case 'json':
        try {
          if (typeof this.component.data.json == 'string') {
            this.setItems(JSON.parse(this.component.data.json));
          }
          else {
            this.setItems(this.component.data.json);
          }
        }
        catch (err) {
          console.warn('Unable to parse JSON for ' + this.component.key);
        }
        break;
      case 'resource':
        let resourceUrl = this.options.formio ? this.options.formio.formsUrl : Formio.getProjectUrl() + '/form';
        resourceUrl += ('/' + this.component.data.resource + '/submission');

        try {
          this.loadItems(resourceUrl, null, this.requestHeaders);
        }
        catch (err) {
          console.warn('Unable to load resources for ' + this.component.key);
        }
        break;
      case 'url':
        let url = this.component.data.url;
        if (url.substr(0, 1) === '/') {
          url = Formio.getBaseUrl() + this.component.data.url;
        }

        this.loadItems(url, null, this.requestHeaders, {
          noToken: true
        });
        break;
    }
  }

  addInput(input, container) {
    super.addInput(input, container, true);
    if (this.component.multiple) {
      input.setAttribute('multiple', true);
    }
    this.choices = new Choices(input, {
      removeItemButton: true,
      itemSelectText: '',
      classNames: {
        containerOuter: 'choices form-group formio-choices',
        containerInner: 'form-control'
      },
      shouldSort: false
    });

    // Create a pseudo-placeholder.
    if (
      this.component.placeholder &&
      !this.choices.placeholderElement
    ) {
      this.placeholder = this.ce('span', {
        class: 'formio-placeholder'
      }, [
        this.text(this.component.placeholder)
      ]);

      // Prepend the placeholder.
      this.choices.containerInner.insertBefore(this.placeholder, this.choices.containerInner.firstChild);
      input.addEventListener('addItem', () => {
        this.placeholder.style.visibility = 'hidden';
      }, false);
      input.addEventListener('removeItem', () => {
        let value = this.getValue();
        if (!value || !value.length) {
          this.placeholder.style.visibility = 'visible';
        }
      }, false);
    }

    if (this.disabled) {
      this.choices.disable();
    }
    this.updateItems();
  }

  set disabled(disabled) {
    super.disabled = disabled;
    if (!this.choices) {
      return;
    }
    if (disabled) {
      this.choices.disable();
    }
    else {
      this.choices.enable();
    }
  }

  getValue() {
    if (!this.choices) {
      return;
    }
    return this.choices.getValue(true);
  }

  setValue(value, flags) {
    flags = this.getFlags.apply(this, arguments);
    this.value = value;
    if (this.choices) {
      if (value && this.choices.store) {
        // Search for the choice.
        const choices = this.choices.store.getChoices();
        const foundChoice = choices.find((choice) => {
          // For resources we may have two different instances of the same resource
          // Unify them so we don't have two copies of the same thing in the dropdown
          // and so the correct resource gets selected in the first place
          if (choice.value._id && value._id && choice.value._id === value._id) {
            value = choice.value;
          }
          return choice.value === value;
        });

        // If it is not found, then add it.
        if (!foundChoice) {
          this.choices._addChoice(this.itemValue(value), this.itemTemplate(value));
        }
      }

      // Now set the value.
      if (value) {
        setTimeout(() => this.choices.setValueByChoice(_isArray(value) ? value : [value]), 10);
      }
      else {
        this.choices.removeActiveItems();
      }
    }
    this.updateValue(flags);
  }

  /**
   * Check if a component is eligible for multiple validation
   *
   * @return {boolean}
   */
  validateMultiple(value) {
    // Select component will contain one input when flagged as multiple.
    return false;
  }

  /**
   * Ouput this select dropdown as a string value.
   * @return {*}
   */
  asString(value) {
    value = value || this.getValue();
    value = (typeof value !== 'object') ? {label: value} : value;
    return this.itemTemplate(value);
  }

  destroy() {
    if (this.choices) {
      this.choices.destroy();
    }
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * 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 Q = require("q"),
        photoshop = require("./photoshop"),
        util = require("util"),
        xpm = require("./xpm");

    var _instanceCount = 0;

    var RE_PLUGIN_FOLDER_NAME_MATCHER = /\.generate$/;

    var REQUEST_TIMEOUT = 5000; // milliseconds. TODO: Make configurable

    function rejectAfter(deferred, timeout) {
        var rejectTimer = setTimeout(function () {
            rejectTimer = null;
            deferred.reject(new Error("timeout"));
        }, timeout);

        deferred.promise.finally(function () {
            if (rejectTimer) {
                clearTimeout(rejectTimer);
            }
        });
    }

    function Generator() {
        if (!this instanceof Generator) {
            return new Generator();
        }
        // TODO: declare these as prototype properties and document types
        this._photoshop = null;
        this._instanceID = _instanceCount++;
        this._channel = require("postal")().channel("generator-" + (this._instanceID));
        this._jsMessageDeferreds = [];
        this._pixmapMessageDeferreds = [];
    }

    function createGenerator() {
        return new Generator();
    }

    Generator.prototype.start = function (options) {
        var self = this;

        function connectToPhotoshop() {
            var connectionDeferred = Q.defer();
            self._photoshop = photoshop.createClient(options);
            self._photoshop.once("connect", function () {
                connectionDeferred.resolve(self);
            });

            self._photoshop.on("error", function (err) {
                // TODO: Gracefully shut down
                self.publish("photoshop.error", err);
            });

            self._photoshop.on("communicationsError", function (err, rawMessage) {
                self.publish("photoshop.error.communcationsError", {error: err, rawMessage: rawMessage});
            });

            self._photoshop.on("message", function (messageID, parsedValue) { // ,rawMessage)
                self.publish("photoshop.message", {id: messageID, body: parsedValue});
                if (self._jsMessageDeferreds[messageID]) {
                    self._jsMessageDeferreds[messageID].resolve(parsedValue);
                }
            });

            self._photoshop.on("info", function (info) {
                self.publish("photoshop.info", info);
            });

            self._photoshop.on("event", function (messageID, eventName, parsedValue) { // , rawMessage)
                self.publish("photoshop.event." + eventName, parsedValue);
            });

            self._photoshop.on("pixmap", function (messageID, messageBody) { // , rawMessage)
                self.publish("photoshop.pixmap", {id: messageID});
                if (self._pixmapMessageDeferreds[messageID]) {
                    self._pixmapMessageDeferreds[messageID].resolve(messageBody);
                }
            });

            return connectionDeferred.promise;
        }

        return (connectToPhotoshop().then(
            function () {
                self.subscribeToPhotoshopEvents(
                    ["imageChanged",
                    "currentDocumentChanged",
                    "save",
                    "generatorMenuChanged"]
                );

                self.getPhotoshopPath().then(
                    function (p) { // success
                        self.publish("generator.info.psPath", p);
                        self._photoshop._applicationPath = p;
                    },
                    function (err) { // error
                        self.publish("generator.info.psPath", "error retrieving: " + err);
                    }
                );
            })
        );

    };

    // Returns a promise that resolves to the ID of the sent message
    // Does NOT return a promise that resolves to the response of the send.
    // That's why this is a private method.
    Generator.prototype._executeJSXFile = function (path, params) {
        var resolve = require("path").resolve,
            readFile = require("fs").readFile;

        var self = this,
            deferred = Q.defer();

        var paramsString = "null";
        if (params) {
            try {
                paramsString = JSON.stringify(params);
            } catch (jsonError) {
                deferred.reject(jsonError);
            }
        }

        if (deferred.promise.isPending()) {
            readFile(resolve(__dirname, path), "utf8", function (err, data) {
                if (err) {
                    deferred.reject(err);
                } else {
                    data = "var params = " + paramsString + ";\n" + data;
                    deferred.resolve(self._photoshop.sendCommand(data));
                }
            });
        }

        return deferred.promise;
    };

    Generator.prototype.evaluateJSXFile = function (path, params) {
        var self = this,
            evaluationDeferred = Q.defer();
        rejectAfter(evaluationDeferred, REQUEST_TIMEOUT);

        self._executeJSXFile(path, params).then(
            function (id) {
                self._jsMessageDeferreds[id] = evaluationDeferred;
                evaluationDeferred.promise.finally(function () { delete self._jsMessageDeferreds[id]; });
            }, function (err) {
                evaluationDeferred.reject(err);
            }
        );
        return evaluationDeferred.promise;
    };

    Generator.prototype.evaluateJSXString = function (s) {
        var self = this,
            evaluationDeferred = Q.defer();
        rejectAfter(evaluationDeferred, REQUEST_TIMEOUT);

        var id = self._photoshop.sendCommand(s);
        self._jsMessageDeferreds[id] = evaluationDeferred;

        evaluationDeferred.promise.finally(function () { delete self._jsMessageDeferreds[id]; });
        return evaluationDeferred.promise;
    };

    Generator.prototype.getPhotoshopPath = function () {
        return this.evaluateJSXString("File(app.path).fsName");
    };

    Generator.prototype.subscribeToPhotoshopEvents = function (events) {
        var self = this,
            subscribeDeferred = Q.defer();

        rejectAfter(subscribeDeferred, REQUEST_TIMEOUT);

        if (!util.isArray(events)) {
            events = [events];
        }
        var params = { events : events };
        self._executeJSXFile("./jsx/networkEventSubscribe.jsx", params).then(
            function (id) {
                self._jsMessageDeferreds[id] = subscribeDeferred;
                subscribeDeferred.promise.finally(function () { delete self._jsMessageDeferreds[id]; });
            },
            function (err) {
                subscribeDeferred.reject(err);
            }
        );

        return subscribeDeferred.promise;
    };

    Generator.prototype.getPixmap = function (layerID, scale) {
        var self = this,
            overallDeferred = Q.defer(),
            pixmapDeferred = Q.defer(),
            jsDeferred = Q.defer();

        rejectAfter(jsDeferred, REQUEST_TIMEOUT);
        rejectAfter(pixmapDeferred, REQUEST_TIMEOUT);

        self._executeJSXFile("./jsx/getLayerPixmap.jsx", {layerID : layerID, scale: scale}).then(
            function (id) {
                self._jsMessageDeferreds[id] = jsDeferred;
                jsDeferred.promise.finally(function () { delete self._jsMessageDeferreds[id]; });
                self._pixmapMessageDeferreds[id] = pixmapDeferred;
                pixmapDeferred.promise.finally(function () { delete self._pixmapMessageDeferreds[id]; });
            }, function (err) {
                jsDeferred.reject(err);
                pixmapDeferred.reject(err);
            }
        );

        Q.all([jsDeferred.promise, pixmapDeferred.promise]).then(
            function (vals) {
                var pixmapBuffer = vals[1];
                var pixmap = xpm.Pixmap(pixmapBuffer);
                overallDeferred.resolve(pixmap);
            }, function (err) {
                overallDeferred.reject(err);
            }
        );

        return overallDeferred.promise;

    };

    Generator.prototype.shutdown = function () {
        return Q.when(
            this._photoshop ? this._photoshop.disconnect() : true,
            function () {
                this._photoshop = null;
            }
        );
    };

    Generator.prototype.isConnected = function () {
        return (this._photoshop && this._photoshop.isConnected());
    };

    Generator.prototype.publish = function () {
        this._channel.publish.apply(this._channel, arguments);
    };

    Generator.prototype.subscribe = function () {
        this._channel.subscribe.apply(this._channel, arguments);
    };

    Generator.prototype.loadPlugin = function (directory) {
        var resolve = require("path").resolve,
            fs = require("fs");

        var absolutePath = resolve(__dirname, "../", directory);

        if (!fs.statSync(absolutePath).isDirectory()) {
            throw new Error("Argument error: specified path is not a directory");
        } else {
            var plugin = require(absolutePath);
            plugin.init(this);
            this.publish("generator.info.pluginLoaded", absolutePath);
        }
    };

    Generator.prototype.loadAllPluginsInDirectory = function (directory) {
        // relative paths are resolved relative to generator core directory
        var resolve = require("path").resolve,
            fs = require("fs"),
            self = this;

        var absolutePath = resolve(__dirname, "../", directory);

        self.publish("generator.info.pluginPath", "Loading plugins from " + absolutePath);

        if (!fs.statSync(absolutePath).isDirectory()) {
            throw new Error("Argument error: specified path is not a directory");
        } else {
            var files = fs.readdirSync(absolutePath);
            var plugins = files.filter(function (f) { return RE_PLUGIN_FOLDER_NAME_MATCHER.test(f); });
            plugins.forEach(function (f) {
                try {
                    self.loadPlugin(resolve(absolutePath, f));
                } catch (e) {
                    self.publish("generator.error.pluginError", "Error loading plugin '" + f + "': " + e);
                }
            });
        }

    };

    exports.Generator = Generator;
    exports.createGenerator = createGenerator;

    exports.logChannelToConsole = function (channel, prefix) {
        channel.subscribe("#", function () {
            console.log(prefix, arguments);
        });
    };

}());
<commit_msg>Prevent duplicate event subscriptions<commit_after>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * 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 Q = require("q"),
        photoshop = require("./photoshop"),
        util = require("util"),
        xpm = require("./xpm");

    var _instanceCount = 0;

    var RE_PLUGIN_FOLDER_NAME_MATCHER = /\.generate$/;

    var REQUEST_TIMEOUT = 5000; // milliseconds. TODO: Make configurable

    function rejectAfter(deferred, timeout) {
        var rejectTimer = setTimeout(function () {
            rejectTimer = null;
            deferred.reject(new Error("timeout"));
        }, timeout);

        deferred.promise.finally(function () {
            if (rejectTimer) {
                clearTimeout(rejectTimer);
            }
        });
    }

    function Generator() {
        if (!this instanceof Generator) {
            return new Generator();
        }
        // TODO: declare these as prototype properties and document types
        this._photoshop = null;
        this._instanceID = _instanceCount++;
        this._channel = require("postal")().channel("generator-" + (this._instanceID));
        this._jsMessageDeferreds = [];
        this._pixmapMessageDeferreds = [];
        this._eventSubscriptions = {};
    }

    function createGenerator() {
        return new Generator();
    }

    Generator.prototype.start = function (options) {
        var self = this;

        function connectToPhotoshop() {
            var connectionDeferred = Q.defer();
            self._photoshop = photoshop.createClient(options);
            self._photoshop.once("connect", function () {
                connectionDeferred.resolve(self);
            });

            self._photoshop.on("error", function (err) {
                // TODO: Gracefully shut down
                self.publish("photoshop.error", err);
            });

            self._photoshop.on("communicationsError", function (err, rawMessage) {
                self.publish("photoshop.error.communcationsError", {error: err, rawMessage: rawMessage});
            });

            self._photoshop.on("message", function (messageID, parsedValue) { // ,rawMessage)
                self.publish("photoshop.message", {id: messageID, body: parsedValue});
                if (self._jsMessageDeferreds[messageID]) {
                    self._jsMessageDeferreds[messageID].resolve(parsedValue);
                }
            });

            self._photoshop.on("info", function (info) {
                self.publish("photoshop.info", info);
            });

            self._photoshop.on("event", function (messageID, eventName, parsedValue) { // , rawMessage)
                self.publish("photoshop.event." + eventName, parsedValue);
            });

            self._photoshop.on("pixmap", function (messageID, messageBody) { // , rawMessage)
                self.publish("photoshop.pixmap", {id: messageID});
                if (self._pixmapMessageDeferreds[messageID]) {
                    self._pixmapMessageDeferreds[messageID].resolve(messageBody);
                }
            });

            return connectionDeferred.promise;
        }

        return (connectToPhotoshop().then(
            function () {
                self.subscribeToPhotoshopEvents(
                    ["imageChanged",
                    "currentDocumentChanged",
                    "save",
                    "generatorMenuChanged"]
                );

                self.getPhotoshopPath().then(
                    function (p) { // success
                        self.publish("generator.info.psPath", p);
                        self._photoshop._applicationPath = p;
                    },
                    function (err) { // error
                        self.publish("generator.info.psPath", "error retrieving: " + err);
                    }
                );
            })
        );

    };

    // Returns a promise that resolves to the ID of the sent message
    // Does NOT return a promise that resolves to the response of the send.
    // That's why this is a private method.
    Generator.prototype._executeJSXFile = function (path, params) {
        var resolve = require("path").resolve,
            readFile = require("fs").readFile;

        var self = this,
            deferred = Q.defer();

        var paramsString = "null";
        if (params) {
            try {
                paramsString = JSON.stringify(params);
            } catch (jsonError) {
                deferred.reject(jsonError);
            }
        }

        if (deferred.promise.isPending()) {
            readFile(resolve(__dirname, path), "utf8", function (err, data) {
                if (err) {
                    deferred.reject(err);
                } else {
                    data = "var params = " + paramsString + ";\n" + data;
                    deferred.resolve(self._photoshop.sendCommand(data));
                }
            });
        }

        return deferred.promise;
    };

    Generator.prototype.evaluateJSXFile = function (path, params) {
        var self = this,
            evaluationDeferred = Q.defer();
        rejectAfter(evaluationDeferred, REQUEST_TIMEOUT);

        self._executeJSXFile(path, params).then(
            function (id) {
                self._jsMessageDeferreds[id] = evaluationDeferred;
                evaluationDeferred.promise.finally(function () { delete self._jsMessageDeferreds[id]; });
            }, function (err) {
                evaluationDeferred.reject(err);
            }
        );
        return evaluationDeferred.promise;
    };

    Generator.prototype.evaluateJSXString = function (s) {
        var self = this,
            evaluationDeferred = Q.defer();
        rejectAfter(evaluationDeferred, REQUEST_TIMEOUT);

        var id = self._photoshop.sendCommand(s);
        self._jsMessageDeferreds[id] = evaluationDeferred;

        evaluationDeferred.promise.finally(function () { delete self._jsMessageDeferreds[id]; });
        return evaluationDeferred.promise;
    };

    Generator.prototype.getPhotoshopPath = function () {
        return this.evaluateJSXString("File(app.path).fsName");
    };

    Generator.prototype.subscribeToPhotoshopEvents = function (events) {
        var self = this,
            subscribeDeferred = Q.defer(),
            e,
            i;

        rejectAfter(subscribeDeferred, REQUEST_TIMEOUT);

        if (!util.isArray(events)) {
            events = [events];
        }

        // Prevent redundant event subscriptions
        for (i = events.length - 1; i >= 0; i--) {
            e = events[i];
            // If we are already subscribed to this event
            if (self._eventSubscriptions[e]) {
                // Remove this event from the list
                events.splice(i, 1);
            } else {
                // Otherwise remember the subscription
                self._eventSubscriptions[e] = true;
            }
        }

        var params = { events : events };
        self._executeJSXFile("./jsx/networkEventSubscribe.jsx", params).then(
            function (id) {
                self._jsMessageDeferreds[id] = subscribeDeferred;
                subscribeDeferred.promise.finally(function () { delete self._jsMessageDeferreds[id]; });
            },
            function (err) {
                subscribeDeferred.reject(err);
            }
        );

        return subscribeDeferred.promise;
    };

    Generator.prototype.getPixmap = function (layerID, scale) {
        var self = this,
            overallDeferred = Q.defer(),
            pixmapDeferred = Q.defer(),
            jsDeferred = Q.defer();

        rejectAfter(jsDeferred, REQUEST_TIMEOUT);
        rejectAfter(pixmapDeferred, REQUEST_TIMEOUT);

        self._executeJSXFile("./jsx/getLayerPixmap.jsx", {layerID : layerID, scale: scale}).then(
            function (id) {
                self._jsMessageDeferreds[id] = jsDeferred;
                jsDeferred.promise.finally(function () { delete self._jsMessageDeferreds[id]; });
                self._pixmapMessageDeferreds[id] = pixmapDeferred;
                pixmapDeferred.promise.finally(function () { delete self._pixmapMessageDeferreds[id]; });
            }, function (err) {
                jsDeferred.reject(err);
                pixmapDeferred.reject(err);
            }
        );

        Q.all([jsDeferred.promise, pixmapDeferred.promise]).then(
            function (vals) {
                var pixmapBuffer = vals[1];
                var pixmap = xpm.Pixmap(pixmapBuffer);
                overallDeferred.resolve(pixmap);
            }, function (err) {
                overallDeferred.reject(err);
            }
        );

        return overallDeferred.promise;

    };

    Generator.prototype.shutdown = function () {
        return Q.when(
            this._photoshop ? this._photoshop.disconnect() : true,
            function () {
                this._photoshop = null;
            }
        );
    };

    Generator.prototype.isConnected = function () {
        return (this._photoshop && this._photoshop.isConnected());
    };

    Generator.prototype.publish = function () {
        this._channel.publish.apply(this._channel, arguments);
    };

    Generator.prototype.subscribe = function () {
        this._channel.subscribe.apply(this._channel, arguments);
    };

    Generator.prototype.loadPlugin = function (directory) {
        var resolve = require("path").resolve,
            fs = require("fs");

        var absolutePath = resolve(__dirname, "../", directory);

        if (!fs.statSync(absolutePath).isDirectory()) {
            throw new Error("Argument error: specified path is not a directory");
        } else {
            var plugin = require(absolutePath);
            plugin.init(this);
            this.publish("generator.info.pluginLoaded", absolutePath);
        }
    };

    Generator.prototype.loadAllPluginsInDirectory = function (directory) {
        // relative paths are resolved relative to generator core directory
        var resolve = require("path").resolve,
            fs = require("fs"),
            self = this;

        var absolutePath = resolve(__dirname, "../", directory);

        self.publish("generator.info.pluginPath", "Loading plugins from " + absolutePath);

        if (!fs.statSync(absolutePath).isDirectory()) {
            throw new Error("Argument error: specified path is not a directory");
        } else {
            var files = fs.readdirSync(absolutePath);
            var plugins = files.filter(function (f) { return RE_PLUGIN_FOLDER_NAME_MATCHER.test(f); });
            plugins.forEach(function (f) {
                try {
                    self.loadPlugin(resolve(absolutePath, f));
                } catch (e) {
                    self.publish("generator.error.pluginError", "Error loading plugin '" + f + "': " + e);
                }
            });
        }

    };

    exports.Generator = Generator;
    exports.createGenerator = createGenerator;

    exports.logChannelToConsole = function (channel, prefix) {
        channel.subscribe("#", function () {
            console.log(prefix, arguments);
        });
    };

}());
<|endoftext|>


<commit_before>/**
 * Duplicity CLI helper
 * Used in the backup model
 */
(function(require, m)
{

    'use strict';

    var exec = require('child_process').exec;
    var os = require('os');
    var moment = require('moment');

    var module = function()
    {

        var process = null;
        var cancelled = false;
        var outputCallback = null;
        var maxBuffer = 1024 * 1000;
        var verbosityLevel = 'info';

        /**
         * Checks if a process is running
         */
        this.isProcessing = function()
        {
            return process !== null;
        };

        /**
         * Registers a callback to be triggered when an output is received from the CLI tool
         * @param callback
         */
        this.onOutput = function(callback)
        {
            outputCallback = callback;
        };

        /**
         * Starts a backup task
         * @param data
         * @param type (full | "")
         * @param callback
         */
        this.doBackup = function(data, type, callback)
        {
            var options = {env: {PASSPHRASE: data.passphrase, TMPDIR: os.tmpdir()}, maxBuffer: maxBuffer};
            var command = 'duplicity ' + type + ' "' + data.path + '" "' + data.url + '" ' + data.cli_options + ' --verbosity ' + verbosityLevel;
            process = exec(command, options, function(error, stdout, stderr)
            {
                process = null;
                callback(cancelled || error !== null);
            });
            process.stdout.on('data', _onStdout.bind(this));
            process.stderr.on('data', _onStderr.bind(this));
        };

        /**
         * Tries to get a file and save it on the given path
         * @param data
         * @param path
         * @param dest_path
         * @param callback
         */
        this.restoreFile = function(data, path, dest_path, callback)
        {
            var options = {env: {PASSPHRASE: data.passphrase, TMPDIR: os.tmpdir()}, maxBuffer: maxBuffer};
            var command = 'duplicity restore --file-to-restore "' + path + '" "' + data.url + '" "' + dest_path + '"' + ' ' + data.cli_options + ' --verbosity ' + verbosityLevel;
            process = exec(command, options, function(error, stdout, stderr)
            {
                process = null;
                callback(cancelled || error !== null);
            });
            process.stdout.on('data', _onStdout.bind(this));
            process.stderr.on('data', _onStderr.bind(this));
        };

        /**
         * Tries to restore a backup
         * @param data
         * @param dest_path
         * @param callback
         */
        this.restoreTree = function(data, dest_path, callback)
        {
            var options = {env: {PASSPHRASE: data.passphrase, TMPDIR: os.tmpdir()}, maxBuffer: maxBuffer};
            var command = 'duplicity restore "' + data.url + '" "' + dest_path + '"' + ' ' + data.cli_options + ' --verbosity ' + verbosityLevel;
            process = exec(command, options, function(error, stdout, stderr)
            {
                process = null;
                callback(cancelled || error !== null);
            });
            process.stdout.on('data', _onStdout.bind(this));
            process.stderr.on('data', _onStderr.bind(this));
        };

        /**
         * Lists the current files in a backup
         * @param data
         * @param callback
         */
        this.getFiles = function(data, callback)
        {
            var options = {env: {PASSPHRASE: data.passphrase, TMPDIR: os.tmpdir()}, maxBuffer: maxBuffer};
            var command = 'duplicity list-current-files ' + data.url + ' ' + data.cli_options + ' --verbosity ' + verbosityLevel;
            process = exec(command, options, function(error, stdout, stderr)
            {
                var regex = /^[a-zA-Z]{3} [a-zA-Z]{3} [0-9 :]+(.*)$/gm;
                var tree = [];
                var match;
                while (match = regex.exec(stdout))
                {
                    if (match[1] !== 'undefined')
                    {
                        var path = match[1];
                        if (path !== '.' && path !== '..')
                        {
                            tree.push({
                                path: path,
                                dir: path.search('/') !== -1 ? path.substring(0, path.lastIndexOf('/')) : '.',
                                name: path.substring(path.lastIndexOf('/') + 1)
                            });
                        }
                    }
                }
                process = null;
                callback(error !== null, tree);
            });
            process.stdout.on('data', _onStdout.bind(this));
            process.stderr.on('data', _onStderr.bind(this));
        };

        /**
         * Gets the current status of a backup
         * @param data
         * @param callback
         */
        this.getStatus = function(data, callback)
        {
            var options = {env: {PASSPHRASE: data.passphrase, TMPDIR: os.tmpdir()}, maxBuffer: maxBuffer};
            var command1 = 'duplicity collection-status ' + data.url + ' ' + data.cli_options + ' --verbosity ' + verbosityLevel;
            var command2 = 'duplicity incremental ' + data.path + ' ' + data.url + ' ' + data.cli_options + ' --verbosity info --dry-run';
            process = exec(command1 + ' && ' + command2, options, function(error, stdout, stderr)
            {
                var data = {};
                var chain_start_time = new RegExp('Chain start time: ([^\n]+)', 'gm').exec(stdout);
                var chain_end_time = new RegExp('Chain end time: ([^\n]+)', 'gm').exec(stdout);
                var backup_sets = new RegExp('Number of contained backup sets: ([0-9]+)', 'gm').exec(stdout);
                var source_files = new RegExp('SourceFiles ([0-9]+)', 'g').exec(stdout);
                var source_file_size = new RegExp('SourceFileSize [0-9]+ [(]([^)]+)[)]', 'g').exec(stdout);
                var backup_volumes = new RegExp('Total number of contained volumes: ([0-9]+)', 'gm').exec(stdout);
                data.chain_start_time = chain_start_time !== null && typeof chain_start_time[1] !== 'undefined' ? Date.parse(chain_start_time[1]) : '';
                data.chain_end_time = chain_end_time !== null && typeof chain_end_time[1] !== 'undefined' ? Date.parse(chain_end_time[1]) : '';
                data.backup_sets = backup_sets !== null && typeof backup_sets[1] !== 'undefined' ? backup_sets[1] : '';
                data.backup_volumes = backup_volumes !== null && typeof backup_volumes[1] !== 'undefined' ? backup_volumes[1] : '';
                data.chain_start_time = data.chain_start_time !== '' ? moment(data.chain_start_time).format('YYYY-MM-DD HH:mm') : '';
                data.chain_end_time = data.chain_end_time !== '' ? moment(data.chain_end_time).format('YYYY-MM-DD HH:mm') : '';
                data.source_files = source_files !== null && typeof source_files[1] !== 'undefined' ? source_files[1] : '';
                data.source_file_size = source_file_size !== null && typeof source_file_size[1] !== 'undefined' ? source_file_size[1] : '';
                process = null;
                callback(error !== null, data);
            });
            process.stdout.on('data', _onStdout.bind(this));
            process.stderr.on('data', _onStderr.bind(this));
        };

        /**
         * Kills the current process
         */
        this.cancel = function()
        {
            cancelled = true;
            process.kill('SIGINT');
        };

        /**
         * Sends stdout
         * @param out
         */
        var _onStdout = function(out)
        {
            outputCallback(out);
        };

        /**
         * Sends stderr
         * @param out
         */
        var _onStderr = function(out)
        {
            outputCallback('<!--:error-->' + out + '<!--error:-->');
        };

    };

    m.exports = module;

})(require, module);<commit_msg>Sets verbosity level to "notice" (default)<commit_after>/**
 * Duplicity CLI helper
 * Used in the backup model
 */
(function(require, m)
{

    'use strict';

    var exec = require('child_process').exec;
    var os = require('os');
    var moment = require('moment');

    var module = function()
    {

        var process = null;
        var cancelled = false;
        var outputCallback = null;
        var maxBuffer = 1024 * 1000;
        var verbosityLevel = 'notice';

        /**
         * Checks if a process is running
         */
        this.isProcessing = function()
        {
            return process !== null;
        };

        /**
         * Registers a callback to be triggered when an output is received from the CLI tool
         * @param callback
         */
        this.onOutput = function(callback)
        {
            outputCallback = callback;
        };

        /**
         * Starts a backup task
         * @param data
         * @param type (full | "")
         * @param callback
         */
        this.doBackup = function(data, type, callback)
        {
            var options = {env: {PASSPHRASE: data.passphrase, TMPDIR: os.tmpdir()}, maxBuffer: maxBuffer};
            var command = 'duplicity ' + type + ' "' + data.path + '" "' + data.url + '" ' + data.cli_options + ' --verbosity ' + verbosityLevel;
            process = exec(command, options, function(error, stdout, stderr)
            {
                process = null;
                callback(cancelled || error !== null);
            });
            process.stdout.on('data', _onStdout.bind(this));
            process.stderr.on('data', _onStderr.bind(this));
        };

        /**
         * Tries to get a file and save it on the given path
         * @param data
         * @param path
         * @param dest_path
         * @param callback
         */
        this.restoreFile = function(data, path, dest_path, callback)
        {
            var options = {env: {PASSPHRASE: data.passphrase, TMPDIR: os.tmpdir()}, maxBuffer: maxBuffer};
            var command = 'duplicity restore --file-to-restore "' + path + '" "' + data.url + '" "' + dest_path + '"' + ' ' + data.cli_options + ' --verbosity ' + verbosityLevel;
            process = exec(command, options, function(error, stdout, stderr)
            {
                process = null;
                callback(cancelled || error !== null);
            });
            process.stdout.on('data', _onStdout.bind(this));
            process.stderr.on('data', _onStderr.bind(this));
        };

        /**
         * Tries to restore a backup
         * @param data
         * @param dest_path
         * @param callback
         */
        this.restoreTree = function(data, dest_path, callback)
        {
            var options = {env: {PASSPHRASE: data.passphrase, TMPDIR: os.tmpdir()}, maxBuffer: maxBuffer};
            var command = 'duplicity restore "' + data.url + '" "' + dest_path + '"' + ' ' + data.cli_options + ' --verbosity ' + verbosityLevel;
            process = exec(command, options, function(error, stdout, stderr)
            {
                process = null;
                callback(cancelled || error !== null);
            });
            process.stdout.on('data', _onStdout.bind(this));
            process.stderr.on('data', _onStderr.bind(this));
        };

        /**
         * Lists the current files in a backup
         * @param data
         * @param callback
         */
        this.getFiles = function(data, callback)
        {
            var options = {env: {PASSPHRASE: data.passphrase, TMPDIR: os.tmpdir()}, maxBuffer: maxBuffer};
            var command = 'duplicity list-current-files ' + data.url + ' ' + data.cli_options + ' --verbosity ' + verbosityLevel;
            process = exec(command, options, function(error, stdout, stderr)
            {
                var regex = /^[a-zA-Z]{3} [a-zA-Z]{3} [0-9 :]+(.*)$/gm;
                var tree = [];
                var match;
                while (match = regex.exec(stdout))
                {
                    if (match[1] !== 'undefined')
                    {
                        var path = match[1];
                        if (path !== '.' && path !== '..')
                        {
                            tree.push({
                                path: path,
                                dir: path.search('/') !== -1 ? path.substring(0, path.lastIndexOf('/')) : '.',
                                name: path.substring(path.lastIndexOf('/') + 1)
                            });
                        }
                    }
                }
                process = null;
                callback(error !== null, tree);
            });
            process.stdout.on('data', _onStdout.bind(this));
            process.stderr.on('data', _onStderr.bind(this));
        };

        /**
         * Gets the current status of a backup
         * @param data
         * @param callback
         */
        this.getStatus = function(data, callback)
        {
            var options = {env: {PASSPHRASE: data.passphrase, TMPDIR: os.tmpdir()}, maxBuffer: maxBuffer};
            var command1 = 'duplicity collection-status ' + data.url + ' ' + data.cli_options + ' --verbosity ' + verbosityLevel;
            var command2 = 'duplicity incremental ' + data.path + ' ' + data.url + ' ' + data.cli_options + ' --verbosity ' + verbosityLevel + ' --dry-run';
            process = exec(command1 + ' && ' + command2, options, function(error, stdout, stderr)
            {
                var data = {};
                var chain_start_time = new RegExp('Chain start time: ([^\n]+)', 'gm').exec(stdout);
                var chain_end_time = new RegExp('Chain end time: ([^\n]+)', 'gm').exec(stdout);
                var backup_sets = new RegExp('Number of contained backup sets: ([0-9]+)', 'gm').exec(stdout);
                var source_files = new RegExp('SourceFiles ([0-9]+)', 'g').exec(stdout);
                var source_file_size = new RegExp('SourceFileSize [0-9]+ [(]([^)]+)[)]', 'g').exec(stdout);
                var backup_volumes = new RegExp('Total number of contained volumes: ([0-9]+)', 'gm').exec(stdout);
                data.chain_start_time = chain_start_time !== null && typeof chain_start_time[1] !== 'undefined' ? Date.parse(chain_start_time[1]) : '';
                data.chain_end_time = chain_end_time !== null && typeof chain_end_time[1] !== 'undefined' ? Date.parse(chain_end_time[1]) : '';
                data.backup_sets = backup_sets !== null && typeof backup_sets[1] !== 'undefined' ? backup_sets[1] : '';
                data.backup_volumes = backup_volumes !== null && typeof backup_volumes[1] !== 'undefined' ? backup_volumes[1] : '';
                data.chain_start_time = data.chain_start_time !== '' ? moment(data.chain_start_time).format('YYYY-MM-DD HH:mm') : '';
                data.chain_end_time = data.chain_end_time !== '' ? moment(data.chain_end_time).format('YYYY-MM-DD HH:mm') : '';
                data.source_files = source_files !== null && typeof source_files[1] !== 'undefined' ? source_files[1] : '';
                data.source_file_size = source_file_size !== null && typeof source_file_size[1] !== 'undefined' ? source_file_size[1] : '';
                process = null;
                callback(error !== null, data);
            });
            process.stdout.on('data', _onStdout.bind(this));
            process.stderr.on('data', _onStderr.bind(this));
        };

        /**
         * Kills the current process
         */
        this.cancel = function()
        {
            cancelled = true;
            process.kill('SIGINT');
        };

        /**
         * Sends stdout
         * @param out
         */
        var _onStdout = function(out)
        {
            outputCallback(out);
        };

        /**
         * Sends stderr
         * @param out
         */
        var _onStderr = function(out)
        {
            outputCallback('<!--:error-->' + out + '<!--error:-->');
        };

    };

    m.exports = module;

})(require, module);<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Link } from 'react-router-dom';

import * as FetchAPI from '../fetch_data';
import dateFromUnixTimestamp from '../utils/date';

const ChangesetInfo = ({ changeset, push, onClick }) => {
  const { author, node, desc, index, showToggle, pushId } = changeset;
  const { collapsed, linkify, date } = push;
  // XXX: For author remove the email address
  // XXX: For desc display only the first line
  // XXX: linkify bug numbers
  // The tipmost changeset should always be visible
  const changesetClass = (index !== 0 && collapsed) ? 'collapsed_changeset' : 'changeset';
  const toggleText = (collapsed) ? '[Expand]' : '[Collapse]';
  return (

        {(date && index === 0) && {dateFromUnixTimestamp(date)}}

        {author.substring(0, 22)}

        {(linkify) ?
          <Link to={`/changeset/${node}`}>{node.substring(0, 12)}</Link>
          : {node.substring(0, 12)}}

        {desc.substring(0, 40)}

        {(showToggle) &&
          {push.numCsets} changesets in push -
            <a href={`#${pushId}`} id={pushId} onClick={onClick}>{toggleText}

        }

  );
};

const ChangesetsViewer = ({ changesets, pushes, onClick }) => (

        Push date
        Author
        Changeset
        Description
        Collapsed csets

      {changesets.map(cset => (
        <ChangesetInfo
          key={cset.node}
          changeset={cset}
          push={pushes[cset.pushId]}
          onClick={onClick}
        />
      ))}

);

const processJsonPushes = (pushes) => {
  const ignore = ({ desc, author }) => {
    if (
      (author.includes('ffxbld')) ||
      (desc.includes('ack out')) ||
      (desc.includes('acked out')) ||
      (desc.includes('=merge')) ||
      ((desc.includes('erge')) && (desc.includes('to')))) {
      return true;
    }

    return false;
  };
  // In here we're flattening the data structure
  // We're now going to have an array of changeset
  // and each changeset is going to have the original
  // metadata explicitely declared
  // This improves manipulating the data structure
  const filteredCsets = [];
  const filteredPushes = {};

  Object.keys(pushes).reverse().forEach((id) => {
    const push = pushes[id];
    const csets = push.changesets;
    const lenCsets = csets.length - 1;
    const tipmost = csets[lenCsets];

    if (!ignore(tipmost)) {
      filteredPushes[id] = {
        date: push.date,
        numCsets: lenCsets,
        tipmost: tipmost.node,
        collapsed: false,
        linkify: true,
      };
      csets.reverse()
        .filter(c => !ignore(c))
        .map((cset, position) => {
          const newCset = {
            index: position,
            pushId: id,
            ...cset,
          };
          if (position === 0 && lenCsets > 1) {
            newCset.showToggle = true;
          }
          filteredCsets.push(newCset);
          return newCset;
        });
    }
  });
  return {
    csets: filteredCsets,
    pushList: filteredPushes,
  };
};

export default class ChangesetsViewerContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      changesets: [],
      pushes: {},
      errorMessage: '',
    };
  }

  componentDidMount() {
    const { repoName } = this.props;

    FetchAPI.getJsonPushes(repoName)
      .then(response =>
        response.json(),
      ).then((text) => {
        const { csets, pushList } = processJsonPushes(text.pushes);

        this.setState({
          changesets: csets,
          pushes: pushList,
        });
      }).catch((error) => {
        console.log(error);
        this.setState({
          changesets: [],
          pushes: {},
          errorMessage: 'We have failed to fetch pushes.',
        });
      });
  }

  toggleRowVisibility(pushId) {
    this.setState((prevState) => {
      const newPushes = [];
      Object.keys(prevState.pushes).forEach((id) => {
        const push = prevState.pushes[id];
        newPushes[id] = {
          ...push,
        };
        if (pushId === id) {
          newPushes[id].collapsed = !push.collapsed;
        }
      });
      return {
        pushes: newPushes,
      };
    });
  }

  render() {
    const { errorMessage, changesets, pushes } = this.state;
    if (errorMessage) {
      return (
{errorMessage}
);
    }

    return (
      <ChangesetsViewer
        changesets={changesets}
        pushes={pushes}
        onClick={event => this.toggleRowVisibility(event.target.id)}
      />
    );
  }
}
<commit_msg>feat: Support fetching coverage summary for each push on the main view<commit_after>import React, { Component } from 'react';
import { Link } from 'react-router-dom';

import * as FetchAPI from '../fetch_data';
import dateFromUnixTimestamp from '../utils/date';

const ChangesetInfo = ({ changeset, push, onClick }) => {
  const hideCset = (desc) => {
    if (desc.includes('=merge') || (desc.includes('erge') && desc.includes('to'))) {
      return true;
    }
    return false;
  };
  const { author, node, desc, index, showToggle, pushId } = changeset;
  const { collapsed, linkify, date, summary } = push;
  // XXX: For author remove the email address
  // XXX: For desc display only the first line
  // XXX: linkify bug numbers
  // The tipmost changeset should always be visible
  let changesetClass;
  // XXX: For now do not hide timpost changesets
  if (index !== 0 && hideCset(desc)) {
    changesetClass = 'collapsed_changeset';
  } else {
    changesetClass = (index !== 0 && collapsed) ? 'collapsed_changeset' : 'changeset';
  }
  const toggleText = (collapsed) ? '[Expand]' : '[Collapse]';
  return (

        {(date && index === 0) && {dateFromUnixTimestamp(date)}}

        {author.substring(0, 22)}

        {(linkify && index !== 0) ?
          <Link to={`/changeset/${node}`}>{node.substring(0, 12)}</Link>
          : {node.substring(0, 12)}}

        {desc.substring(0, 40)}

        {(showToggle) &&
          {push.numCsets} changesets in push -
            <a href={`#${pushId}`} id={pushId} onClick={onClick}>{toggleText}

        }

        {(summary && index === 0 && summary.error) &&
          {summary.error}
        }
        {(summary && index === 0 && summary.overall_cur) &&
          {summary.overall_cur}
        }

  );
};

const ChangesetsViewer = ({ changesets, pushes, onClick }) => (

        Push date
        Author
        Changeset
        Description
        Collapsed csets
        Coverage summary

      {changesets.map(cset => (
        <ChangesetInfo
          key={cset.node}
          changeset={cset}
          push={pushes[cset.pushId]}
          onClick={onClick}
        />
      ))}

);

const processJsonPushes = (pushes) => {
  const ignore = ({ desc, author }) => {
    if (
      (author.includes('ffxbld')) ||
      (desc.includes('ack out')) ||
      (desc.includes('acked out'))) {
      return true;
    }

    return false;
  };
  // In here we're flattening the data structure
  // We're now going to have an array of changeset
  // and each changeset is going to have the original
  // metadata explicitely declared
  // This improves manipulating the data structure
  const filteredCsets = [];
  const filteredPushes = {};

  Object.keys(pushes).reverse().forEach((id) => {
    const push = pushes[id];
    const csets = push.changesets;
    const lenCsets = csets.length - 1;
    const tipmost = csets[lenCsets];

    if (lenCsets > 1 || !ignore(tipmost)) {
      filteredPushes[id] = {
        date: push.date,
        numCsets: lenCsets,
        tipmost: tipmost.node,
        collapsed: true,
        linkify: false,
      };
      csets.reverse()
        .filter(c => !ignore(c))
        .map((cset, position) => {
          const newCset = {
            index: position,
            pushId: id,
            ...cset,
          };
          if (position === 0 && lenCsets > 1) {
            newCset.showToggle = true;
          }
          filteredCsets.push(newCset);
          return newCset;
        });
    }
  });
  return {
    csets: filteredCsets,
    pushList: filteredPushes,
  };
};

// XXX: For now, we can only make 1 request a time to the backend
// or we will overwhelm it: https://github.com/mozilla-releng/services/issues/632
// Recursively fetching one summary at a time
const getSummaries = async (pushIds, pushes) => {
  // We're expecting the list of pushIds to be sorted from highest to lowest
  // since we grab the pushId from the end.
  // This is because more recent pushes are likely not to have code coverage data.
  const pushId = pushIds.pop();
  if (!pushId) {
    return {};
  }

  const push = pushes[pushId];
  const processedPushes = {};
  processedPushes[pushId] = {
    ...push,
  };

  try {
    console.log(`About to fetch ${pushId} (${push.tipmost})`);
    // XXX: fetch does not support timeouts. I would like to add a 5 second
    // timeout rather than wait Heroku's default 30 second timeout. Specially
    // since we're doing sequential fetches.
    // XXX: Wrap fetch() in a Promise; add a setTimeout and call reject() if
    // it goes off, otherwise resolve with the result of the fetch()
    const res = await FetchAPI.getChangesetCoverage(push.tipmost);
    if (res.status === 202) {
      processedPushes[pushId].summary = {
        error: 'Pending',
      };
    } else if (res.status === 200) {
      const ccSum = await res.json();
      processedPushes[pushId].summary = ccSum;

      if (ccSum.overall_cur) {
        // We have coverage data, thus, adding links to the coverage diff viewer
        processedPushes[pushId].linkify = true;
        processedPushes[pushId].collapsed = false;
      }
    } else {
      processedPushes[pushId].summary = {
        error: res.statusText,
      };
    }
  } catch (e) {
    console.log(e);
    processedPushes[pushId].summary = {
      error: 'Failed to fetch.',
    };
  }

  try {
    const remainingSummaries = await getSummaries(pushIds, pushes);
    return Object.assign(processedPushes, remainingSummaries);
  } catch (e) {
    // Once one fails; let's just stop and return the original list
    return pushes;
  }
};

export default class ChangesetsViewerContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      changesets: [],
      pushes: {},
      errorMessage: '',
    };
  }

  componentDidMount() {
    const { repoName } = this.props;

    FetchAPI.getJsonPushes(repoName)
      .then(response =>
        response.json(),
      ).then(async (text) => {
        const { csets, pushList } = processJsonPushes(text.pushes);

        this.setState({
          changesets: csets,
          pushes: pushList,
        });

        try {
          // Ordered from lowest to higher
          const pushIds = Object.keys(pushList).reverse();
          const newPushes = await getSummaries(pushIds, pushList);
          this.setState({
            pushes: newPushes,
          });
        } catch (e) {
          console.log(e);
        }
      }).catch((error) => {
        console.log(error);
        this.setState({
          changesets: [],
          pushes: {},
          errorMessage: 'We have failed to fetch pushes.',
        });
      });
  }

  toggleRowVisibility(pushId) {
    this.setState((prevState) => {
      const newPushes = [];
      Object.keys(prevState.pushes).forEach((id) => {
        const push = prevState.pushes[id];
        newPushes[id] = {
          ...push,
        };
        if (pushId === id) {
          newPushes[id].collapsed = !push.collapsed;
        }
      });
      return {
        pushes: newPushes,
      };
    });
  }

  render() {
    const { errorMessage, changesets, pushes } = this.state;
    if (errorMessage) {
      return (
{errorMessage}
);
    }

    return (
      <ChangesetsViewer
        changesets={changesets}
        pushes={pushes}
        onClick={event => this.toggleRowVisibility(event.target.id)}
      />
    );
  }
}
<|endoftext|>


<commit_before>/**
 * @copyright 2016 rsmakota@gmail.com
 * @author Rodion Smakota <rsmakota@gmail.com>
 */

BackOffice.Calendar = {
    workHours:       null,
    eventsUrl:       null,
    eventDataUrl:    null,
    formUrl:         null,
    _officeId:       null,
    eventDataParam: 'appId',

    getCalendar: function()
    {
        return $('#calendar');
    },

    getEventsUrl: function()
    {
        return BackOffice.Calendar.eventsUrl+'?office_id='+BackOffice.Calendar._officeId;
    },

    init: function (doctorId) {
        var me       = BackOffice.Calendar;
        var modal    = BackOffice.Modal;

        me._officeId = BackOffice.OfficeHelper.getOfficeId();
        me.getCalendar().fullCalendar({
            header: {
                left:   'prev,next today',
                center: 'title',
                right:  'month,agendaWeek,agendaDay'
            },
            buttonText: {
                today: 'сегодня',
                month: 'месяц',
                week:  'неделя',
                day:   'день'
            },

            monthNames:     ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'],
            monthNamesShort:['Янв.','Фев.','Мрт.','Апр.','Май','Июн','Июл','Авг.','Сен.','Окт.','Нбр.','Дек.'],
            dayNames:       ['Воскресенье','Понедельник','Вторник','Среда','Чеверг','Пятница','Суббота'],
            dayNamesShort:  ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
            columnFormat:   'ddd D/M',
            timeFormat:     'H:mm',
            axisFormat:     'H:mm',
            firstHour:      9,
            allDaySlot:     false,
            slotDuration:   '00:15:01',
            defaultView:    'agendaWeek',
            firstDay:       1,
            minTime:        "09:00:00",
            maxTime:        "19:30:00",
            events:         me.getEventsUrl(),
            editable:       false,
            droppable:      false,
            selectable:     true,
            textEscape:     false,
            eventClick:     function (calEvent) {
                modal.load(me.eventDataUrl+'?'+me.eventDataParam+'='+calEvent.id, {});
            },

            dayClick: function (date) {
                // if (null == doctorId) {
                //     return false;
                // }

                var data = {
                    officeId: BackOffice.OfficeHelper.getOfficeId(),
                    doctorId: doctorId,
                    date:     date.format("DD/MM/YYYY"),
                    time:     date.format("HH:mm"),
                    location: "calendar"
                };
                modal.load(me.formUrl, data);
            },

            eventRender: function(event, element,  view) {
                if (event.phone) {
                    element.find('.fc-title').append(' ');
                }
                element.addClass(event.class);
                // if ((event instanceof Object) && event.hasOwnProperty('phone') && (event.phone == 1)) {
                //     event.title = 'B ' + event.title;
                // }
        }

        });
    },

    reInit: function(officeId) {
        var me = BackOffice.Calendar;
        var calendar = me.getCalendar();
        if (me._officeId == officeId) {
            return;
        }
        calendar.fullCalendar('removeEventSource', me.getEventsUrl());
        me._officeId = officeId;
        // BackOffice.OfficeHelper.setOfficeId(officeId);
        calendar.fullCalendar('addEventSource', me.getEventsUrl());
    },

    update: function(){
        var me = BackOffice.Calendar;
        me.getCalendar().fullCalendar('refetchEvents');
    }

};
<commit_msg>20 min calendar step<commit_after>/**
 * @copyright 2016 rsmakota@gmail.com
 * @author Rodion Smakota <rsmakota@gmail.com>
 */

BackOffice.Calendar = {
    workHours:       null,
    eventsUrl:       null,
    eventDataUrl:    null,
    formUrl:         null,
    _officeId:       null,
    eventDataParam: 'appId',

    getCalendar: function()
    {
        return $('#calendar');
    },

    getEventsUrl: function()
    {
        return BackOffice.Calendar.eventsUrl+'?office_id='+BackOffice.Calendar._officeId;
    },

    init: function (doctorId) {
        var me       = BackOffice.Calendar;
        var modal    = BackOffice.Modal;

        me._officeId = BackOffice.OfficeHelper.getOfficeId();
        me.getCalendar().fullCalendar({
            header: {
                left:   'prev,next today',
                center: 'title',
                right:  'month,agendaWeek,agendaDay'
            },
            buttonText: {
                today: 'сегодня',
                month: 'месяц',
                week:  'неделя',
                day:   'день'
            },

            monthNames:     ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'],
            monthNamesShort:['Янв.','Фев.','Мрт.','Апр.','Май','Июн','Июл','Авг.','Сен.','Окт.','Нбр.','Дек.'],
            dayNames:       ['Воскресенье','Понедельник','Вторник','Среда','Чеверг','Пятница','Суббота'],
            dayNamesShort:  ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
            columnFormat:   'ddd D/M',
            timeFormat:     'H:mm',
            axisFormat:     'H:mm',
            firstHour:      9,
            allDaySlot:     false,
            slotDuration:   '00:20:01',
            defaultView:    'agendaWeek',
            firstDay:       1,
            minTime:        "09:00:00",
            maxTime:        "20:00:00",
            events:         me.getEventsUrl(),
            editable:       false,
            droppable:      false,
            selectable:     true,
            textEscape:     false,
            eventClick:     function (calEvent) {
                modal.load(me.eventDataUrl+'?'+me.eventDataParam+'='+calEvent.id, {});
            },

            dayClick: function (date) {
                // if (null == doctorId) {
                //     return false;
                // }

                var data = {
                    officeId: BackOffice.OfficeHelper.getOfficeId(),
                    doctorId: doctorId,
                    date:     date.format("DD/MM/YYYY"),
                    time:     date.format("HH:mm"),
                    location: "calendar"
                };
                modal.load(me.formUrl, data);
            },

            eventRender: function(event, element,  view) {
                if (event.phone) {
                    element.find('.fc-title').append(' ');
                }
                element.addClass(event.class);
                // if ((event instanceof Object) && event.hasOwnProperty('phone') && (event.phone == 1)) {
                //     event.title = 'B ' + event.title;
                // }
        }

        });
    },

    reInit: function(officeId) {
        var me = BackOffice.Calendar;
        var calendar = me.getCalendar();
        if (me._officeId == officeId) {
            return;
        }
        calendar.fullCalendar('removeEventSource', me.getEventsUrl());
        me._officeId = officeId;
        // BackOffice.OfficeHelper.setOfficeId(officeId);
        calendar.fullCalendar('addEventSource', me.getEventsUrl());
    },

    update: function(){
        var me = BackOffice.Calendar;
        me.getCalendar().fullCalendar('refetchEvents');
    }

};
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();

router.get('/', function(req, res) {
    var message = 'Hodor';

    var points = ['!', '.', '?', ' '];
    var point = points[Math.floor(Math.random() * points.length)];
    message = message + point;

    res.format({
        'text/plain': function(){
            res.send(message);
        },

        'text/html': function(){
            res.render('hodor',{ 'message': message });
        },

        'application/json': function(){
            res.send({ 'message': message});
        },

        'default': function() {
            // log the request and respond with 406
            res.status(406).send('Not Acceptable');
        }
    });
});

router.get('/number/:number', function(req, res) {
    var message = '';
    for (i = 0; i < req.params.number; i++ ) {
        message += 'Hodor, ';
    }
    message = message.slice(0, -2);

    var points = ['!', '.', '?', ' '];
    var point = points[Math.floor(Math.random() * points.length)];
    message = message + point;

    res.format({
        'text/plain': function(){
            res.send(message);
        },

        'text/html': function(){
            res.render('hodor',{ 'message': message });
        },

        'application/json': function(){
            res.send({ 'message': message});
        },

        'default': function() {
            // log the request and respond with 406
            res.status(406).send('Not Acceptable');
        }
    });
});

router.get('/question/:question', function(req, res) {
    var message = '';

    var number = Math.floor((Math.random() * 5) + 1);

    for (i = 0; i < number; i++ ) {
        message += 'Hodor, ';
    }
    message = message.slice(0, -2);

    var points = ['!', '.', '?', ' '];
    var point = points[Math.floor(Math.random() * points.length)];

    message = message + point;

    res.format({
        'text/plain': function(){
            res.send(message);
        },

        'text/html': function(){
            res.render('Hodor',{ 'message': message });
        },

        'application/json': function(){
            res.send({ 'message': message});
        },

        'default': function() {
            // log the request and respond with 406
            res.status(406).send('Not Acceptable');
        }
    });
});

module.exports = router;
<commit_msg>random comma and dot<commit_after>var express = require('express');
var router = express.Router();

router.get('/', function(req, res) {
    var message = 'Hodor';

    var points = ['!', '.', '?', ' '];
    var point = points[Math.floor(Math.random() * points.length)];
    message = message + point;

    res.format({
        'text/plain': function(){
            res.send(message);
        },

        'text/html': function(){
            res.render('hodor',{ 'message': message });
        },

        'application/json': function(){
            res.send({ 'message': message});
        },

        'default': function() {
            // log the request and respond with 406
            res.status(406).send('Not Acceptable');
        }
    });
});

router.get('/number/:number', function(req, res) {
    var message = '';
    for (i = 0; i < req.params.number; i++ ) {
        message += 'Hodor, ';
    }
    message = message.slice(0, -2);

    var points = ['!', '.', '?', ' '];
    var point = points[Math.floor(Math.random() * points.length)];
    message = message + point;

    res.format({
        'text/plain': function(){
            res.send(message);
        },

        'text/html': function(){
            res.render('hodor',{ 'message': message });
        },

        'application/json': function(){
            res.send({ 'message': message});
        },

        'default': function() {
            // log the request and respond with 406
            res.status(406).send('Not Acceptable');
        }
    });
});

router.get('/question/:question', function(req, res) {
    var message = '';
    var finalPoints = ['!', '.', '?', ' '];
    // we want most space or comma
    var points = ['!', '.', '?', ' ', ' ', ' ', ' ', ';', ':', ',', ','];
    var number = Math.floor((Math.random() * 5) + 1);

    for (i = 0; i < number; i++ ) {
        message += 'Hodor' + points[Math.floor(Math.random() * points.length)] + ' ';
    }
    message = message.slice(0, -2);

    var point = finalPoints[Math.floor(Math.random() * finalPoints.length)];
    message = message + point;

    res.format({
        'text/plain': function(){
            res.send(message);
        },

        'text/html': function(){
            res.render('Hodor',{ 'message': message });
        },

        'application/json': function(){
            res.send({ 'message': message});
        },

        'default': function() {
            // log the request and respond with 406
            res.status(406).send('Not Acceptable');
        }
    });
});

module.exports = router;
<|endoftext|>


<commit_before>'use strict';
import React, { PropTypes as T } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import c from 'classnames';
import TimeAgo from 'timeago-react';
import mapboxgl from 'mapbox-gl';

import config from '../config';
import {
  invalidateProjects,
  fetchProjects,
  postProject,
  resetProjectFrom,
  showAlert
} from '../actions';
import { prettyPrint } from '../utils/utils';
import { t, getLanguage } from '../utils/i18n';
import { showGlobalLoading, hideGlobalLoading } from '../components/global-loading';

import StickyHeader from '../components/sticky-header';
import ProjectFormModal from '../components/project/project-form-modal';

const projectStatusMatrix = {
  active: 'Active',
  pending: 'Draft'
};

var Home = React.createClass({
  displayName: 'Home',

  propTypes: {
    _invalidateProjects: T.func,
    _fetchProjects: T.func,
    _postProject: T.func,
    _showAlert: T.func,
    _resetProjectFrom: T.func,

    projects: T.object,
    projectForm: T.object
  },

  getInitialState: function () {
    return {
      projectFormModal: false
    };
  },

  openModal: function () {
    this.setState({projectFormModal: true});
  },

  closeModal: function () {
    this.setState({projectFormModal: false});
  },

  componentDidMount: function () {
    this.props._fetchProjects();
    showGlobalLoading();
  },

  componentWillReceiveProps: function (nextProps) {
    if (this.props.projects.fetching && !nextProps.projects.fetching) {
      hideGlobalLoading();
    }
  },

  renderProjectListItem: function (project) {
    let projectUrl = project.status === 'pending'
      ? `/${getLanguage()}/projects/${project.id}/setup`
      : `/${getLanguage()}/projects/${project.id}`;

    let cardSubtitle;
    if (project.status === 'pending') {
      cardSubtitle = t('Pending scenarios');
    } else {
      cardSubtitle = project.scenarioCount === 1
        ? t('1 scenario')
        : t('{count} scenarios', {count: project.scenarioCount});
    }

    return (

        <article className='project project--card card' id={`project-${project.id}`}>

            <ProjectThumb
              projectUrl={projectUrl}
              bbox={project.bbox}
            />

                <Link to={projectUrl} title={t('View project')} className='link-wrapper'>

{project.name}

                </Link>

{cardSubtitle}

                {project.description ? (

{project.description}

                ) : (

{t('No description.')}

                )}

Updated

<TimeAgo datetime={project.updated_at} />

Status

{projectStatusMatrix[project.status]}

    );
  },

  renderProjectList: function () {
    let { fetched, fetching, error, data } = this.props.projects;

    if (!fetched && !fetching || !fetched && fetching) {
      return (

          <StickyHeader className='inpage__header' />

      );
    }

    if (error) {
      return
Error: {prettyPrint(error)}
;
    }

    return (

        {data.results.map(o => this.renderProjectListItem(o))}

          <button title={t('Create new project')} className='card__button card__button--add' type='button' onClick={this.openModal}>{t('New project')}</button>

    );
  },

  render: function () {
    return (

        <StickyHeader className='inpage__header'>

{t('Projects')}

            <button title={t('Create new project')} className='ipa-plus' type='button' onClick={this.openModal}>{t('New project')}</button>

        </StickyHeader>

            {this.renderProjectList()}

        <ProjectFormModal
          _showGlobalLoading={showGlobalLoading}
          _hideGlobalLoading={hideGlobalLoading}
          _showAlert={this.props._showAlert}
          revealed={this.state.projectFormModal}
          onCloseClick={this.closeModal}
          projectForm={this.props.projectForm}
          saveProject={this.props._postProject}
          resetForm={this.props._resetProjectFrom}
        />

    );
  }
});

// /////////////////////////////////////////////////////////////////// //
// Connect functions

function selector (state) {
  return {
    projects: state.projects,
    projectForm: state.projectForm
  };
}

function dispatcher (dispatch) {
  return {
    _invalidateProjects: (...args) => dispatch(invalidateProjects(...args)),
    _fetchProjects: (...args) => dispatch(fetchProjects(...args)),
    _postProject: (...args) => dispatch(postProject(...args)),
    _resetProjectFrom: (...args) => dispatch(resetProjectFrom(...args)),
    _showAlert: (...args) => dispatch(showAlert(...args))
  };
}

module.exports = connect(selector, dispatcher)(Home);

const ProjectThumb = React.createClass({
  propTypes: {
    projectUrl: T.string,
    bbox: T.array
  },

  theMap: null,

  setupMap: function () {
    mapboxgl.accessToken = config.mbtoken;
    let { bbox } = this.props;

    this.theMap = new mapboxgl.Map({
      container: this.refs.map,
      style: 'mapbox://styles/mapbox/streets-v10',
      interactive: false
    });

    this.theMap.fitBounds(bbox);
  },

  componentDidMount: function () {
    if (this.props.bbox) this.setupMap();
  },

  componentWillUnmount: function () {
    if (this.theMap) {
      this.theMap.remove();
    }
  },

  render: function () {
    let { projectUrl, bbox } = this.props;

    return (

        <Link to={projectUrl} title='View project' className='link-wrapper'>

          {bbox ? (

          ) : (

          )}

        </Link>

    );
  }
});
<commit_msg>Remove redundant header<commit_after>'use strict';
import React, { PropTypes as T } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import c from 'classnames';
import TimeAgo from 'timeago-react';
import mapboxgl from 'mapbox-gl';

import config from '../config';
import {
  invalidateProjects,
  fetchProjects,
  postProject,
  resetProjectFrom,
  showAlert
} from '../actions';
import { prettyPrint } from '../utils/utils';
import { t, getLanguage } from '../utils/i18n';
import { showGlobalLoading, hideGlobalLoading } from '../components/global-loading';

import StickyHeader from '../components/sticky-header';
import ProjectFormModal from '../components/project/project-form-modal';

const projectStatusMatrix = {
  active: 'Active',
  pending: 'Draft'
};

var Home = React.createClass({
  displayName: 'Home',

  propTypes: {
    _invalidateProjects: T.func,
    _fetchProjects: T.func,
    _postProject: T.func,
    _showAlert: T.func,
    _resetProjectFrom: T.func,

    projects: T.object,
    projectForm: T.object
  },

  getInitialState: function () {
    return {
      projectFormModal: false
    };
  },

  openModal: function () {
    this.setState({projectFormModal: true});
  },

  closeModal: function () {
    this.setState({projectFormModal: false});
  },

  componentDidMount: function () {
    this.props._fetchProjects();
    showGlobalLoading();
  },

  componentWillReceiveProps: function (nextProps) {
    if (this.props.projects.fetching && !nextProps.projects.fetching) {
      hideGlobalLoading();
    }
  },

  renderProjectListItem: function (project) {
    let projectUrl = project.status === 'pending'
      ? `/${getLanguage()}/projects/${project.id}/setup`
      : `/${getLanguage()}/projects/${project.id}`;

    let cardSubtitle;
    if (project.status === 'pending') {
      cardSubtitle = t('Pending scenarios');
    } else {
      cardSubtitle = project.scenarioCount === 1
        ? t('1 scenario')
        : t('{count} scenarios', {count: project.scenarioCount});
    }

    return (

        <article className='project project--card card' id={`project-${project.id}`}>

            <ProjectThumb
              projectUrl={projectUrl}
              bbox={project.bbox}
            />

                <Link to={projectUrl} title={t('View project')} className='link-wrapper'>

{project.name}

                </Link>

{cardSubtitle}

                {project.description ? (

{project.description}

                ) : (

{t('No description.')}

                )}

Updated

<TimeAgo datetime={project.updated_at} />

Status

{projectStatusMatrix[project.status]}

    );
  },

  renderProjectList: function () {
    let { fetched, fetching, error, data } = this.props.projects;

    if (!fetched && !fetching || !fetched && fetching) {
      return null;
    }

    if (error) {
      return
Error: {prettyPrint(error)}
;
    }

    return (

        {data.results.map(o => this.renderProjectListItem(o))}

          <button title={t('Create new project')} className='card__button card__button--add' type='button' onClick={this.openModal}>{t('New project')}</button>

    );
  },

  render: function () {
    return (

        <StickyHeader className='inpage__header'>

{t('Projects')}

            <button title={t('Create new project')} className='ipa-plus' type='button' onClick={this.openModal}>{t('New project')}</button>

        </StickyHeader>

            {this.renderProjectList()}

        <ProjectFormModal
          _showGlobalLoading={showGlobalLoading}
          _hideGlobalLoading={hideGlobalLoading}
          _showAlert={this.props._showAlert}
          revealed={this.state.projectFormModal}
          onCloseClick={this.closeModal}
          projectForm={this.props.projectForm}
          saveProject={this.props._postProject}
          resetForm={this.props._resetProjectFrom}
        />

    );
  }
});

// /////////////////////////////////////////////////////////////////// //
// Connect functions

function selector (state) {
  return {
    projects: state.projects,
    projectForm: state.projectForm
  };
}

function dispatcher (dispatch) {
  return {
    _invalidateProjects: (...args) => dispatch(invalidateProjects(...args)),
    _fetchProjects: (...args) => dispatch(fetchProjects(...args)),
    _postProject: (...args) => dispatch(postProject(...args)),
    _resetProjectFrom: (...args) => dispatch(resetProjectFrom(...args)),
    _showAlert: (...args) => dispatch(showAlert(...args))
  };
}

module.exports = connect(selector, dispatcher)(Home);

const ProjectThumb = React.createClass({
  propTypes: {
    projectUrl: T.string,
    bbox: T.array
  },

  theMap: null,

  setupMap: function () {
    mapboxgl.accessToken = config.mbtoken;
    let { bbox } = this.props;

    this.theMap = new mapboxgl.Map({
      container: this.refs.map,
      style: 'mapbox://styles/mapbox/streets-v10',
      interactive: false
    });

    this.theMap.fitBounds(bbox);
  },

  componentDidMount: function () {
    if (this.props.bbox) this.setupMap();
  },

  componentWillUnmount: function () {
    if (this.theMap) {
      this.theMap.remove();
    }
  },

  render: function () {
    let { projectUrl, bbox } = this.props;

    return (

        <Link to={projectUrl} title='View project' className='link-wrapper'>

          {bbox ? (

          ) : (

          )}

        </Link>

    );
  }
});
<|endoftext|>


<commit_before>var data = require('../data.json');
var userdata = require('../public/js/userdata');

exports.view = function(req, res){
	//Find the user_id currently logged on
	var user_id = -1;
	for(var i = 0; i < data['users'].length; i++){
		if(data['users'][i]['active']){
			user_id = i;
		}
	}

	var user = userdata.getUserData(data, user_id);

	res.render('homeB', user);
};
<commit_msg>Delete homeb.js<commit_after><|endoftext|>