<commit_before>/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports, Graph, arguments */

////////////////////////////////////////////////////////////////////////////////
/// @brief Graph functionality
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2014 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Florian Bartels
/// @author Copyright 2011-2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var arangodb = require("org/arangodb"),
  ArangoCollection = arangodb.ArangoCollection,
  db = arangodb.db,
  _ = require("underscore");

// -----------------------------------------------------------------------------
// --SECTION--                             module "org/arangodb/general-graph"
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                                 private functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @brief transform a string into an array.
////////////////////////////////////////////////////////////////////////////////

var stringToArray = function (x) {
  if (typeof(x) === "string") {
    return [x];
  }
  return x;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief checks if a parameter is not defined, an empty string or an empty
//  array
////////////////////////////////////////////////////////////////////////////////

var isValidCollectionsParameter = function (x) {
  if (!x) {
    return false;
  }
  if (Array.isArray(x) && x.length === 0) {
    return false;
  }
  if (typeof(x) !== "string" && !Array.isArray(x)) {
    return false;
  }
  return true;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief find or create a collection by name
////////////////////////////////////////////////////////////////////////////////

var findOrCreateCollectionByName = function (name, type) {
  var col = db._collection(name),res = false;
  if (col === null) {
    if (type === ArangoCollection.TYPE_DOCUMENT) {
      col = db._create(name);
    } else {
      col = db._createEdgeCollection(name);
    }
    res = true;
  } else if (!(col instanceof ArangoCollection) || col.type() !== type) {
    throw "<" + name + "> must be a " +
    (type === ArangoCollection.TYPE_DOCUMENT ? "document" : "edge")
    + " collection";
  }
  return res;
};

// -----------------------------------------------------------------------------
// --SECTION--                             module "org/arangodb/general-graph"
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                                 public functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @brief define an undirected relation.
////////////////////////////////////////////////////////////////////////////////

var _undirectedRelationDefinition = function (relationName, vertexCollections) {

  if (arguments.length < 2) {
    throw "method _undirectedRelationDefinition expects 2 arguments";
  }

  if (typeof relationName !== "string" || relationName === "") {
    throw "<relationName> must be a not empty string";
  }

  if (!isValidCollectionsParameter(vertexCollections)) {
    throw "<vertexCollections> must be a not empty string or array";
  }

  return {
    collection: relationName,
    from: stringToArray(vertexCollections),
    to: stringToArray(vertexCollections)
  };
};

////////////////////////////////////////////////////////////////////////////////
/// @brief define an directed relation.
////////////////////////////////////////////////////////////////////////////////

var _directedRelationDefinition = function (
  relationName, fromVertexCollections, toVertexCollections) {

  if (arguments.length < 3) {
    throw "method _undirectedRelationDefinition expects 3 arguments";
  }

  if (typeof relationName !== "string" || relationName === "") {
    throw "<relationName> must be a not empty string";
  }

  if (!isValidCollectionsParameter(fromVertexCollections)) {
    throw "<fromVertexCollections> must be a not empty string or array";
  }

  if (!isValidCollectionsParameter(toVertexCollections)) {
    throw "<toVertexCollections> must be a not empty string or array";
  }

  return {
    collection: relationName,
    from: stringToArray(fromVertexCollections),
    to: stringToArray(toVertexCollections)
  };
};

////////////////////////////////////////////////////////////////////////////////
/// @brief create a list of edge definitions
////////////////////////////////////////////////////////////////////////////////

var edgeDefinitions = function () {

  var res = [], args = arguments;
  Object.keys(args).forEach(function (x) {
    res.push(args[x]);
  });

  return res;

};

////////////////////////////////////////////////////////////////////////////////
/// @brief create a new graph
////////////////////////////////////////////////////////////////////////////////

var _create = function (graphName, edgeDefinitions) {

  var gdb = db._collection("_graphs"),
    g,
    graphAlreadyExists = true;

  if (gdb === null) {
    throw "_graphs collection does not exist.";
  }

  if (!graphName) {
    throw "a graph name is required to create a graph.";
  }
  if (!Array.isArray(edgeDefinitions) || edgeDefinitions.length === 0) {
    throw "at least one edge definition is required to create a graph.";
  }

  try {
    g = gdb.document(graphName);
  } catch (e) {
    if (e.errorNum !== 1202) {
      throw e;
    }
    graphAlreadyExists = false;
  }

  if (graphAlreadyExists) {
    throw "graph " + graphName + " already exists.";
  }

  var vertexCollections = {};
  var edgeCollections = {};
  edgeDefinitions.forEach(function (e) {
    e.from.concat(e.to).forEach(function (v) {
      findOrCreateCollectionByName(v, ArangoCollection.TYPE_DOCUMENT);
      vertexCollections[v] = db[v];
    });
    findOrCreateCollectionByName(e.collection, ArangoCollection.TYPE_EDGE);
    edgeCollections[e] = db[e];
  });

  gdb.save({
    'edgeDefinitions' : edgeDefinitions,
    '_key' : graphName
  });

  return new Graph(graphName, edgeDefinitions, vertexCollections, edgeCollections);
};

////////////////////////////////////////////////////////////////////////////////
/// @brief load a graph.
////////////////////////////////////////////////////////////////////////////////

var Graph = function(graphName, edgeDefinitions, vertexCollections, edgeCollections) {
  this.vertexCollections = vertexCollections;
  this.edgeCollections = edgeCollections;
  this.edgeDefinitions = edgeDefinitions;
};

var _graph = function() {
  return new Graph();
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return all edge collections of the graph.
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.edgeCollections = function() {
  return _.values(this.edgeCollections);
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return all vertex collections of the graph.
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.vertexCollections = function() {
  return _.values(this.vertexCollections);
};

////////////////////////////////////////////////////////////////////////////////
/// @brief edges(vertexId).
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.edges = function(vertexId) {
  var edgeCollections = this.edgeCollections();
  var result = [];

  edgeCollections.forEach(
    function(edgeCollection) {
      //todo: test, if collection may point to vertex
      result = result.concat(edgeCollection.edges(vertexId));
    }
  );
  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief inEdges(vertexId).
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.inEdges = function(vertexId) {
  var edgeCollections = this.edgeCollections();
  var result = [];

  edgeCollections.forEach(
    function(edgeCollection) {
      //todo: test, if collection may point to vertex
      result = result.concat(edgeCollection.inEdges(vertexId));
    }
  );
  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief outEdges(vertexId).
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.outEdges = function(vertexId) {
  var edgeCollections = this.edgeCollections();
  var result = [];

  edgeCollections.forEach(
    function(edgeCollection) {
      //todo: test, if collection may point to vertex
      result = result.concat(edgeCollection.outEdges(vertexId));
    }
  );
  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief get ingoing vertex of an edge.
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.getInVertex = function(edgeId) {
  var edgeCollection = this.getEdgeCollectionByName(edgeId.split("/")[0]);
  var document = edgeCollection.document(edgeId);
  if (document) {
    var vertexId = document._from;
    var vertexCollection = this.getVertexCollectionByName(vertexId.split("/")[0]);
    return vertexCollection.document(vertexId);
  }
};

////////////////////////////////////////////////////////////////////////////////
/// @brief get outgoing vertex of an edge .
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.getOutVertex = function(edgeId) {
  var edgeCollection = this.getEdgeCollectionByName(edgeId.split("/")[0]);
  var document = edgeCollection.document(edgeId);
  if (document) {
    var vertexId = document._to;
    var vertexCollection = this.getVertexCollectionByName(vertexId.split("/")[0]);
    return vertexCollection.document(vertexId);
  }
};

////////////////////////////////////////////////////////////////////////////////
/// @brief get edge collection by name.
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.getEdgeCollectionByName = function(name) {
  var edgeCollections = this.edgeCollections();
  var results = edgeCollections.filter(
    function(edgeCollection) {
      return edgeCollection.name() === name;
    }
  );

  if (results.length === 1) {
    return results[0];
  }
  throw "Collection " + name + " does not exist in graph.";
};

////////////////////////////////////////////////////////////////////////////////
/// @brief get vertex collection by name.
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.getVertexCollectionByName = function(name) {
  var vertexCollections = this.vertexCollections();
  var results = vertexCollections.filter(
    function(vertexCollection) {
      return vertexCollection.name() === name;
    }
  );

  if (results.length === 1) {
    return results[0];
  }
  throw "Collection " + name + " does not exist in graph.";
};

// -----------------------------------------------------------------------------
// --SECTION--                                                    MODULE EXPORTS
// -----------------------------------------------------------------------------

exports._undirectedRelationDefinition = _undirectedRelationDefinition;
exports._directedRelationDefinition = _directedRelationDefinition;
exports._graph = _graph;
exports.edgeDefinitions = edgeDefinitions;
exports._create = _create;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// End:
<commit_msg>Renamed internal functionality to '_' and internal variables with equal names to '__'<commit_after>/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports, Graph, arguments */

////////////////////////////////////////////////////////////////////////////////
/// @brief Graph functionality
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2014 triagens GmbH, Cologne, Germany
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Florian Bartels
/// @author Copyright 2011-2014, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var arangodb = require("org/arangodb"),
  ArangoCollection = arangodb.ArangoCollection,
  db = arangodb.db,
  _ = require("underscore");

// -----------------------------------------------------------------------------
// --SECTION--                             module "org/arangodb/general-graph"
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                                 private functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @brief transform a string into an array.
////////////////////////////////////////////////////////////////////////////////

var stringToArray = function (x) {
  if (typeof(x) === "string") {
    return [x];
  }
  return x;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief checks if a parameter is not defined, an empty string or an empty
//  array
////////////////////////////////////////////////////////////////////////////////

var isValidCollectionsParameter = function (x) {
  if (!x) {
    return false;
  }
  if (Array.isArray(x) && x.length === 0) {
    return false;
  }
  if (typeof(x) !== "string" && !Array.isArray(x)) {
    return false;
  }
  return true;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief find or create a collection by name
////////////////////////////////////////////////////////////////////////////////

var findOrCreateCollectionByName = function (name, type) {
  var col = db._collection(name),res = false;
  if (col === null) {
    if (type === ArangoCollection.TYPE_DOCUMENT) {
      col = db._create(name);
    } else {
      col = db._createEdgeCollection(name);
    }
    res = true;
  } else if (!(col instanceof ArangoCollection) || col.type() !== type) {
    throw "<" + name + "> must be a " +
    (type === ArangoCollection.TYPE_DOCUMENT ? "document" : "edge")
    + " collection";
  }
  return res;
};

// -----------------------------------------------------------------------------
// --SECTION--                             module "org/arangodb/general-graph"
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                                 public functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @brief define an undirected relation.
////////////////////////////////////////////////////////////////////////////////

var _undirectedRelationDefinition = function (relationName, vertexCollections) {

  if (arguments.length < 2) {
    throw "method _undirectedRelationDefinition expects 2 arguments";
  }

  if (typeof relationName !== "string" || relationName === "") {
    throw "<relationName> must be a not empty string";
  }

  if (!isValidCollectionsParameter(vertexCollections)) {
    throw "<vertexCollections> must be a not empty string or array";
  }

  return {
    collection: relationName,
    from: stringToArray(vertexCollections),
    to: stringToArray(vertexCollections)
  };
};

////////////////////////////////////////////////////////////////////////////////
/// @brief define an directed relation.
////////////////////////////////////////////////////////////////////////////////

var _directedRelationDefinition = function (
  relationName, fromVertexCollections, toVertexCollections) {

  if (arguments.length < 3) {
    throw "method _undirectedRelationDefinition expects 3 arguments";
  }

  if (typeof relationName !== "string" || relationName === "") {
    throw "<relationName> must be a not empty string";
  }

  if (!isValidCollectionsParameter(fromVertexCollections)) {
    throw "<fromVertexCollections> must be a not empty string or array";
  }

  if (!isValidCollectionsParameter(toVertexCollections)) {
    throw "<toVertexCollections> must be a not empty string or array";
  }

  return {
    collection: relationName,
    from: stringToArray(fromVertexCollections),
    to: stringToArray(toVertexCollections)
  };
};

////////////////////////////////////////////////////////////////////////////////
/// @brief create a list of edge definitions
////////////////////////////////////////////////////////////////////////////////

var edgeDefinitions = function () {

  var res = [], args = arguments;
  Object.keys(args).forEach(function (x) {
    res.push(args[x]);
  });

  return res;

};

////////////////////////////////////////////////////////////////////////////////
/// @brief create a new graph
////////////////////////////////////////////////////////////////////////////////

var _create = function (graphName, edgeDefinitions) {

  var gdb = db._collection("_graphs"),
    g,
    graphAlreadyExists = true;

  if (gdb === null) {
    throw "_graphs collection does not exist.";
  }

  if (!graphName) {
    throw "a graph name is required to create a graph.";
  }
  if (!Array.isArray(edgeDefinitions) || edgeDefinitions.length === 0) {
    throw "at least one edge definition is required to create a graph.";
  }

  try {
    g = gdb.document(graphName);
  } catch (e) {
    if (e.errorNum !== 1202) {
      throw e;
    }
    graphAlreadyExists = false;
  }

  if (graphAlreadyExists) {
    throw "graph " + graphName + " already exists.";
  }

  var vertexCollections = {};
  var edgeCollections = {};
  edgeDefinitions.forEach(function (e) {
    e.from.concat(e.to).forEach(function (v) {
      findOrCreateCollectionByName(v, ArangoCollection.TYPE_DOCUMENT);
      vertexCollections[v] = db[v];
    });
    findOrCreateCollectionByName(e.collection, ArangoCollection.TYPE_EDGE);
    edgeCollections[e] = db[e];
  });

  gdb.save({
    'edgeDefinitions' : edgeDefinitions,
    '_key' : graphName
  });

  return new Graph(graphName, edgeDefinitions, vertexCollections, edgeCollections);
};

////////////////////////////////////////////////////////////////////////////////
/// @brief load a graph.
////////////////////////////////////////////////////////////////////////////////

var Graph = function(graphName, edgeDefinitions, vertexCollections, edgeCollections) {
  this.__vertexCollections = vertexCollections;
  this.__edgeCollections = edgeCollections;
  this.__edgeDefinitions = edgeDefinitions;
};

var _graph = function() {
  return new Graph();
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return all edge collections of the graph.
////////////////////////////////////////////////////////////////////////////////

Graph.prototype._edgeCollections = function() {
  return _.values(this.__edgeCollections);
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return all vertex collections of the graph.
////////////////////////////////////////////////////////////////////////////////

Graph.prototype._vertexCollections = function() {
  return _.values(this.__vertexCollections);
};

////////////////////////////////////////////////////////////////////////////////
/// @brief edges(vertexId).
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.edges = function(vertexId) {
  var edgeCollections = this._edgeCollections();
  var result = [];

  edgeCollections.forEach(
    function(edgeCollection) {
      //todo: test, if collection may point to vertex
      result = result.concat(edgeCollection.edges(vertexId));
    }
  );
  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief inEdges(vertexId).
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.inEdges = function(vertexId) {
  var edgeCollections = this._edgeCollections();
  var result = [];

  edgeCollections.forEach(
    function(edgeCollection) {
      //todo: test, if collection may point to vertex
      result = result.concat(edgeCollection.inEdges(vertexId));
    }
  );
  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief outEdges(vertexId).
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.outEdges = function(vertexId) {
  var edgeCollections = this._edgeCollections();
  var result = [];

  edgeCollections.forEach(
    function(edgeCollection) {
      //todo: test, if collection may point to vertex
      result = result.concat(edgeCollection.outEdges(vertexId));
    }
  );
  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief get ingoing vertex of an edge.
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.getInVertex = function(edgeId) {
  var edgeCollection = this._getEdgeCollectionByName(edgeId.split("/")[0]);
  var document = edgeCollection.document(edgeId);
  if (document) {
    var vertexId = document._from;
    var vertexCollection = this._getVertexCollectionByName(vertexId.split("/")[0]);
    return vertexCollection.document(vertexId);
  }
};

////////////////////////////////////////////////////////////////////////////////
/// @brief get outgoing vertex of an edge .
////////////////////////////////////////////////////////////////////////////////

Graph.prototype.getOutVertex = function(edgeId) {
  var edgeCollection = this._getEdgeCollectionByName(edgeId.split("/")[0]);
  var document = edgeCollection.document(edgeId);
  if (document) {
    var vertexId = document._to;
    var vertexCollection = this._getVertexCollectionByName(vertexId.split("/")[0]);
    return vertexCollection.document(vertexId);
  }
};

////////////////////////////////////////////////////////////////////////////////
/// @brief get edge collection by name.
////////////////////////////////////////////////////////////////////////////////

Graph.prototype._getEdgeCollectionByName = function(name) {
  if (this.__edgeCollections[name]) {
    return this.__edgeCollections[name];
  }
  throw "Collection " + name + " does not exist in graph.";
};

////////////////////////////////////////////////////////////////////////////////
/// @brief get vertex collection by name.
////////////////////////////////////////////////////////////////////////////////

Graph.prototype._getVertexCollectionByName = function(name) {
  if (this.__vertexCollections[name]) {
    return this.__vertexCollections[name];
  }
  throw "Collection " + name + " does not exist in graph.";
};

// -----------------------------------------------------------------------------
// --SECTION--                                                    MODULE EXPORTS
// -----------------------------------------------------------------------------

exports._undirectedRelationDefinition = _undirectedRelationDefinition;
exports._directedRelationDefinition = _directedRelationDefinition;
exports._graph = _graph;
exports.edgeDefinitions = edgeDefinitions;
exports._create = _create;

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)"
// End:
<|endoftext|>


<commit_before>/* eslint-disable max-statements */

var locations = tresdb.stores.locations;

var models = require('tresdb-models');
var ui = require('tresdb-ui');

var commentsModel = require('./Comments/model');

var forwardComments = function (entry, ev) {
  commentsModel.forward(entry.comments, ev);
};

var forwarders = {
  'location_entry_changed': function (entry, ev) {
    Object.assign(entry, ev.data.delta);
  },

  'location_entry_comment_created': forwardComments,
  'location_entry_comment_changed': forwardComments,
  'location_entry_comment_removed': forwardComments,
};

exports.forward = models.forward(forwarders);

exports.change = function (entry, entryData, callback) {
  // Parameters:
  //   entry
  //     complete entry object
  //   entryData
  //     data from form
  //   callback
  //     function (err)
  //
  locations.changeEntry(entry, entryData, callback);
};

exports.getId = function (entry) {
  return entry._id;
};

exports.getTime = function (entry) {
  return entry.time;
};

exports.getUserName = function (entry) {
  return entry.user;
};

exports.getLocationId = function (entry) {
  return String(entry.locationId);
};

exports.hasMarkdown = function (entry) {
  return (entry.markdown.length > 0);
};

exports.getMarkdown = function (entry) {
  return entry.markdown;
};

exports.getMarkdownHTML = function (entry) {
  return ui.markdownToHtml(entry.markdown);
};

exports.isVisit = function (entry) {
  return entry.flags.indexOf('visit') !== -1;
};

exports.hasFile = function (entry) {
  return (entry.attachments.length > 0);
};

exports.getAttachments = function (entry) {
  return entry.attachments;
};

exports.getImages = function (entry) {
  const HEAD = 6;
  return entry.attachments.filter(at => {
    return at.mimetype.substr(0, HEAD) === 'image/';
  });
};

exports.getComments = function (entry) {
  return entry.comments;
};

exports.remove = function (entry, callback) {
  // Remove entry from the backend
  var lid = entry.locationId;
  var eid = entry._id;
  locations.removeEntry(lid, eid, callback);
};

exports.createComment = function (entry, markdown, callback) {
  locations.createComment(entry, markdown, callback);
};
<commit_msg>Improve code style<commit_after>/* eslint-disable max-statements */
var locations = tresdb.stores.locations;
var models = require('tresdb-models');
var ui = require('tresdb-ui');

var commentsModel = require('./Comments/model');

var forwardComments = function (entry, ev) {
  commentsModel.forward(entry.comments, ev);
};

var forwarders = {
  'location_entry_changed': function (entry, ev) {
    Object.assign(entry, ev.data.delta);
  },

  'location_entry_comment_created': forwardComments,
  'location_entry_comment_changed': forwardComments,
  'location_entry_comment_removed': forwardComments,
};

exports.forward = models.forward(forwarders);

exports.change = function (entry, entryData, callback) {
  // Parameters:
  //   entry
  //     complete entry object
  //   entryData
  //     data from form
  //   callback
  //     function (err)
  //
  locations.changeEntry(entry, entryData, callback);
};

exports.getId = function (entry) {
  return entry._id;
};

exports.getTime = function (entry) {
  return entry.time;
};

exports.getUserName = function (entry) {
  return entry.user;
};

exports.getLocationId = function (entry) {
  return String(entry.locationId);
};

exports.hasMarkdown = function (entry) {
  return (entry.markdown.length > 0);
};

exports.getMarkdown = function (entry) {
  return entry.markdown;
};

exports.getMarkdownHTML = function (entry) {
  return ui.markdownToHtml(entry.markdown);
};

exports.isVisit = function (entry) {
  return entry.flags.indexOf('visit') !== -1;
};

exports.hasFile = function (entry) {
  return (entry.attachments.length > 0);
};

exports.getAttachments = function (entry) {
  return entry.attachments;
};

exports.getImages = function (entry) {
  const HEAD = 6;
  return entry.attachments.filter(function (at) {
    return at.mimetype.substr(0, HEAD) === 'image/';
  });
};

exports.getComments = function (entry) {
  return entry.comments;
};

exports.remove = function (entry, callback) {
  // Remove entry from the backend
  var lid = entry.locationId;
  var eid = entry._id;
  locations.removeEntry(lid, eid, callback);
};

exports.createComment = function (entry, markdown, callback) {
  locations.createComment(entry, markdown, callback);
};
<|endoftext|>


<commit_before>/**
 * @license
 * The MIT License (MIT)
 * Copyright 2015 Government of Canada
 *
 * @author
 * Ian Boyes
 *
 * @exports IsolateAdd
 */

import React from "react";
import { find, union } from "lodash-es";
import { connect } from "react-redux";
import { Modal } from "react-bootstrap";

import { editSequence, hideOTUModal } from "../../actions";
import { clearError } from "../../../errors/actions";
import { InputError } from "../../../base";
import { getTargetChange } from "../../../utils/utils";
import SequenceForm from "./SequenceForm";
import { SegmentCol } from "./SegmentCol";
import { StyledAccessionSegmentCol, StyledAccessionCol } from "./AccessionSegment";

const getInitialState = props => {
    if (props.sequenceId) {
        const isolate = { ...props.isolate };
        const sequence = find(isolate.sequences, { id: props.sequenceId });

        return {
            definition: sequence.definition,
            host: sequence.host || "",
            sequence: sequence.sequence,
            segment: sequence.segment,
            schema: sequence.segment ? union([sequence.segment], props.schema) : props.schema,
            autofillPending: false,
            errorDefinition: "",
            errorSequence: "",
            error: props.error
        };
    }

    return {
        definition: "",
        host: "",
        sequence: "",
        segment: "",
        autofillPending: false,
        errorDefinition: "",
        errorSequence: "",
        error: ""
    };
};

class EditSequence extends React.Component {
    constructor(props) {
        super(props);
        this.state = getInitialState(this.props);
    }

    static getDerivedStateFromProps(nextProps, prevState) {
        if (prevState.error !== nextProps.error) {
            return getInitialState(nextProps);
        }
        return null;
    }

    handleChange = e => {
        const { name, value, error } = getTargetChange(e.target);

        this.setState({
            [name]: value,
            [error]: ""
        });

        if (this.props.error) {
            this.props.onClearError("EDIT_SEQUENCE_ERROR");
        }
    };

    handleModalEnter = () => {
        this.setState(getInitialState(this.props));
    };

    handleHide = () => {
        this.props.onHide();

        if (this.props.error) {
            this.props.onClearError("EDIT_SEQUENCE_ERROR");
        }
    };

    handleSubmit = e => {
        e.preventDefault();

        if (!this.state.definition.length) {
            return this.setState({ errorDefinition: "Minimum length is 1" });
        }

        if (!this.state.sequence.length) {
            return this.setState({ errorSequence: "Minimum length is 1" });
        }

        this.props.onSave(
            this.props.otuId,
            this.props.isolateId,
            this.props.sequenceId,
            this.state.definition,
            this.state.host,
            this.state.sequence,
            this.state.segment
        );
    };

    render() {
        let overlay;

        if (this.state.autofillPending) {
            overlay = (

                    Loading

            );
        }
        const AccessionCol = <InputError label="Accession (ID)" name="id" value={this.props.sequenceId} readOnly />;
        let AccessionSegmentCol = (

        );
        if (this.props.dataType === "barcode") {
            AccessionSegmentCol = ;
        }

        return (
            <Modal show={!!this.props.sequenceId} onEnter={this.handleModalEnter} onHide={this.handleHide}>
                <Modal.Header onHide={this.handleHide} closeButton>
                    Edit Sequence
                </Modal.Header>
                <SequenceForm
                    host={this.state.host}
                    definition={this.state.definition}
                    sequence={this.state.sequence}
                    schema={this.state.schema}
                    overlay={overlay}
                    AccessionSegmentCol={AccessionSegmentCol}
                    handleChange={this.handleChange}
                    handleSubmit={this.handleSubmit}
                    errorDefinition={this.state.errorDefinition}
                    errorSequence={this.state.errorSequence}
                />
            </Modal>
        );
    }
}

const mapStateToProps = state => ({
    detail: state.otus.detail,
    isolate: state.otus.activeIsolate,
    sequenceId: state.otus.editSequence,
    otuId: state.otus.detail.id,
    dataType: state.references.detail.data_type
});

const mapDispatchToProps = dispatch => ({
    onHide: () => {
        dispatch(hideOTUModal());
    },

    onSave: (otuId, isolateId, sequenceId, definition, host, sequence, segment) => {
        dispatch(editSequence(otuId, isolateId, sequenceId, definition, host, sequence, segment));
    },

    onClearError: error => {
        dispatch(clearError(error));
    }
});

export default connect(mapStateToProps, mapDispatchToProps)(EditSequence);
<commit_msg>Replace bootstrap modal in EditSequence Component<commit_after>/**
 * @license
 * The MIT License (MIT)
 * Copyright 2015 Government of Canada
 *
 * @author
 * Ian Boyes
 *
 * @exports IsolateAdd
 */

import React from "react";
import { find, union } from "lodash-es";
import { connect } from "react-redux";
import { editSequence, hideOTUModal } from "../../actions";
import { clearError } from "../../../errors/actions";
import { InputError, ModalDialog } from "../../../base";
import { getTargetChange } from "../../../utils/utils";
import SequenceForm from "./SequenceForm";
import { SegmentCol } from "./SegmentCol";
import { StyledAccessionSegmentCol, StyledAccessionCol } from "./AccessionSegment";

const getInitialState = props => {
    if (props.sequenceId) {
        const isolate = { ...props.isolate };
        const sequence = find(isolate.sequences, { id: props.sequenceId });

        return {
            definition: sequence.definition,
            host: sequence.host || "",
            sequence: sequence.sequence,
            segment: sequence.segment,
            schema: sequence.segment ? union([sequence.segment], props.schema) : props.schema,
            autofillPending: false,
            errorDefinition: "",
            errorSequence: "",
            error: props.error
        };
    }

    return {
        definition: "",
        host: "",
        sequence: "",
        segment: "",
        autofillPending: false,
        errorDefinition: "",
        errorSequence: "",
        error: ""
    };
};

class EditSequence extends React.Component {
    constructor(props) {
        super(props);
        this.state = getInitialState(this.props);
    }

    static getDerivedStateFromProps(nextProps, prevState) {
        if (prevState.error !== nextProps.error) {
            return getInitialState(nextProps);
        }
        return null;
    }

    handleChange = e => {
        const { name, value, error } = getTargetChange(e.target);

        this.setState({
            [name]: value,
            [error]: ""
        });

        if (this.props.error) {
            this.props.onClearError("EDIT_SEQUENCE_ERROR");
        }
    };

    handleModalEnter = () => {
        this.setState(getInitialState(this.props));
    };

    handleHide = () => {
        this.props.onHide();

        if (this.props.error) {
            this.props.onClearError("EDIT_SEQUENCE_ERROR");
        }
    };

    handleSubmit = e => {
        e.preventDefault();

        if (!this.state.definition.length) {
            return this.setState({ errorDefinition: "Minimum length is 1" });
        }

        if (!this.state.sequence.length) {
            return this.setState({ errorSequence: "Minimum length is 1" });
        }

        this.props.onSave(
            this.props.otuId,
            this.props.isolateId,
            this.props.sequenceId,
            this.state.definition,
            this.state.host,
            this.state.sequence,
            this.state.segment
        );
    };

    render() {
        let overlay;

        if (this.state.autofillPending) {
            overlay = (

                    Loading

            );
        }
        const AccessionCol = <InputError label="Accession (ID)" name="id" value={this.props.sequenceId} readOnly />;
        let AccessionSegmentCol = (

        );
        if (this.props.dataType === "barcode") {
            AccessionSegmentCol = ;
        }

        return (
            <ModalDialog
                headerText="Edit Sequence"
                show={!!this.props.sequenceId}
                onEnter={this.handleModalEnter}
                onHide={this.handleHide}
            >
                <SequenceForm
                    host={this.state.host}
                    definition={this.state.definition}
                    sequence={this.state.sequence}
                    schema={this.state.schema}
                    overlay={overlay}
                    AccessionSegmentCol={AccessionSegmentCol}
                    handleChange={this.handleChange}
                    handleSubmit={this.handleSubmit}
                    errorDefinition={this.state.errorDefinition}
                    errorSequence={this.state.errorSequence}
                />
            </ModalDialog>
        );
    }
}

const mapStateToProps = state => ({
    detail: state.otus.detail,
    isolate: state.otus.activeIsolate,
    sequenceId: state.otus.editSequence,
    otuId: state.otus.detail.id,
    dataType: state.references.detail.data_type
});

const mapDispatchToProps = dispatch => ({
    onHide: () => {
        dispatch(hideOTUModal());
    },

    onSave: (otuId, isolateId, sequenceId, definition, host, sequence, segment) => {
        dispatch(editSequence(otuId, isolateId, sequenceId, definition, host, sequence, segment));
    },

    onClearError: error => {
        dispatch(clearError(error));
    }
});

export default connect(mapStateToProps, mapDispatchToProps)(EditSequence);
<|endoftext|>


<commit_before>/** @jsx React.DOM */
'use strict';

var React = require('react/addons');

var classNames = require('classnames');

var Icon = require('./Icon.js');
var TimeFormatter = require('./TimeFormatter.js');
var GroupAttributes = require('./GroupAttributes.js');
var ArtworkImage = require('./ArtworkImage.js');

var CollectionStore = require('../stores/CollectionStore.js');
var CollectionActions = require('../actions/CollectionActions.js');

var NowPlayingStore = require('../stores/NowPlayingStore.js');

var RootCollection = React.createClass({
  render: function() {
    return <GroupContent path={["Root"]} depth={0} />;
  }
});

var Group = React.createClass({
  propTypes: {
    path: React.PropTypes.array.isRequired,
    item: React.PropTypes.object.isRequired,
    depth: React.PropTypes.number.isRequired
  },

  getInitialState: function() {
    return {
      expanded: (this.props.depth !== 1) || CollectionStore.isExpanded(this.props.path),
      common: {},
    };
  },

  setCommon: function(c) {
    this.setState({
      common: c,
    });
  },

  render: function() {
    var content = null;
    var play = null;
    var headerDiv = null;
    var image = null;

    if (this.state.expanded) {
      content = [
        <GroupContent path={this.props.path} depth={this.props.depth} setCommon={this.setCommon} key="GroupContent0" />
      ];

      if (this.props.depth === 1) {
        content.push(

        );
      }

      var duration = null;
      if (this.state.common.totalTime) {
        duration = <TimeFormatter className="duration" time={parseInt(this.state.common.totalTime/1000)} />;
      }

      var attributeArr = [];

      if (this.state.common.artist) {
        attributeArr.push(this.state.common.artist);
      }

      if (this.state.common.composer) {
         attributeArr.push(this.state.common.composer);
      }

      if (this.state.common.year) {
        attributeArr.push(this.state.common.year);
      }

      var attributes = null;
      if (attributeArr.length > 0) {
        attributes = <GroupAttributes list={attributeArr} />;
      }

      play = (

          <Icon icon="play" title="Play Now" onClick={this._onPlayNow} />
          <Icon icon="list" title="Queue" onClick={this._onQueue} />
          {duration}
          {attributes}

      );

      if (this.state.common.trackId && this.props.depth == 1) {
        image = <ArtworkImage path={"/artwork/" + this.state.common.trackId} />;
      }
    }

    // FIXME: this is to get around the "everthing else" group which has no name
    // (and can't be closed)
    var itemName = this.props.item.Name;
    if (this.props.depth === 1 && itemName === "") {
      itemName = "Misc";
    }

    if (itemName !== "") {
      headerDiv = (

          {image}
          {itemName}
          {play}

      );
    }

    var groupClasses = {
      'group': true,
      'untitled': this.props.item.Name === "",
      'expanded': this.state.expanded,
    };

    return (

        {headerDiv}
        {content}

    );
  },

  _onClick: function(e) {
    if (!this.state.expanded) {
      this._onClickName(e);
    }
  },

  _onClickName: function(e) {
    e.stopPropagation();
    this.setState({
      expanded: !this.state.expanded
    });
    CollectionActions.expandPath(this.props.path, !this.state.expanded);
  },

  _onPlayNow: function(e) {
    CollectionActions.playNow(this.props.path);
    e.stopPropagation();
  },

  _onQueue: function(e) {
    CollectionActions.appendToPlaylist(this.props.path);
    e.stopPropagation();
  },

});

var GroupContent = React.createClass({
  propTypes: {
    path: React.PropTypes.array.isRequired,
    depth: React.PropTypes.number.isRequired,
  },

  getInitialState: function() {
    return {item: null};
  },

  componentDidMount: function() {
    CollectionStore.addChangeListener(this._onChange);
    CollectionActions.fetch(this.props.path);
  },

  componentWillUnmount: function() {
    CollectionStore.removeChangeListener(this._onChange);
  },

  render: function() {
    var item = this.state.item;
    if (item === null) {
      return null;
    }

    if (item.Groups) {
      return <GroupList path={this.props.path} depth={this.props.depth} list={item.Groups} />;
    }
    return <TrackList path={this.props.path} list={item.Tracks} listStyle={item.ListStyle} />;
  },

  _onChange: function(keyPath) {
    if (CollectionStore.pathToKey(this.props.path) === keyPath) {
      var item = CollectionStore.getCollection(this.props.path);

      var common = {};
      var empty = true;
      if (item.TotalTime) {
        common.totalTime = item.TotalTime;
        empty = false;
      }

      if (item.Artist) {
        common.artist = item.Artist;
        empty = false;
      }

      if (item.TrackID) {
        common.trackId = item.TrackID;
        empty = false;
      }

      if (item.Year) {
        common.year = item.Year;
        empty = false;
      }

      if (item.Composer) {
        common.composer = item.Composer;
        empty = false;
      }

      if (!empty) {
        this.props.setCommon(common);
      }

      this.setState({item: item});
    }
  }
});

var GroupList = React.createClass({
  propTypes: {
    path: React.PropTypes.array.isRequired,
    depth: React.PropTypes.number.isRequired,
    list: React.PropTypes.array.isRequired,
  },

  render: function() {
    var list = this.props.list.map(function(item) {
      return <Group path={this.props.path.concat([item.Key])} depth={this.props.depth + 1} item={item} key={item.Key} />;
    }.bind(this));

    return (

        {list}

    );
  },
});

var TrackList = React.createClass({
  propTypes: {
    path: React.PropTypes.array.isRequired,
    list: React.PropTypes.array.isRequired,
    listStyle: React.PropTypes.string.isRequired,
  },

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

  render: function() {
    var discs = {};
    var discIndices = [];
    var trackNumber = 0;
    this.props.list.forEach(function(track) {
      var discNumber = track.DiscNumber;
      if (!discs[discNumber]) {
        discs[discNumber] = [];
        discIndices.push(discNumber);
      }
      track.Key = ""+(trackNumber++);
      discs[discNumber].push(track);
    });

    var ols = [];
    var buildTrack = function(track) {
      return <Track key={track.TrackID} data={track} path={this.props.path.concat([track.Key])} />;
    }.bind(this);

    for (var i = 0; i < discIndices.length; i++) {
      var disc = discs[discIndices[i]];
      ols.push(

          {disc.map(buildTrack)}

      );
    }

    return (

        {ols}

    );
  },
});

function isCurrent(trackId) {
  var t = NowPlayingStore.getTrack();
  if (t) {
    return t.TrackID === trackId;
  }
  return false;
}

function getTrackState(trackID) {
  return {
    current: isCurrent(trackID),
    playing: NowPlayingStore.getPlaying(),
  };
}

var Track = React.createClass({
  propTypes: {
    path: React.PropTypes.array.isRequired,
  },

  getInitialState: function() {
    return getTrackState(this.props.data.TrackID);
  },

  componentDidMount: function() {
    NowPlayingStore.addChangeListener(this._onChange);
  },

  componentWillUnmount: function() {
    NowPlayingStore.removeChangeListener(this._onChange);
  },

  render: function() {
    var durationSecs = parseInt(this.props.data.TotalTime/1000);
    var liClasses = {
      'current': this.state.current,
      'playing': this.state.current && this.state.playing,
    };
    return (

        {this.props.data.Name}
        <TimeFormatter className="duration" time={durationSecs} />

          <Icon icon="play" title="Play Now" onClick={this._onPlayNow} />
          <Icon icon="list" title="Queue" onClick={this._onQueue} />

    );
  },

  _onChange: function() {
    this.setState(getTrackState(this.props.data.TrackID));
  },

  _onClick: function(e) {
    e.stopPropagation();
    CollectionActions.setCurrentTrack(this.props.data);
  },

  _onPlayNow: function(e) {
    e.stopPropagation();
    CollectionActions.playNow(this.props.path.concat([this.props.Key]));
  },

  _onQueue: function(e) {
    e.stopPropagation();
    CollectionActions.appendToPlaylist(this.props.path.concat([this.props.Key]));
  },
});

module.exports.RootCollection = RootCollection;
module.exports.Group = Group;
<commit_msg>Tidy up common fields in Collections components<commit_after>/** @jsx React.DOM */
'use strict';

var React = require('react/addons');

var classNames = require('classnames');

var Icon = require('./Icon.js');
var TimeFormatter = require('./TimeFormatter.js');
var GroupAttributes = require('./GroupAttributes.js');
var ArtworkImage = require('./ArtworkImage.js');

var CollectionStore = require('../stores/CollectionStore.js');
var CollectionActions = require('../actions/CollectionActions.js');

var NowPlayingStore = require('../stores/NowPlayingStore.js');

var RootCollection = React.createClass({
  render: function() {
    return <GroupContent path={["Root"]} depth={0} />;
  }
});

var Group = React.createClass({
  propTypes: {
    path: React.PropTypes.array.isRequired,
    item: React.PropTypes.object.isRequired,
    depth: React.PropTypes.number.isRequired
  },

  getInitialState: function() {
    return {
      expanded: (this.props.depth !== 1) || CollectionStore.isExpanded(this.props.path),
      common: {},
    };
  },

  setCommon: function(c) {
    this.setState({
      common: c,
    });
  },

  render: function() {
    var content = null;
    var play = null;
    var headerDiv = null;
    var image = null;

    if (this.state.expanded) {
      content = [
        <GroupContent path={this.props.path} depth={this.props.depth} setCommon={this.setCommon} key="GroupContent0" />
      ];

      if (this.props.depth === 1) {
        content.push(

        );
      }

      var common = this.state.common;
      var duration = null;
      if (this.state.common.TotalTime) {
        duration = <TimeFormatter className="duration" time={parseInt(common.TotalTime/1000)} />;
      }

      var attributeArr = [];
      var fields = ['Artist', 'Composer', 'Year'];
      fields.forEach(function(f) {
        if (common[f]) {
          attributeArr.push(common[f]);
        }
      });

      var attributes = null;
      if (attributeArr.length > 0) {
        attributes = <GroupAttributes list={attributeArr} />;
      }

      play = (

          <Icon icon="play" title="Play Now" onClick={this._onPlayNow} />
          <Icon icon="list" title="Queue" onClick={this._onQueue} />
          {duration}
          {attributes}

      );

      if (this.state.common.TrackID && this.props.depth == 1) {
        image = <ArtworkImage path={"/artwork/" + common.TrackID} />;
      }
    }

    // FIXME: this is to get around the "everything else" group which has no name
    // (and can't be closed)
    var itemName = this.props.item.Name;
    if (this.props.depth === 1 && itemName === "") {
      itemName = "Misc";
    }

    if (itemName !== "") {
      headerDiv = (

          {image}
          {itemName}
          {play}

      );
    }

    var groupClasses = {
      'group': true,
      'untitled': this.props.item.Name === "",
      'expanded': this.state.expanded,
    };

    return (

        {headerDiv}
        {content}

    );
  },

  _onClick: function(e) {
    if (!this.state.expanded) {
      this._onClickName(e);
    }
  },

  _onClickName: function(e) {
    e.stopPropagation();
    this.setState({
      expanded: !this.state.expanded
    });
    CollectionActions.expandPath(this.props.path, !this.state.expanded);
  },

  _onPlayNow: function(e) {
    CollectionActions.playNow(this.props.path);
    e.stopPropagation();
  },

  _onQueue: function(e) {
    CollectionActions.appendToPlaylist(this.props.path);
    e.stopPropagation();
  },

});

var GroupContent = React.createClass({
  propTypes: {
    path: React.PropTypes.array.isRequired,
    depth: React.PropTypes.number.isRequired,
  },

  getInitialState: function() {
    return {item: null};
  },

  componentDidMount: function() {
    CollectionStore.addChangeListener(this._onChange);
    CollectionActions.fetch(this.props.path);
  },

  componentWillUnmount: function() {
    CollectionStore.removeChangeListener(this._onChange);
  },

  render: function() {
    var item = this.state.item;
    if (item === null) {
      return null;
    }

    if (item.Groups) {
      return <GroupList path={this.props.path} depth={this.props.depth} list={item.Groups} />;
    }
    return <TrackList path={this.props.path} list={item.Tracks} listStyle={item.ListStyle} />;
  },

  _onChange: function(keyPath) {
    if (CollectionStore.pathToKey(this.props.path) === keyPath) {
      var item = CollectionStore.getCollection(this.props.path);

      var common = {};
      var fields = ['TotalTime', 'Artist', 'TrackID', 'Composer', 'Year'];
      fields.forEach(function(f) {
        if (item[f]) {
          common[f] = item[f];
        }
      });

      if (Object.keys(common).length > 0 && this.props.setCommon) {
        this.props.setCommon(common);
      }
      this.setState({item: item});
    }
  }
});

var GroupList = React.createClass({
  propTypes: {
    path: React.PropTypes.array.isRequired,
    depth: React.PropTypes.number.isRequired,
    list: React.PropTypes.array.isRequired,
  },

  render: function() {
    var list = this.props.list.map(function(item) {
      return <Group path={this.props.path.concat([item.Key])} depth={this.props.depth + 1} item={item} key={item.Key} />;
    }.bind(this));

    return (

        {list}

    );
  },
});

var TrackList = React.createClass({
  propTypes: {
    path: React.PropTypes.array.isRequired,
    list: React.PropTypes.array.isRequired,
    listStyle: React.PropTypes.string.isRequired,
  },

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

  render: function() {
    var discs = {};
    var discIndices = [];
    var trackNumber = 0;
    this.props.list.forEach(function(track) {
      var discNumber = track.DiscNumber;
      if (!discs[discNumber]) {
        discs[discNumber] = [];
        discIndices.push(discNumber);
      }
      track.Key = ""+(trackNumber++);
      discs[discNumber].push(track);
    });

    var ols = [];
    var buildTrack = function(track) {
      return <Track key={track.TrackID} data={track} path={this.props.path.concat([track.Key])} />;
    }.bind(this);

    for (var i = 0; i < discIndices.length; i++) {
      var disc = discs[discIndices[i]];
      ols.push(

          {disc.map(buildTrack)}

      );
    }

    return (

        {ols}

    );
  },
});

function isCurrent(trackId) {
  var t = NowPlayingStore.getTrack();
  if (t) {
    return t.TrackID === trackId;
  }
  return false;
}

function getTrackState(trackID) {
  return {
    current: isCurrent(trackID),
    playing: NowPlayingStore.getPlaying(),
  };
}

var Track = React.createClass({
  propTypes: {
    path: React.PropTypes.array.isRequired,
  },

  getInitialState: function() {
    return getTrackState(this.props.data.TrackID);
  },

  componentDidMount: function() {
    NowPlayingStore.addChangeListener(this._onChange);
  },

  componentWillUnmount: function() {
    NowPlayingStore.removeChangeListener(this._onChange);
  },

  render: function() {
    var durationSecs = parseInt(this.props.data.TotalTime/1000);
    var liClasses = {
      'current': this.state.current,
      'playing': this.state.current && this.state.playing,
    };
    return (

        {this.props.data.Name}
        <TimeFormatter className="duration" time={durationSecs} />

          <Icon icon="play" title="Play Now" onClick={this._onPlayNow} />
          <Icon icon="list" title="Queue" onClick={this._onQueue} />

    );
  },

  _onChange: function() {
    this.setState(getTrackState(this.props.data.TrackID));
  },

  _onClick: function(e) {
    e.stopPropagation();
    CollectionActions.setCurrentTrack(this.props.data);
  },

  _onPlayNow: function(e) {
    e.stopPropagation();
    CollectionActions.playNow(this.props.path.concat([this.props.Key]));
  },

  _onQueue: function(e) {
    e.stopPropagation();
    CollectionActions.appendToPlaylist(this.props.path.concat([this.props.Key]));
  },
});

module.exports.RootCollection = RootCollection;
module.exports.Group = Group;
<|endoftext|>


<commit_before>'use strict';

var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('eventemitter3').EventEmitter;
var assign = require('object-assign');

var WebsocketConstants = require('../constants/WebsocketConstants.js');
var WebsocketAPI = require('../utils/WebsocketAPI.js');

var ControlConstants = require('../constants/ControlConstants.js');

var CHANGE_EVENT = 'change';

var _playerKey = null;
var _pushKey = null;

function setKey(k) {
  _playerKey = k;
  localStorage.setItem("playerKey", k);
}

function key() {
  if (_playerKey !== null) {
    return _playerKey;
  }
  var k = localStorage.getItem("playerKey");
  _playerKey = (k) ? k : "";
  return _playerKey;
}

function sendKey(key) {
  WebsocketAPI.send("KEY", {key: key});
}

function setPushKey(k) {
  _pushKey = k;
  localStorage.setItem("pushKey", k);
}

function pushKey() {
  if (_pushKey !== null) {
    return _pushKey;
  }
  var k = localStorage.getItem("pushKey");
  _pushKey = (k) ? k : "";
  return _pushKey;
}

var PlayerKeyStore = assign({}, EventEmitter.prototype, {

  isKeySet: function() {
    var k = key();
    if (k === null || k === "") {
      return false;
    }
    return true;
  },

  getKey: function() {
    return key();
  },

  isPushKeySet: function() {
    var k = pushKey();
    if (k === null || k === "") {
      return false;
    }
    return true;
  },

  getPushKey: function() {
    return pushKey();
  },

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  /**
   * @param {function} callback
   */
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  /**
   * @param {function} callback
   */
  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  },

});

PlayerKeyStore.dispatchToken = AppDispatcher.register(function(payload) {
  var action = payload.action;
  var source = payload.source;

  if (source === 'VIEW_ACTION') {
    switch (action.actionType) {
      case ControlConstants.SET_KEY:
        setKey(action.key);
        sendKey(action.key);
        PlayerKeyStore.emitChange();
        break;

      case ControlConstants.SET_PUSH_KEY:
        setPushKey(action.key);
        PlayerKeyStore.emitChange();
        break;

      case WebsocketConstants.RECONNECT:
        if (PlayerKeyStore.isKeySet()) {
          sendKey(PlayerKeyStore.getKey());
        }
        break;
    }
  }
  return true;
});

module.exports = PlayerKeyStore;
<commit_msg>[es6] Rewritten PlayerKeyStore<commit_after>'use strict';

import {ChangeEmitter} from '../utils/ChangeEmitter.js';
import AppDispatcher from '../dispatcher/AppDispatcher';

import WebsocketConstants from '../constants/WebsocketConstants.js';
import WebsocketAPI from '../utils/WebsocketAPI.js';

import ControlConstants from '../constants/ControlConstants.js';

var _playerKey = null;
var _pushKey = null;

function setKey(k) {
  _playerKey = k;
  localStorage.setItem("playerKey", k);
}

function key() {
  if (_playerKey !== null) {
    return _playerKey;
  }
  var k = localStorage.getItem("playerKey");
  _playerKey = (k) ? k : "";
  return _playerKey;
}

function sendKey(key) {
  WebsocketAPI.send("KEY", {key: key});
}

function setPushKey(k) {
  _pushKey = k;
  localStorage.setItem("pushKey", k);
}

function pushKey() {
  if (_pushKey !== null) {
    return _pushKey;
  }
  var k = localStorage.getItem("pushKey");
  _pushKey = (k) ? k : "";
  return _pushKey;
}

class PlayerKeyStore extends ChangeEmitter {
  isKeySet() {
    var k = key();
    if (k === null || k === "") {
      return false;
    }
    return true;
  }

  getKey() {
    return key();
  }

  isPushKeySet() {
    var k = pushKey();
    if (k === null || k === "") {
      return false;
    }
    return true;
  }

  getPushKey() {
    return pushKey();
  }
}

var _playerKeyStore = new PlayerKeyStore();

_playerKeyStore.dispatchToken = AppDispatcher.register(function(payload) {
  var action = payload.action;
  var source = payload.source;

  if (source === 'VIEW_ACTION') {
    switch (action.actionType) {
      case ControlConstants.SET_KEY:
        setKey(action.key);
        sendKey(action.key);
        _playerKeyStore.emitChange();
        break;

      case ControlConstants.SET_PUSH_KEY:
        setPushKey(action.key);
        _playerKeyStore.emitChange();
        break;

      case WebsocketConstants.RECONNECT:
        if (_playerKeyStore.isKeySet()) {
          sendKey(_playerKeyStore.getKey());
        }
        break;
    }
  }
  return true;
});

export default _playerKeyStore;
<|endoftext|>


<commit_before>import DatePicker from '..';
import focusTest from '../../../tests/shared/focusTest';

const { MonthPicker } = DatePicker;

describe('MonthPicker', () => {
  focusTest(MonthPicker);
});
<commit_msg>add test case for month picker<commit_after>import React from 'react';
import { mount } from 'enzyme';
import moment from 'moment';
import DatePicker from '..';
import focusTest from '../../../tests/shared/focusTest';
import { openPanel } from './utils';

const { MonthPicker } = DatePicker;

describe('MonthPicker', () => {
  focusTest(MonthPicker);

  it('reset select item when popup close', () => {
    const wrapper = mount(
      <MonthPicker value={moment('2018-07-01')} />
    );
    openPanel(wrapper);
    wrapper.find('.ant-calendar-month-panel-month').first().simulate('click');
    wrapper.find('.ant-calendar-month-panel-cell').at(6).hasClass('ant-calendar-month-panel-selected-cell');
  });
});
<|endoftext|>


<commit_before>// @flow
import * as React from 'react'
import * as Container from '../../../../../util/container'
import * as Constants from '../../../../../constants/chat2'
import * as Types from '../../../../../constants/types/chat2'
import * as WalletTypes from '../../../../../constants/types/wallets'
import * as WalletGen from '../../../../../actions/wallets-gen'
import * as RouteTreeGen from '../../../../../actions/route-tree-gen'
import * as Styles from '../../../../../styles'
import {walletsTab} from '../../../../../constants/tabs'
import {formatTimeForMessages} from '../../../../../util/timestamp'
import PaymentPopup from '.'
import type {Position} from '../../../../../common-adapters/relative-popup-hoc'

// This file has two connectors and a wrapper. One connector is for sendPayment
// and the other for requestPayment. The wrapper decides which to use.

type OwnProps = {|
  attachTo: () => ?React.Component<any>,
  message: Types.MessageRequestPayment | Types.MessageSendPayment,
  onHidden: () => void,
  position: Position,
  visible: boolean,
|}

type SendOwnProps = {|
  ...OwnProps,
  message: Types.MessageSendPayment,
|}

type RequestOwnProps = {
  ...OwnProps,
  message: Types.MessageRequestPayment,
}

const commonLoadingProps = {
  amountNominal: '',
  balanceChange: '',
  balanceChangeColor: '',
  bottomLine: '',
  icon: 'sending',
  loading: true,
  onCancel: null,
  onSeeDetails: null,
  sender: '',
  senderDeviceName: '',
  timestamp: '',
  topLine: '',
  txVerb: 'sent',
}

// MessageSendPayment ===================================
const sendMapStateToProps = (state: Container.TypedState, ownProps: SendOwnProps) => ({
  paymentInfo: Constants.getPaymentMessageInfo(state, ownProps.message),
  _you: state.config.username,
})

const sendMapDispatchToProps = dispatch => ({
  onSeeDetails: (accountID: WalletTypes.AccountID, paymentID: WalletTypes.PaymentID) => {
    dispatch(WalletGen.createSelectAccount({accountID}))
    dispatch(
      RouteTreeGen.createNavigateTo({
        path: [walletsTab, 'wallet', {selected: 'transactionDetails', props: {accountID, paymentID}}],
      })
    )
  },
})

const sendMergeProps = (stateProps, dispatchProps, ownProps: SendOwnProps) => {
  if (ownProps.message.type !== 'sendPayment') {
    throw new Error(`SendPaymentPopup: impossible case encountered: ${ownProps.message.type}`)
  }
  const {paymentInfo} = stateProps
  if (!paymentInfo) {
    return {
      ...commonLoadingProps,
      attachTo: ownProps.attachTo,
      onHidden: ownProps.onHidden,
      position: ownProps.position,
      visible: ownProps.visible,
    }
  }
  const {_you: you} = stateProps
  return {
    amountNominal: paymentInfo.worth || paymentInfo.amountDescription,
    attachTo: ownProps.attachTo,
    balanceChange: `${paymentInfo.delta === 'increase' ? '+' : '-'}${paymentInfo.amountDescription}`,
    balanceChangeColor:
      paymentInfo.delta === 'increase' ? Styles.globalColors.green : Styles.globalColors.red,
    bottomLine: '', // TODO on asset support in payment
    icon: paymentInfo.delta === 'increase' ? 'receiving' : 'sending',
    loading: false,
    onCancel: null,
    onHidden: ownProps.onHidden,
    onSeeDetails: () => dispatchProps.onSeeDetails(paymentInfo.accountID, paymentInfo.paymentID),
    position: ownProps.position,
    sender: ownProps.message.author,
    senderDeviceName: ownProps.message.deviceName,
    timestamp: formatTimeForMessages(ownProps.message.timestamp),
    topLine: `${ownProps.message.author === you ? 'you sent' : 'you received'}${
      paymentInfo.worth ? ' Lumens worth' : ''
    }`,
    txVerb: 'sent',
    visible: ownProps.visible,
  }
}

const SendPaymentPopup = Container.connect(sendMapStateToProps, sendMapDispatchToProps, sendMergeProps)(
  PaymentPopup
)

// MessageRequestPayment ================================
const requestMapStateToProps = (state: Container.TypedState, ownProps: RequestOwnProps) => ({
  requestInfo: Constants.getRequestMessageInfo(state, ownProps.message),
  _you: state.config.username,
})

const requestMapDispatchToProps = (dispatch, ownProps: RequestOwnProps) => ({
  onCancel: () => {
    if (ownProps.message.type !== 'requestPayment') {
      throw new Error(`RequestPaymentPopup: impossible case encountered: ${ownProps.message.type}`)
    }
    dispatch(
      WalletGen.createCancelRequest({
        conversationIDKey: ownProps.message.conversationIDKey,
        ordinal: ownProps.message.ordinal,
        requestID: ownProps.message.requestID,
      })
    )
  },
})

const requestMergeProps = (stateProps, dispatchProps, ownProps: RequestOwnProps) => {
  const {_you: you} = stateProps
  const {message} = ownProps
  if (message.type !== 'requestPayment') {
    throw new Error(`RequestPaymentPopup: impossible case encountered: ${message.type}`)
  }
  const {requestInfo} = stateProps
  if (!requestInfo) {
    return {
      ...commonLoadingProps,
      attachTo: ownProps.attachTo,
      onHidden: ownProps.onHidden,
      position: ownProps.position,
      visible: ownProps.visible,
    }
  }

  let bottomLine = ''
  if (requestInfo.asset !== 'native' && requestInfo.asset !== 'currency') {
    bottomLine = requestInfo.asset.issuerName || requestInfo.asset.issuerAccountID || ''
  }

  let topLine = `${ownProps.message.author === you ? 'you requested' : 'requested'}${
    requestInfo.asset === 'currency' ? ' Lumens worth' : ''
  }`

  return {
    amountNominal: requestInfo.amountDescription,
    attachTo: ownProps.attachTo,
    balanceChange: '',
    balanceChangeColor: '',
    bottomLine,
    icon: 'receiving',
    loading: false,
    onCancel: ownProps.message.author === you ? dispatchProps.onCancel : null,
    onHidden: ownProps.onHidden,
    onSeeDetails: null,
    position: ownProps.position,
    sender: ownProps.message.author,
    senderDeviceName: ownProps.message.deviceName,
    timestamp: formatTimeForMessages(ownProps.message.timestamp),
    topLine,
    txVerb: 'requested',
    visible: ownProps.visible,
  }
}

const RequestPaymentPopup = Container.connect(
  requestMapStateToProps,
  requestMapDispatchToProps,
  requestMergeProps
)(PaymentPopup)

// Wrapper ==============================================
const PaymentPopupChooser = (props: OwnProps) => {
  switch (props.message.type) {
    case 'sendPayment':
      // $FlowIssue doesn't understand message is the right type
      return <SendPaymentPopup {...props} />
    case 'requestPayment':
      // $FlowIssue doesn't understand message is the right type
      return <RequestPaymentPopup {...props} />
    default:
      throw new Error(`PaymentPopup: impossible case encountered: ${props.message.type}`)
  }
}

export default PaymentPopupChooser
<commit_msg>use getBalanceChangeColor in message menu (#14114)<commit_after>// @flow
import * as React from 'react'
import * as Container from '../../../../../util/container'
import * as Constants from '../../../../../constants/chat2'
import * as Types from '../../../../../constants/types/chat2'
import * as WalletConstants from '../../../../../constants/wallets'
import * as WalletTypes from '../../../../../constants/types/wallets'
import * as WalletGen from '../../../../../actions/wallets-gen'
import * as RouteTreeGen from '../../../../../actions/route-tree-gen'
import {walletsTab} from '../../../../../constants/tabs'
import {formatTimeForMessages} from '../../../../../util/timestamp'
import PaymentPopup from '.'
import type {Position} from '../../../../../common-adapters/relative-popup-hoc'

// This file has two connectors and a wrapper. One connector is for sendPayment
// and the other for requestPayment. The wrapper decides which to use.

type OwnProps = {|
  attachTo: () => ?React.Component<any>,
  message: Types.MessageRequestPayment | Types.MessageSendPayment,
  onHidden: () => void,
  position: Position,
  visible: boolean,
|}

type SendOwnProps = {|
  ...OwnProps,
  message: Types.MessageSendPayment,
|}

type RequestOwnProps = {
  ...OwnProps,
  message: Types.MessageRequestPayment,
}

const commonLoadingProps = {
  amountNominal: '',
  balanceChange: '',
  balanceChangeColor: '',
  bottomLine: '',
  icon: 'sending',
  loading: true,
  onCancel: null,
  onSeeDetails: null,
  sender: '',
  senderDeviceName: '',
  timestamp: '',
  topLine: '',
  txVerb: 'sent',
}

// MessageSendPayment ===================================
const sendMapStateToProps = (state: Container.TypedState, ownProps: SendOwnProps) => ({
  paymentInfo: Constants.getPaymentMessageInfo(state, ownProps.message),
  _you: state.config.username,
})

const sendMapDispatchToProps = dispatch => ({
  onSeeDetails: (accountID: WalletTypes.AccountID, paymentID: WalletTypes.PaymentID) => {
    dispatch(WalletGen.createSelectAccount({accountID}))
    dispatch(
      RouteTreeGen.createNavigateTo({
        path: [walletsTab, 'wallet', {selected: 'transactionDetails', props: {accountID, paymentID}}],
      })
    )
  },
})

const sendMergeProps = (stateProps, dispatchProps, ownProps: SendOwnProps) => {
  if (ownProps.message.type !== 'sendPayment') {
    throw new Error(`SendPaymentPopup: impossible case encountered: ${ownProps.message.type}`)
  }
  const {paymentInfo} = stateProps
  if (!paymentInfo) {
    return {
      ...commonLoadingProps,
      attachTo: ownProps.attachTo,
      onHidden: ownProps.onHidden,
      position: ownProps.position,
      visible: ownProps.visible,
    }
  }
  const {_you: you} = stateProps
  let sign = ''
  if (paymentInfo.delta !== 'none') {
    sign = paymentInfo.delta === 'increase' ? '+' : '-'
  }
  return {
    amountNominal: paymentInfo.worth || paymentInfo.amountDescription,
    attachTo: ownProps.attachTo,
    balanceChange: `${sign}${paymentInfo.amountDescription}`,
    balanceChangeColor: WalletConstants.getBalanceChangeColor(paymentInfo.delta, paymentInfo.status),
    bottomLine: '', // TODO on asset support in payment
    icon: paymentInfo.delta === 'increase' ? 'receiving' : 'sending',
    loading: false,
    onCancel: null,
    onHidden: ownProps.onHidden,
    onSeeDetails: () => dispatchProps.onSeeDetails(paymentInfo.accountID, paymentInfo.paymentID),
    position: ownProps.position,
    sender: ownProps.message.author,
    senderDeviceName: ownProps.message.deviceName,
    timestamp: formatTimeForMessages(ownProps.message.timestamp),
    topLine: `${ownProps.message.author === you ? 'you sent' : 'you received'}${
      paymentInfo.worth ? ' Lumens worth' : ''
    }`,
    txVerb: 'sent',
    visible: ownProps.visible,
  }
}

const SendPaymentPopup = Container.connect(sendMapStateToProps, sendMapDispatchToProps, sendMergeProps)(
  PaymentPopup
)

// MessageRequestPayment ================================
const requestMapStateToProps = (state: Container.TypedState, ownProps: RequestOwnProps) => ({
  requestInfo: Constants.getRequestMessageInfo(state, ownProps.message),
  _you: state.config.username,
})

const requestMapDispatchToProps = (dispatch, ownProps: RequestOwnProps) => ({
  onCancel: () => {
    if (ownProps.message.type !== 'requestPayment') {
      throw new Error(`RequestPaymentPopup: impossible case encountered: ${ownProps.message.type}`)
    }
    dispatch(
      WalletGen.createCancelRequest({
        conversationIDKey: ownProps.message.conversationIDKey,
        ordinal: ownProps.message.ordinal,
        requestID: ownProps.message.requestID,
      })
    )
  },
})

const requestMergeProps = (stateProps, dispatchProps, ownProps: RequestOwnProps) => {
  const {_you: you} = stateProps
  const {message} = ownProps
  if (message.type !== 'requestPayment') {
    throw new Error(`RequestPaymentPopup: impossible case encountered: ${message.type}`)
  }
  const {requestInfo} = stateProps
  if (!requestInfo) {
    return {
      ...commonLoadingProps,
      attachTo: ownProps.attachTo,
      onHidden: ownProps.onHidden,
      position: ownProps.position,
      visible: ownProps.visible,
    }
  }

  let bottomLine = ''
  if (requestInfo.asset !== 'native' && requestInfo.asset !== 'currency') {
    bottomLine = requestInfo.asset.issuerName || requestInfo.asset.issuerAccountID || ''
  }

  let topLine = `${ownProps.message.author === you ? 'you requested' : 'requested'}${
    requestInfo.asset === 'currency' ? ' Lumens worth' : ''
  }`

  return {
    amountNominal: requestInfo.amountDescription,
    attachTo: ownProps.attachTo,
    balanceChange: '',
    balanceChangeColor: '',
    bottomLine,
    icon: 'receiving',
    loading: false,
    onCancel: ownProps.message.author === you ? dispatchProps.onCancel : null,
    onHidden: ownProps.onHidden,
    onSeeDetails: null,
    position: ownProps.position,
    sender: ownProps.message.author,
    senderDeviceName: ownProps.message.deviceName,
    timestamp: formatTimeForMessages(ownProps.message.timestamp),
    topLine,
    txVerb: 'requested',
    visible: ownProps.visible,
  }
}

const RequestPaymentPopup = Container.connect(
  requestMapStateToProps,
  requestMapDispatchToProps,
  requestMergeProps
)(PaymentPopup)

// Wrapper ==============================================
const PaymentPopupChooser = (props: OwnProps) => {
  switch (props.message.type) {
    case 'sendPayment':
      // $FlowIssue doesn't understand message is the right type
      return <SendPaymentPopup {...props} />
    case 'requestPayment':
      // $FlowIssue doesn't understand message is the right type
      return <RequestPaymentPopup {...props} />
    default:
      throw new Error(`PaymentPopup: impossible case encountered: ${props.message.type}`)
  }
}

export default PaymentPopupChooser
<|endoftext|>


<commit_before>// @flow
import * as React from 'react'
import * as Types from '../../../../../constants/types/chat2'
import {Box, Box2, Icon, OverlayParentHOC, type OverlayParentProps} from '../../../../../common-adapters'
import Timestamp from '../timestamp'
import {
  glamorous,
  globalStyles,
  globalColors,
  isMobile,
  platformStyles,
  styleSheetCreate,
} from '../../../../../styles'
import WrapperAuthor from '../wrapper-author/container'
import ReactionsRow from '../../reactions-row/container'
import ReactButton from '../../react-button/container'
import MessagePopup from '../../message-popup'
import ExplodingMeta from '../exploding-meta/container'
import LongPressable from './long-pressable'

/**
 * WrapperTimestamp adds the orange line, timestamp, menu button, menu, reacji
 * button, and exploding meta tag.
 */

export type Props = {|
  conversationIDKey: Types.ConversationIDKey,
  decorate: boolean,
  exploded: boolean,
  ordinal: Types.Ordinal,
  measure: null | (() => void),
  message: Types.Message,
  previous: ?Types.Message,
  children?: React.Node,
  isEditing: boolean,
  timestamp: string,
  // 'children': render children directly
  // 'wrapper-author': additionally render WrapperAuthor and tell it the message type
  type: 'wrapper-author' | 'children',
  orangeLineAbove: boolean,
|}

const HoverBox = isMobile
  ? LongPressable
  : glamorous(Box2)(props => ({
      '& .menu-button': {
        flexShrink: 0,
        height: 17,
        opacity: 0,
        visibility: 'hidden',
      },
      '&.active .menu-button, &:hover .menu-button': {
        opacity: 1,
        visibility: 'visible',
      },
      '&.active, &:hover': props.decorate
        ? {
            backgroundColor: globalColors.blue5,
          }
        : {},
    }))

type State = {
  showingPicker: boolean,
}
class _WrapperTimestamp extends React.Component<Props & OverlayParentProps, State> {
  state = {showingPicker: false}
  componentDidUpdate(prevProps: Props) {
    if (this.props.measure) {
      if (
        this.props.orangeLineAbove !== prevProps.orangeLineAbove ||
        this.props.timestamp !== prevProps.timestamp
      ) {
        this.props.measure()
      }
    }
  }
  _setShowingPicker = (showingPicker: boolean) =>
    this.setState(s => (s.showingPicker === showingPicker ? null : {showingPicker}))
  render() {
    const props = this.props
    return (
      <Box style={styles.container}>
        {props.orangeLineAbove && <Box style={styles.orangeLine} />}
        {props.timestamp && <Timestamp timestamp={props.timestamp} />}
        <HoverBox
          className={props.showingMenu || this.state.showingPicker ? 'active' : ''}
          {...(isMobile && props.decorate
            ? {onLongPress: props.toggleShowingMenu, underlayColor: globalColors.blue5}
            : {})}
          direction="column"
          decorate={props.decorate}
          fullWidth={true}
        >
          {/* Additional Box here because NativeTouchableHighlight only supports one child */}
          <Box>
            <Box2 direction="horizontal" fullWidth={true} style={styles.alignItemsFlexEnd}>
              {props.type === 'children' && props.children}
              {/* Additional checks on props.message.type to appease flow */}
              {props.type === 'wrapper-author' &&
                (props.message.type === 'attachment' || props.message.type === 'text') && (
                  <WrapperAuthor
                    message={props.message}
                    previous={props.previous}
                    isEditing={props.isEditing}
                    measure={props.measure}
                    toggleMessageMenu={props.toggleShowingMenu}
                  />
                )}
              {!props.exploded &&
                props.decorate && (
                  <MenuButtons
                    conversationIDKey={props.conversationIDKey}
                    message={props.message}
                    ordinal={props.ordinal}
                    setAttachmentRef={props.setAttachmentRef}
                    setShowingPicker={this._setShowingPicker}
                    toggleShowingMenu={props.toggleShowingMenu}
                  />
                )}
            </Box2>
            <ReactionsRow conversationIDKey={props.conversationIDKey} ordinal={props.ordinal} />
          </Box>
        </HoverBox>
        {(props.message.type === 'attachment' || props.message.type === 'text') && (
          <MessagePopup
            attachTo={props.attachmentRef}
            message={props.message}
            onHidden={props.toggleShowingMenu}
            position="top center"
            visible={props.showingMenu}
          />
        )}
      </Box>
    )
  }
}
const WrapperTimestamp = OverlayParentHOC(_WrapperTimestamp)

type MenuButtonsProps = {
  conversationIDKey: Types.ConversationIDKey,
  message: Types.Message,
  ordinal: Types.Ordinal,
  setAttachmentRef: ?(ref: ?React.Component<any, any>) => void,
  setShowingPicker: boolean => void,
  toggleShowingMenu: () => void,
}
const MenuButtons = (props: MenuButtonsProps) => (
  <Box2 direction="horizontal" gap="tiny" gapEnd={true} style={styles.controls}>
    {!isMobile && (
      <Box className="menu-button" style={styles.menuButtons}>
        <ReactButton
          conversationIDKey={props.conversationIDKey}
          ordinal={props.ordinal}
          onShowPicker={props.setShowingPicker}
          showBorder={false}
          style={styles.reactButton}
        />
        <Box ref={props.setAttachmentRef}>
          {(props.message.type === 'attachment' || props.message.type === 'text') && (
            <Icon type="iconfont-ellipsis" onClick={props.toggleShowingMenu} fontSize={16} />
          )}
        </Box>
      </Box>
    )}
    <ExplodingMeta
      conversationIDKey={props.conversationIDKey}
      onClick={props.toggleShowingMenu}
      ordinal={props.ordinal}
    />
  </Box2>
)

const styles = styleSheetCreate({
  container: {...globalStyles.flexBoxColumn, width: '100%'},
  controls: platformStyles({
    common: {
      alignItems: 'center',
      alignSelf: 'flex-start',
      marginTop: 2,
    },
  }),
  menuButtons: platformStyles({
    isElectron: {
      ...globalStyles.flexBoxRow,
      alignItems: 'center',
    },
  }),
  orangeLine: {backgroundColor: globalColors.orange, height: 1, width: '100%'},
  reactButton: {
    marginTop: -3,
  },
})

export default WrapperTimestamp
<commit_msg>no strings in boxes (#13425)<commit_after>// @flow
import * as React from 'react'
import * as Types from '../../../../../constants/types/chat2'
import {Box, Box2, Icon, OverlayParentHOC, type OverlayParentProps} from '../../../../../common-adapters'
import Timestamp from '../timestamp'
import {
  glamorous,
  globalStyles,
  globalColors,
  isMobile,
  platformStyles,
  styleSheetCreate,
} from '../../../../../styles'
import WrapperAuthor from '../wrapper-author/container'
import ReactionsRow from '../../reactions-row/container'
import ReactButton from '../../react-button/container'
import MessagePopup from '../../message-popup'
import ExplodingMeta from '../exploding-meta/container'
import LongPressable from './long-pressable'

/**
 * WrapperTimestamp adds the orange line, timestamp, menu button, menu, reacji
 * button, and exploding meta tag.
 */

export type Props = {|
  conversationIDKey: Types.ConversationIDKey,
  decorate: boolean,
  exploded: boolean,
  ordinal: Types.Ordinal,
  measure: null | (() => void),
  message: Types.Message,
  previous: ?Types.Message,
  children?: React.Node,
  isEditing: boolean,
  timestamp: string,
  // 'children': render children directly
  // 'wrapper-author': additionally render WrapperAuthor and tell it the message type
  type: 'wrapper-author' | 'children',
  orangeLineAbove: boolean,
|}

const HoverBox = isMobile
  ? LongPressable
  : glamorous(Box2)(props => ({
      '& .menu-button': {
        flexShrink: 0,
        height: 17,
        opacity: 0,
        visibility: 'hidden',
      },
      '&.active .menu-button, &:hover .menu-button': {
        opacity: 1,
        visibility: 'visible',
      },
      '&.active, &:hover': props.decorate
        ? {
            backgroundColor: globalColors.blue5,
          }
        : {},
    }))

type State = {
  showingPicker: boolean,
}
class _WrapperTimestamp extends React.Component<Props & OverlayParentProps, State> {
  state = {showingPicker: false}
  componentDidUpdate(prevProps: Props) {
    if (this.props.measure) {
      if (
        this.props.orangeLineAbove !== prevProps.orangeLineAbove ||
        this.props.timestamp !== prevProps.timestamp
      ) {
        this.props.measure()
      }
    }
  }
  _setShowingPicker = (showingPicker: boolean) =>
    this.setState(s => (s.showingPicker === showingPicker ? null : {showingPicker}))
  render() {
    const props = this.props
    return (
      <Box style={styles.container}>
        {props.orangeLineAbove && <Box style={styles.orangeLine} />}
        {!!props.timestamp && <Timestamp timestamp={props.timestamp} />}
        <HoverBox
          className={props.showingMenu || this.state.showingPicker ? 'active' : ''}
          {...(isMobile && props.decorate
            ? {onLongPress: props.toggleShowingMenu, underlayColor: globalColors.blue5}
            : {})}
          direction="column"
          decorate={props.decorate}
          fullWidth={true}
        >
          {/* Additional Box here because NativeTouchableHighlight only supports one child */}
          <Box>
            <Box2 direction="horizontal" fullWidth={true} style={styles.alignItemsFlexEnd}>
              {props.type === 'children' && props.children}
              {/* Additional checks on props.message.type to appease flow */}
              {props.type === 'wrapper-author' &&
                (props.message.type === 'attachment' || props.message.type === 'text') && (
                  <WrapperAuthor
                    message={props.message}
                    previous={props.previous}
                    isEditing={props.isEditing}
                    measure={props.measure}
                    toggleMessageMenu={props.toggleShowingMenu}
                  />
                )}
              {!props.exploded &&
                !!props.decorate && (
                  <MenuButtons
                    conversationIDKey={props.conversationIDKey}
                    message={props.message}
                    ordinal={props.ordinal}
                    setAttachmentRef={props.setAttachmentRef}
                    setShowingPicker={this._setShowingPicker}
                    toggleShowingMenu={props.toggleShowingMenu}
                  />
                )}
            </Box2>
            <ReactionsRow conversationIDKey={props.conversationIDKey} ordinal={props.ordinal} />
          </Box>
        </HoverBox>
        {(props.message.type === 'attachment' || props.message.type === 'text') && (
          <MessagePopup
            attachTo={props.attachmentRef}
            message={props.message}
            onHidden={props.toggleShowingMenu}
            position="top center"
            visible={props.showingMenu}
          />
        )}
      </Box>
    )
  }
}
const WrapperTimestamp = OverlayParentHOC(_WrapperTimestamp)

type MenuButtonsProps = {
  conversationIDKey: Types.ConversationIDKey,
  message: Types.Message,
  ordinal: Types.Ordinal,
  setAttachmentRef: ?(ref: ?React.Component<any, any>) => void,
  setShowingPicker: boolean => void,
  toggleShowingMenu: () => void,
}
const MenuButtons = (props: MenuButtonsProps) => (
  <Box2 direction="horizontal" gap="tiny" gapEnd={true} style={styles.controls}>
    {!isMobile && (
      <Box className="menu-button" style={styles.menuButtons}>
        <ReactButton
          conversationIDKey={props.conversationIDKey}
          ordinal={props.ordinal}
          onShowPicker={props.setShowingPicker}
          showBorder={false}
          style={styles.reactButton}
        />
        <Box ref={props.setAttachmentRef}>
          {(props.message.type === 'attachment' || props.message.type === 'text') && (
            <Icon type="iconfont-ellipsis" onClick={props.toggleShowingMenu} fontSize={16} />
          )}
        </Box>
      </Box>
    )}
    <ExplodingMeta
      conversationIDKey={props.conversationIDKey}
      onClick={props.toggleShowingMenu}
      ordinal={props.ordinal}
    />
  </Box2>
)

const styles = styleSheetCreate({
  container: {...globalStyles.flexBoxColumn, width: '100%'},
  controls: platformStyles({
    common: {
      alignItems: 'center',
      alignSelf: 'flex-start',
      marginTop: 2,
    },
  }),
  menuButtons: platformStyles({
    isElectron: {
      ...globalStyles.flexBoxRow,
      alignItems: 'center',
    },
  }),
  orangeLine: {backgroundColor: globalColors.orange, height: 1, width: '100%'},
  reactButton: {
    marginTop: -3,
  },
})

export default WrapperTimestamp
<|endoftext|>


<commit_before>import Immutable from 'immutable';
import PropTypes from 'prop-types';
import * as React from 'react';
import CellMeasurer from './CellMeasurer';
import CellMeasurerCache from './CellMeasurerCache';
import {Column, Table} from '../Table';
import styles from './CellMeasurer.example.css';

export default class DynamicHeightTableColumn extends React.PureComponent {
  static propTypes = {
    list: PropTypes.instanceOf(Immutable.List).isRequired,
    width: PropTypes.number.isRequired,
  };

  _cache = new CellMeasurerCache({
    fixedWidth: true,
    minHeight: 25,
  });

  _lastRenderedWidth = this.props.width;

  render() {
    const {width} = this.props;

    if (this._lastRenderedWidth !== this.props.width) {
      this._lastRenderedWidth = this.props.width;
      this._cache.clearAll();
    }

    return (
      <Table
        deferredMeasurementCache={this._cache}
        headerHeight={20}
        height={400}
        overscanRowCount={2}
        rowClassName={styles.tableRow}
        rowHeight={this._cache.rowHeight}
        rowGetter={this._rowGetter}
        rowCount={1000}
        width={width}>
        <Column
          className={styles.tableColumn}
          dataKey="name"
          label="Name"
          width={125}
        />
        <Column
          className={styles.tableColumn}
          dataKey="color"
          label="Color"
          width={75}
        />
        <Column
          width={width - 200}
          dataKey="random"
          label="Dyanmic text"
          cellRenderer={this._columnCellRenderer}
        />
      </Table>
    );
  }

  _columnCellRenderer = ({dataKey, parent, rowIndex}) => {
    const {list} = this.props;

    const datum = list.get(rowIndex % list.size);
    const content = rowIndex % 5 === 0 ? '' : datum.randomLong;

    return (
      <CellMeasurer
        cache={this._cache}
        columnIndex={0}
        key={dataKey}
        parent={parent}
        rowIndex={rowIndex}>
        <div
          className={styles.tableColumn}
          style={{
            whiteSpace: 'normal',
          }}>
          {content}
        </div>
      </CellMeasurer>
    );
  };

  _rowGetter = ({index}) => {
    const {list} = this.props;

    return list.get(index % list.size);
  };
}
<commit_msg>Fix typo in CellMeasurer.DynamicHeightTableColumn.example.js (#1190)<commit_after>import Immutable from 'immutable';
import PropTypes from 'prop-types';
import * as React from 'react';
import CellMeasurer from './CellMeasurer';
import CellMeasurerCache from './CellMeasurerCache';
import {Column, Table} from '../Table';
import styles from './CellMeasurer.example.css';

export default class DynamicHeightTableColumn extends React.PureComponent {
  static propTypes = {
    list: PropTypes.instanceOf(Immutable.List).isRequired,
    width: PropTypes.number.isRequired,
  };

  _cache = new CellMeasurerCache({
    fixedWidth: true,
    minHeight: 25,
  });

  _lastRenderedWidth = this.props.width;

  render() {
    const {width} = this.props;

    if (this._lastRenderedWidth !== this.props.width) {
      this._lastRenderedWidth = this.props.width;
      this._cache.clearAll();
    }

    return (
      <Table
        deferredMeasurementCache={this._cache}
        headerHeight={20}
        height={400}
        overscanRowCount={2}
        rowClassName={styles.tableRow}
        rowHeight={this._cache.rowHeight}
        rowGetter={this._rowGetter}
        rowCount={1000}
        width={width}>
        <Column
          className={styles.tableColumn}
          dataKey="name"
          label="Name"
          width={125}
        />
        <Column
          className={styles.tableColumn}
          dataKey="color"
          label="Color"
          width={75}
        />
        <Column
          width={width - 200}
          dataKey="random"
          label="Dynamic text"
          cellRenderer={this._columnCellRenderer}
        />
      </Table>
    );
  }

  _columnCellRenderer = ({dataKey, parent, rowIndex}) => {
    const {list} = this.props;

    const datum = list.get(rowIndex % list.size);
    const content = rowIndex % 5 === 0 ? '' : datum.randomLong;

    return (
      <CellMeasurer
        cache={this._cache}
        columnIndex={0}
        key={dataKey}
        parent={parent}
        rowIndex={rowIndex}>
        <div
          className={styles.tableColumn}
          style={{
            whiteSpace: 'normal',
          }}>
          {content}
        </div>
      </CellMeasurer>
    );
  };

  _rowGetter = ({index}) => {
    const {list} = this.props;

    return list.get(index % list.size);
  };
}
<|endoftext|>