<commit_before>var fs = require('fs');
exports.commands = {

 dm: 'daymute',
	daymute: function (target, room, user) {
		if (!target) return this.parse('/help daymute');
		if (!this.can('mute', targetUser, room)) return false;
                if (room.isMuted(user) && !user.can('bypassall')) return this.errorReply("You cannot do this while unable to talk.");

		target = this.splitTarget(target);
		var targetUser = this.targetUser;
		if (!targetUser) return this.sendReply('User "' + this.targetUsername + '" not found.');

		if (((targetUser.mutedRooms[room.id] && (targetUser.muteDuration[room.id] || 0) >= 50 * 60 * 1000) || targetUser.locked) && !target) {
			var problem = ' but was already ' + (!targetUser.connected ? 'offline' : targetUser.locked ? 'locked' : 'muted');
			return this.privateModCommand('(' + targetUser.name + ' would be muted by ' + user.name + problem + '.)');
		}

		targetUser.popup(user.name + ' has muted you for 24 hours. ' + target);
		this.addModCommand('' + targetUser.name + ' was muted by ' + user.name + ' for 24 hours.' + (target ? " (" + target + ")" : ""));
		var alts = targetUser.getAlts();
		if (alts.length) this.addModCommand("" + targetUser.name + "'s alts were also muted: " + alts.join(", "));

		targetUser.mute(room.id, 24 * 60 * 60 * 1000, true);
	},

};
<commit_msg>Update daymute.js<commit_after>var fs = require('fs');
exports.commands = {

 dm: 'daymute',
	daymute: function (target, room, user) {
		if (!target) return this.parse('/help daymute');
		if (!this.can('mute', targetUser, room)) return false;
                if (room.isMuted(user) && !user.can('bypassall')) return this.errorReply("You cannot do this while unable to talk.");

		target = this.splitTarget(target);
		var targetUser = this.targetUser;
		if (!targetUser) return this.sendReply('User "' + this.targetUsername + '" not found.');

		if ((room.isMuted(targetUser) && !canBeMutedFurther) || targetUser.locked || !targetUser.connected) {
			var problem = " but was already " + (!targetUser.connected ? "offline" : targetUser.locked ? "locked" : "licked");
			if (!target) {
				return this.privateModCommand("(" + targetUser.name + " would be muted by " + user.name + problem + ".)");
			}
			return this.addModCommand("" + targetUser.name + " would be muted by " + user.name + problem + "." + (target ? " (" + target + ")" : ""));
		}

		targetUser.popup(user.name + ' has muted you for 24 hours. ' + target);
		this.addModCommand('' + targetUser.name + ' was muted by ' + user.name + ' for 24 hours.' + (target ? " (" + target + ")" : ""));
		var alts = targetUser.getAlts();
		if (alts.length) this.addModCommand("" + targetUser.name + "'s alts were also muted: " + alts.join(", "));

		targetUser.mute(room.id, 24 * 60 * 60 * 1000, true);
	},

};
<|endoftext|>


<commit_before>var globalAnimations = (function(){
    var all = document.querySelectorAll(".js-animations main > *");
    console.log(all);
    for (var i = 0, max = all.length; i < max; i++) {
        animate({
            el: all[i],
            delay: 2000 + (50 * i),
            duration: 2000,
            translateY: [20, 0],
            opacity: [0, 1],
            easing: "easeInOutQuint"
        });
    }
})();<commit_msg>removed console log<commit_after>var globalAnimations = (function(){
    var all = document.querySelectorAll(".js-animations main > *");
    for (var i = 0, max = all.length; i < max; i++) {
        animate({
            el: all[i],
            delay: 2000 + (50 * i),
            duration: 2000,
            translateY: [20, 0],
            opacity: [0, 1],
            easing: "easeInOutQuint"
        });
    }
})();<|endoftext|>


<commit_before>/**
@module ember
@submodule ember-handlebars-compiler
*/

// Eliminate dependency on any Ember to simplify precompilation workflow
var objectCreate = Object.create || function(parent) {
  function F() {}
  F.prototype = parent;
  return new F();
};

var Handlebars = this.Handlebars || (Ember.imports && Ember.imports.Handlebars);
if(!Handlebars && typeof require === 'function') {
  Handlebars = require('handlebars');
}

Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater. Include a SCRIPT tag in the HTML HEAD linking to the Handlebars file before you link to Ember.", Handlebars && Handlebars.COMPILER_REVISION === 2);

/**
  Prepares the Handlebars templating library for use inside Ember's view
  system.

  The `Ember.Handlebars` object is the standard Handlebars library, extended to
  use Ember's `get()` method instead of direct property access, which allows
  computed properties to be used inside templates.

  To create an `Ember.Handlebars` template, call `Ember.Handlebars.compile()`.
  This will return a function that can be used by `Ember.View` for rendering.

  @class Handlebars
  @namespace Ember
*/
Ember.Handlebars = objectCreate(Handlebars);

Ember.Handlebars.helper = function(name, value) {
  if (Ember.View.detect(value)) {
    Ember.Handlebars.registerHelper(name, function(options) {
      Ember.assert("You can only pass attributes as parameters to a application-defined helper", arguments.length < 3);
      options.hash = Ember.Handlebars.resolveHash(this, options.hash, options);
      return Ember.Handlebars.helpers.view.call(this, value, options);
    });
  } else {
    Ember.Handlebars.registerBoundHelper.apply(null, arguments);
  }
}

/**
@class helpers
@namespace Ember.Handlebars
*/
Ember.Handlebars.helpers = objectCreate(Handlebars.helpers);

/**
  Override the the opcode compiler and JavaScript compiler for Handlebars.

  @class Compiler
  @namespace Ember.Handlebars
  @private
  @constructor
*/
Ember.Handlebars.Compiler = function() {};

// Handlebars.Compiler doesn't exist in runtime-only
if (Handlebars.Compiler) {
  Ember.Handlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
}

Ember.Handlebars.Compiler.prototype.compiler = Ember.Handlebars.Compiler;

/**
  @class JavaScriptCompiler
  @namespace Ember.Handlebars
  @private
  @constructor
*/
Ember.Handlebars.JavaScriptCompiler = function() {};

// Handlebars.JavaScriptCompiler doesn't exist in runtime-only
if (Handlebars.JavaScriptCompiler) {
  Ember.Handlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
  Ember.Handlebars.JavaScriptCompiler.prototype.compiler = Ember.Handlebars.JavaScriptCompiler;
}

Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";

Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
  return "''";
};

/**
  @private

  Override the default buffer for Ember Handlebars. By default, Handlebars
  creates an empty String at the beginning of each invocation and appends to
  it. Ember's Handlebars overrides this to append to a single shared buffer.

  @method appendToBuffer
  @param string {String}
*/
Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
  return "data.buffer.push("+string+");";
};

var prefix = "ember" + (+new Date()), incr = 1;

/**
  @private

  Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that
  all simple mustaches in Ember's Handlebars will also set up an observer to
  keep the DOM up to date when the underlying property changes.

  @method mustache
  @for Ember.Handlebars.Compiler
  @param mustache
*/
Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
  if (mustache.isHelper && mustache.id.string === 'control') {
    mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
    mustache.hash.pairs.push(["controlID", new Handlebars.AST.StringNode(prefix + incr++)]);
  } else if (mustache.params.length || mustache.hash) {
    // no changes required
  } else {
    var id = new Handlebars.AST.IdNode(['_triageMustache']);

    // Update the mustache node to include a hash value indicating whether the original node
    // was escaped. This will allow us to properly escape values when the underlying value
    // changes and we need to re-render the value.
    if(!mustache.escaped) {
      mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
      mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
    }
    mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
  }

  return Handlebars.Compiler.prototype.mustache.call(this, mustache);
};

/**
  Used for precompilation of Ember Handlebars templates. This will not be used
  during normal app execution.

  @method precompile
  @for Ember.Handlebars
  @static
  @param {String} string The template to precompile
*/
Ember.Handlebars.precompile = function(string) {
  var ast = Handlebars.parse(string);

  var options = {
    knownHelpers: {
      action: true,
      unbound: true,
      bindAttr: true,
      template: true,
      view: true,
      _triageMustache: true
    },
    data: true,
    stringParams: true
  };

  var environment = new Ember.Handlebars.Compiler().compile(ast, options);
  return new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
};

// We don't support this for Handlebars runtime-only
if (Handlebars.compile) {
  /**
    The entry point for Ember Handlebars. This replaces the default
    `Handlebars.compile` and turns on template-local data and String
    parameters.

    @method compile
    @for Ember.Handlebars
    @static
    @param {String} string The template to compile
    @return {Function}
  */
  Ember.Handlebars.compile = function(string) {
    var ast = Handlebars.parse(string);
    var options = { data: true, stringParams: true };
    var environment = new Ember.Handlebars.Compiler().compile(ast, options);
    var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);

    return Ember.Handlebars.template(templateSpec);
  };
}

<commit_msg>Make view helpers work with bindings<commit_after>/**
@module ember
@submodule ember-handlebars-compiler
*/

// Eliminate dependency on any Ember to simplify precompilation workflow
var objectCreate = Object.create || function(parent) {
  function F() {}
  F.prototype = parent;
  return new F();
};

var Handlebars = this.Handlebars || (Ember.imports && Ember.imports.Handlebars);
if(!Handlebars && typeof require === 'function') {
  Handlebars = require('handlebars');
}

Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater. Include a SCRIPT tag in the HTML HEAD linking to the Handlebars file before you link to Ember.", Handlebars && Handlebars.COMPILER_REVISION === 2);

/**
  Prepares the Handlebars templating library for use inside Ember's view
  system.

  The `Ember.Handlebars` object is the standard Handlebars library, extended to
  use Ember's `get()` method instead of direct property access, which allows
  computed properties to be used inside templates.

  To create an `Ember.Handlebars` template, call `Ember.Handlebars.compile()`.
  This will return a function that can be used by `Ember.View` for rendering.

  @class Handlebars
  @namespace Ember
*/
Ember.Handlebars = objectCreate(Handlebars);

function makeBindings(options) {
  var hash = options.hash,
      hashType = options.hashTypes;

  for (var prop in hash) {
    if (hashType[prop] === 'ID') {
      hash[prop + 'Binding'] = hash[prop];
      hashType[prop + 'Binding'] = 'STRING';
      delete hash[prop];
      delete hashType[prop];
    }
  }
}

Ember.Handlebars.helper = function(name, value) {
  if (Ember.View.detect(value)) {
    Ember.Handlebars.registerHelper(name, function(options) {
      Ember.assert("You can only pass attributes as parameters to a application-defined helper", arguments.length < 3);
      makeBindings(options);
      return Ember.Handlebars.helpers.view.call(this, value, options);
    });
  } else {
    Ember.Handlebars.registerBoundHelper.apply(null, arguments);
  }
}

/**
@class helpers
@namespace Ember.Handlebars
*/
Ember.Handlebars.helpers = objectCreate(Handlebars.helpers);

/**
  Override the the opcode compiler and JavaScript compiler for Handlebars.

  @class Compiler
  @namespace Ember.Handlebars
  @private
  @constructor
*/
Ember.Handlebars.Compiler = function() {};

// Handlebars.Compiler doesn't exist in runtime-only
if (Handlebars.Compiler) {
  Ember.Handlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
}

Ember.Handlebars.Compiler.prototype.compiler = Ember.Handlebars.Compiler;

/**
  @class JavaScriptCompiler
  @namespace Ember.Handlebars
  @private
  @constructor
*/
Ember.Handlebars.JavaScriptCompiler = function() {};

// Handlebars.JavaScriptCompiler doesn't exist in runtime-only
if (Handlebars.JavaScriptCompiler) {
  Ember.Handlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
  Ember.Handlebars.JavaScriptCompiler.prototype.compiler = Ember.Handlebars.JavaScriptCompiler;
}

Ember.Handlebars.JavaScriptCompiler.prototype.namespace = "Ember.Handlebars";

Ember.Handlebars.JavaScriptCompiler.prototype.initializeBuffer = function() {
  return "''";
};

/**
  @private

  Override the default buffer for Ember Handlebars. By default, Handlebars
  creates an empty String at the beginning of each invocation and appends to
  it. Ember's Handlebars overrides this to append to a single shared buffer.

  @method appendToBuffer
  @param string {String}
*/
Ember.Handlebars.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
  return "data.buffer.push("+string+");";
};

var prefix = "ember" + (+new Date()), incr = 1;

/**
  @private

  Rewrite simple mustaches from `{{foo}}` to `{{bind "foo"}}`. This means that
  all simple mustaches in Ember's Handlebars will also set up an observer to
  keep the DOM up to date when the underlying property changes.

  @method mustache
  @for Ember.Handlebars.Compiler
  @param mustache
*/
Ember.Handlebars.Compiler.prototype.mustache = function(mustache) {
  if (mustache.isHelper && mustache.id.string === 'control') {
    mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
    mustache.hash.pairs.push(["controlID", new Handlebars.AST.StringNode(prefix + incr++)]);
  } else if (mustache.params.length || mustache.hash) {
    // no changes required
  } else {
    var id = new Handlebars.AST.IdNode(['_triageMustache']);

    // Update the mustache node to include a hash value indicating whether the original node
    // was escaped. This will allow us to properly escape values when the underlying value
    // changes and we need to re-render the value.
    if(!mustache.escaped) {
      mustache.hash = mustache.hash || new Handlebars.AST.HashNode([]);
      mustache.hash.pairs.push(["unescaped", new Handlebars.AST.StringNode("true")]);
    }
    mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, !mustache.escaped);
  }

  return Handlebars.Compiler.prototype.mustache.call(this, mustache);
};

/**
  Used for precompilation of Ember Handlebars templates. This will not be used
  during normal app execution.

  @method precompile
  @for Ember.Handlebars
  @static
  @param {String} string The template to precompile
*/
Ember.Handlebars.precompile = function(string) {
  var ast = Handlebars.parse(string);

  var options = {
    knownHelpers: {
      action: true,
      unbound: true,
      bindAttr: true,
      template: true,
      view: true,
      _triageMustache: true
    },
    data: true,
    stringParams: true
  };

  var environment = new Ember.Handlebars.Compiler().compile(ast, options);
  return new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
};

// We don't support this for Handlebars runtime-only
if (Handlebars.compile) {
  /**
    The entry point for Ember Handlebars. This replaces the default
    `Handlebars.compile` and turns on template-local data and String
    parameters.

    @method compile
    @for Ember.Handlebars
    @static
    @param {String} string The template to compile
    @return {Function}
  */
  Ember.Handlebars.compile = function(string) {
    var ast = Handlebars.parse(string);
    var options = { data: true, stringParams: true };
    var environment = new Ember.Handlebars.Compiler().compile(ast, options);
    var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);

    return Ember.Handlebars.template(templateSpec);
  };
}

<|endoftext|>


<commit_before>"use strict";

var _ = require("underscore");
var util = require("substance-util");
var Document = require("./document");
var Operator = require("substance-operator");

var _getConfig;

// A class that provides helpers to manage a document's annotations.
// --------
//
// Note: the provided document is used to retrieve the annotation behavior and to initialize an annotation index.
//
var Annotator = function(doc) {
  this.config = _getConfig(doc);
  doc.addIndex("annotations", {types: ["annotation"], property: "path"});
  this.document = doc;
};

Annotator.Prototype = function() {

  // Updates all annotations according to a given operation.
  // --------
  //
  // The provided operation is an ObjectOperation which has been applied already or should be applied afterwards.
  //
  // Depending on the operation's `path` and the impact on an annotations range
  // there are the following cases:
  // 1. op='update', path==a.path: update the range following the rules given in the configuration
  // 2. op='delete', path[0]==a.path[0]: the annotation gets deleted
  // 3. op='set', path==a.path: the annotation gets deleted as the referenced property has been reset
  //
  this.update = function(op, options) {
    options = options || {};
    var path = options.path || op.path;
    var index = this.document.getIndex("annotations");
    var annotations = index.get(path);
    _.each(annotations, function(a) {
      _update(this, a, op, options);
    }, this);
  };

  // Copy annotations in the given selection.
  // --------
  // This is the pendant to the writer's copy method.
  // Partially selected annotations may not get copied depending on the
  // annotation type, for others, new annotation fragments would be created.

  this.copy = function(/*selection*/) {
    throw new Error("FIXME: this must be updated considering the other API changes.");

    // var ranges = _getRanges(this, selection);

    // // get all affected annotations
    // var annotations = this.getAnnotations(session, selection);
    // var result = [];

    // _.each(annotations, function(annotation) {

    //   // TODO: determine if an annotation would be split by the given selection.
    //   var range = ranges[annotation.path[0]];
    //   var isPartial = (range[0] > annotation.range[0] || range[1] < annotation.range[1]);

    //   var newAnnotation;
    //   if (isPartial) {
    //     // for the others create a new fragment (depending on type) and truncate the original
    //     if (this.isSplittable(annotation.type)) {
    //       newAnnotation = util.clone(annotation);
    //       // make the range relative to the selection
    //       newAnnotation.id = util.uuid();
    //       newAnnotation.range = [Math.max(0, annotation.range[0] - range[0]), annotation.range[1] - range[0]];
    //       result.push(newAnnotation);
    //     }
    //   } else {
    //     // add totally included ones
    //     // TODO: need more control over uuid generation
    //     newAnnotation = util.clone(annotation);
    //     newAnnotation.id = util.uuid();
    //     newAnnotation.range = [newAnnotation.range[0] - range[0], newAnnotation.range[1] - range[0]];
    //     result.push(newAnnotation);
    //   }

    // }, this);

    // return result;
  };

  this.paste = function(/*annotations, newNodeId, offset*/) {
    throw new Error("FIXME: this must be updated considering the other API changes.");
    // for (var i = 0; i < annotations.length; i++) {
    //   var annotation = annotations[i];
    //   if (newNodeId !== undefined) {
    //     annotation.path = _.clone(annotation.path);
    //     annotation.path[0] = newNodeId;
    //   }
    //   if (offset !== undefined) {
    //     annotation.range[0] += offset;
    //     annotation.range[1] += offset;
    //   }
    //   this.create(annotation);
    // }
  };

  // A helper to implement an editor which can breaks or joins nodes.
  // --------
  // TODO: this seems to be very tailored to text nodes. Refactor this when needed.
  //
  this.transferAnnotations = function(node, charPos, newNode, offset) {
    offset = offset || 0;

    var annotations = _nodeAnnotationsByRange(this, node, {start: charPos});
    _.each(annotations, function(annotation) {
    //   var range = ranges[annotation.path[0]];
      var isInside = (charPos > annotation.range[0] || charPos[1] < annotation.range[1]);
      var newRange;

      // 1. if the cursor is inside an annotation it gets either split or truncated
      if (isInside) {
        // create a new annotation fragment if the annotation is splittable
        if (this.isSplittable(annotation.type)) {
          var splitAnnotation = util.clone(annotation);
          splitAnnotation.range = [offset, offset + annotation.range[1] - charPos];
          splitAnnotation.id = util.uuid();
          splitAnnotation.path[0] = newNode.id;
          this.document.create(splitAnnotation);
        }
        // in either cases truncate the first part
        newRange =_.clone(annotation.range);
        newRange[1] = charPos;

        // if the fragment has a zero range now, delete it
        if (newRange[1] === newRange[0]) {
          this.document.delete(annotation.id);
        }
        // ... otherwise update the range
        else {
          this.document.set([annotation.id, "range"], newRange);
        }
      }

      // 2. if the cursor is before an annotation then simply transfer the annotation to the new node
      else {
        // Note: we are preserving the annotation so that anything which is connected to the annotation
        // remains valid.
        var newPath = _.clone(annotation.path);
        newPath[0] = newNode.id;
        this.document.set([annotation.id, "path"], newPath);
        newRange = [offset + annotation.range[0] - charPos, offset + annotation.range[1] - charPos];
        this.document.set([annotation.id, "range"], newRange);
      }
    }, this);
  };

  this.getAnnotationsForNode = function(nodeId) {
    return this.index.get(nodeId);
  };

  // Provides all annotations that correspond to a given selection.
  // --------
  // TODO: we could do a minor optimization, as it happens that the same query is performed multiple times.
  //
  this.getAnnotations = function(sel) {
    if (!(sel instanceof Document.Selection)) {
      throw new Error("API has changed: now getAnnotations() takes only a selection.");
    }

    var annotations = [];
    var ranges = sel.getRanges();
    for (var i = 0; i < ranges.length; i++) {
      var range = ranges[i];
      var annos = _annotationsByRange(this, this.document, range);
      annotations = annotations.concat(annos);
    }
    // console.log("Annotator.getAnnotations():", sel, annotations);
    return annotations;
  };

  // Returns true if two annotation types are mutually exclusive
  // ---------
  // Currently there is a built-in mechanism which considers two annotations
  // exclusive if they belong to the same group.
  //
  this.isExclusive = function(type1, type2) {
    return this.config.groups[type1] === this.config.groups[type2];
  };

  // Tell if an annotation can be split or should be truncated only.
  // --------
  //
  // E.g. when cutting a selection or inserting a new node existing annotations
  // may be affected. In some cases (e.g., `emphasis` or `strong`) it is wanted
  // that a new annotation of the same type is created for the cut fragment.
  //
  this.isSplittable = function(type) {
    return this.config.split.indexOf(type) >= 0;
  };

  // Updates a single annotation according to a given operation.
  // --------
  //
  var _update = function(self, annotation, op, options) {
    var path = options.path || op.path;

    // only apply the transformation on annotations with the same property
    // Note: currently we only have annotations on the `content` property of nodes
    if (!_.isEqual(annotation.path, path)) return;

    if (op.type === "update") {
      // Note: these are implicit transformations, i.e., not triggered via annotation controls
      var expandLeft = false;
      var expandRight = false;

      var expandSpec = self.config.expansion[annotation.type];
      if (expandSpec) {
        if (expandSpec.left) expandLeft =  expandSpec.left(annotation);
        if (expandSpec.right) expandRight = expandSpec.right(annotation);
      }

      var newRange = util.clone(annotation.range);
      var changed = Operator.TextOperation.Range.transform(newRange, op.diff, expandLeft, expandRight);
      if (changed) {
        if (newRange[0] === newRange[1]) {
          self.document.delete(annotation.id);
        } else {
          self.document.set([annotation.id, "range"], newRange);
        }
      }
    }
    // if somebody has reset the property we must delete the annotation
    else if (op.type === "delete" || op.type === "set") {
      self.document.delete(annotation.id);
    }
  };

  // Checks if an annotation overlaps with a given range
  // --------
  //
  var __isOverlap = function(self, anno, range) {
    var sStart = range.start;
    var sEnd = range.end;
    var aStart = anno.range[0];
    var aEnd = anno.range[1];

    var expandLeft = false;
    var expandRight = false;
    var expandSpec = self.config.expansion[anno.type];
    if (expandSpec) {
      if (expandSpec.left) expandLeft =  expandSpec.left(anno);
      if (expandSpec.right) expandRight = expandSpec.right(anno);
    }

    var overlap;
    if (expandRight) {
      overlap = (aEnd >= sStart);
    } else {
      overlap = (aEnd > sStart);
    }

    // Note: it is allowed to leave range.end undefined
    if (_.isNumber(sEnd)) {
      if (expandLeft) {
        overlap &= (aStart <= sEnd);
      } else {
        overlap &= (aStart < sEnd);
      }
    }

    return overlap;
  };

  var _annotationsByRange = function(self, doc, range) {
    var result = [];
    var component = range.component;
    var annotations;
    var index = doc.getIndex("annotations");

    // Note: If a component displays a referenced property (e.g., Cover.title)
    // IMO it makes more sense to attach annotations to the referencing path instead of the original path.
    // E.g., ["cover", "title"] instead of ["document", "title"]
    //annotations = index.get(component.propertyPath);
    annotations = index.get(component.path);

    _.each(annotations, function(a) {
      if (__isOverlap(self, a, range)) {
        result.push(a);
      }
    });
    return result;
  };

  var _nodeAnnotationsByRange = function(self, node, range) {
    var result = [];
    var index = self.document.getIndex("annotations");
    var annotations = index.get(node.id);
    _.each(annotations, function(a) {
      if (__isOverlap(self, a, range)) {
        result.push(a);
      }
    });
    return result;
  };
};

Annotator.Prototype.prototype = util.Events;
Annotator.prototype = new Annotator.Prototype();

Annotator.isOnNodeStart = function(a) {
  return a.range[0] === 0;
};

Annotator.isTrue = function() {
  return true;
};

// A helper to decide whether a graph operation affects annotations of a given node
// --------
// E.g., this is used by node views to detect annotation changes and to update the view accordingly.
//

var _isInstanceOf = function(doc, node, type) {
  var schema = doc.getSchema();
  return schema.isInstanceOf(node.type, type);
};

Annotator.changesAnnotations = function(doc, op, path) {
  var anno;
  if (op.type === "delete") {
    anno = op.val;
  } else {
    anno = doc.get(op.path[0]);
  }
  var result = false;

  if (_isInstanceOf(doc, anno, "annotation")) {
    // any annotation operation with appropriate path
    if (_.isEqual(path, anno.path)) {
      result = true;
    }
    // ... or those who are changing the path to that
    else if (op.type === "set" && op.path[1] === "path" && (_.isEqual(path, op.original)|| _.isEqual(path, op.val))) {
      result = true;
    }
  }

  return result;
};

// A static helper to create a document index for annotations
Annotator.createIndex = function(doc) {
  return doc.addIndex("annotations", {types: ["annotation"], property: "path"});
};

_getConfig = function(doc) {
  // Note: this is rather experimental
  // It is important to inverse the control over the annotation behavior,
  // i.e., which annotations exist and how they should be handled
  var annotationBehavior = doc.getAnnotationBehavior();
  if (!annotationBehavior) {
    throw new Error("No Annotation behavior specified.");
  }
  return annotationBehavior;
};

module.exports = Annotator;
<commit_msg>Retrieve annotations via component.alias.<commit_after>"use strict";

var _ = require("underscore");
var util = require("substance-util");
var Document = require("./document");
var Operator = require("substance-operator");

var _getConfig;

// A class that provides helpers to manage a document's annotations.
// --------
//
// Note: the provided document is used to retrieve the annotation behavior and to initialize an annotation index.
//
var Annotator = function(doc) {
  this.config = _getConfig(doc);
  doc.addIndex("annotations", {types: ["annotation"], property: "path"});
  this.document = doc;
};

Annotator.Prototype = function() {

  // Updates all annotations according to a given operation.
  // --------
  //
  // The provided operation is an ObjectOperation which has been applied already or should be applied afterwards.
  //
  // Depending on the operation's `path` and the impact on an annotations range
  // there are the following cases:
  // 1. op='update', path==a.path: update the range following the rules given in the configuration
  // 2. op='delete', path[0]==a.path[0]: the annotation gets deleted
  // 3. op='set', path==a.path: the annotation gets deleted as the referenced property has been reset
  //
  this.update = function(op, options) {
    options = options || {};
    var path = options.path || op.path;
    var index = this.document.getIndex("annotations");
    var annotations = index.get(path);
    _.each(annotations, function(a) {
      _update(this, a, op, options);
    }, this);
  };

  // Copy annotations in the given selection.
  // --------
  // This is the pendant to the writer's copy method.
  // Partially selected annotations may not get copied depending on the
  // annotation type, for others, new annotation fragments would be created.

  this.copy = function(/*selection*/) {
    throw new Error("FIXME: this must be updated considering the other API changes.");

    // var ranges = _getRanges(this, selection);

    // // get all affected annotations
    // var annotations = this.getAnnotations(session, selection);
    // var result = [];

    // _.each(annotations, function(annotation) {

    //   // TODO: determine if an annotation would be split by the given selection.
    //   var range = ranges[annotation.path[0]];
    //   var isPartial = (range[0] > annotation.range[0] || range[1] < annotation.range[1]);

    //   var newAnnotation;
    //   if (isPartial) {
    //     // for the others create a new fragment (depending on type) and truncate the original
    //     if (this.isSplittable(annotation.type)) {
    //       newAnnotation = util.clone(annotation);
    //       // make the range relative to the selection
    //       newAnnotation.id = util.uuid();
    //       newAnnotation.range = [Math.max(0, annotation.range[0] - range[0]), annotation.range[1] - range[0]];
    //       result.push(newAnnotation);
    //     }
    //   } else {
    //     // add totally included ones
    //     // TODO: need more control over uuid generation
    //     newAnnotation = util.clone(annotation);
    //     newAnnotation.id = util.uuid();
    //     newAnnotation.range = [newAnnotation.range[0] - range[0], newAnnotation.range[1] - range[0]];
    //     result.push(newAnnotation);
    //   }

    // }, this);

    // return result;
  };

  this.paste = function(/*annotations, newNodeId, offset*/) {
    throw new Error("FIXME: this must be updated considering the other API changes.");
    // for (var i = 0; i < annotations.length; i++) {
    //   var annotation = annotations[i];
    //   if (newNodeId !== undefined) {
    //     annotation.path = _.clone(annotation.path);
    //     annotation.path[0] = newNodeId;
    //   }
    //   if (offset !== undefined) {
    //     annotation.range[0] += offset;
    //     annotation.range[1] += offset;
    //   }
    //   this.create(annotation);
    // }
  };

  // A helper to implement an editor which can breaks or joins nodes.
  // --------
  // TODO: this seems to be very tailored to text nodes. Refactor this when needed.
  //
  this.transferAnnotations = function(node, charPos, newNode, offset) {
    offset = offset || 0;

    var annotations = _nodeAnnotationsByRange(this, node, {start: charPos});
    _.each(annotations, function(annotation) {
    //   var range = ranges[annotation.path[0]];
      var isInside = (charPos > annotation.range[0] || charPos[1] < annotation.range[1]);
      var newRange;

      // 1. if the cursor is inside an annotation it gets either split or truncated
      if (isInside) {
        // create a new annotation fragment if the annotation is splittable
        if (this.isSplittable(annotation.type)) {
          var splitAnnotation = util.clone(annotation);
          splitAnnotation.range = [offset, offset + annotation.range[1] - charPos];
          splitAnnotation.id = util.uuid();
          splitAnnotation.path[0] = newNode.id;
          this.document.create(splitAnnotation);
        }
        // in either cases truncate the first part
        newRange =_.clone(annotation.range);
        newRange[1] = charPos;

        // if the fragment has a zero range now, delete it
        if (newRange[1] === newRange[0]) {
          this.document.delete(annotation.id);
        }
        // ... otherwise update the range
        else {
          this.document.set([annotation.id, "range"], newRange);
        }
      }

      // 2. if the cursor is before an annotation then simply transfer the annotation to the new node
      else {
        // Note: we are preserving the annotation so that anything which is connected to the annotation
        // remains valid.
        var newPath = _.clone(annotation.path);
        newPath[0] = newNode.id;
        this.document.set([annotation.id, "path"], newPath);
        newRange = [offset + annotation.range[0] - charPos, offset + annotation.range[1] - charPos];
        this.document.set([annotation.id, "range"], newRange);
      }
    }, this);
  };

  this.getAnnotationsForNode = function(nodeId) {
    return this.index.get(nodeId);
  };

  // Provides all annotations that correspond to a given selection.
  // --------
  // TODO: we could do a minor optimization, as it happens that the same query is performed multiple times.
  //
  this.getAnnotations = function(sel) {
    if (!(sel instanceof Document.Selection)) {
      throw new Error("API has changed: now getAnnotations() takes only a selection.");
    }

    var annotations = [];
    var ranges = sel.getRanges();
    for (var i = 0; i < ranges.length; i++) {
      var range = ranges[i];
      var annos = _annotationsByRange(this, this.document, range);
      annotations = annotations.concat(annos);
    }
    // console.log("Annotator.getAnnotations():", sel, annotations);
    return annotations;
  };

  // Returns true if two annotation types are mutually exclusive
  // ---------
  // Currently there is a built-in mechanism which considers two annotations
  // exclusive if they belong to the same group.
  //
  this.isExclusive = function(type1, type2) {
    return this.config.groups[type1] === this.config.groups[type2];
  };

  // Tell if an annotation can be split or should be truncated only.
  // --------
  //
  // E.g. when cutting a selection or inserting a new node existing annotations
  // may be affected. In some cases (e.g., `emphasis` or `strong`) it is wanted
  // that a new annotation of the same type is created for the cut fragment.
  //
  this.isSplittable = function(type) {
    return this.config.split.indexOf(type) >= 0;
  };

  // Updates a single annotation according to a given operation.
  // --------
  //
  var _update = function(self, annotation, op, options) {
    var path = options.path || op.path;

    // only apply the transformation on annotations with the same property
    // Note: currently we only have annotations on the `content` property of nodes
    if (!_.isEqual(annotation.path, path)) return;

    if (op.type === "update") {
      // Note: these are implicit transformations, i.e., not triggered via annotation controls
      var expandLeft = false;
      var expandRight = false;

      var expandSpec = self.config.expansion[annotation.type];
      if (expandSpec) {
        if (expandSpec.left) expandLeft =  expandSpec.left(annotation);
        if (expandSpec.right) expandRight = expandSpec.right(annotation);
      }

      var newRange = util.clone(annotation.range);
      var changed = Operator.TextOperation.Range.transform(newRange, op.diff, expandLeft, expandRight);
      if (changed) {
        if (newRange[0] === newRange[1]) {
          self.document.delete(annotation.id);
        } else {
          self.document.set([annotation.id, "range"], newRange);
        }
      }
    }
    // if somebody has reset the property we must delete the annotation
    else if (op.type === "delete" || op.type === "set") {
      self.document.delete(annotation.id);
    }
  };

  // Checks if an annotation overlaps with a given range
  // --------
  //
  var __isOverlap = function(self, anno, range) {
    var sStart = range.start;
    var sEnd = range.end;
    var aStart = anno.range[0];
    var aEnd = anno.range[1];

    var expandLeft = false;
    var expandRight = false;
    var expandSpec = self.config.expansion[anno.type];
    if (expandSpec) {
      if (expandSpec.left) expandLeft =  expandSpec.left(anno);
      if (expandSpec.right) expandRight = expandSpec.right(anno);
    }

    var overlap;
    if (expandRight) {
      overlap = (aEnd >= sStart);
    } else {
      overlap = (aEnd > sStart);
    }

    // Note: it is allowed to leave range.end undefined
    if (_.isNumber(sEnd)) {
      if (expandLeft) {
        overlap &= (aStart <= sEnd);
      } else {
        overlap &= (aStart < sEnd);
      }
    }

    return overlap;
  };

  var _annotationsByRange = function(self, doc, range) {
    var result = [];
    var component = range.component;
    var annotations;
    var index = doc.getIndex("annotations");

    // Note: If a component displays a referenced property (e.g., Cover.title)
    // IMO it makes more sense to attach annotations to the referencing path instead of the original path.
    // E.g., ["cover", "title"] instead of ["document", "title"]
    annotations = index.get(component.path);

    if (component.alias) {
      // HACK: component.alias and component.path are not used consistently yet
      // so we use both for now
      annotations = _.extend(annotations, index.get(component.alias));
    }

    _.each(annotations, function(a) {
      if (__isOverlap(self, a, range)) {
        result.push(a);
      }
    });
    return result;
  };

  var _nodeAnnotationsByRange = function(self, node, range) {
    var result = [];
    var index = self.document.getIndex("annotations");
    var annotations = index.get(node.id);
    _.each(annotations, function(a) {
      if (__isOverlap(self, a, range)) {
        result.push(a);
      }
    });
    return result;
  };
};

Annotator.Prototype.prototype = util.Events;
Annotator.prototype = new Annotator.Prototype();

Annotator.isOnNodeStart = function(a) {
  return a.range[0] === 0;
};

Annotator.isTrue = function() {
  return true;
};

// A helper to decide whether a graph operation affects annotations of a given node
// --------
// E.g., this is used by node views to detect annotation changes and to update the view accordingly.
//

var _isInstanceOf = function(doc, node, type) {
  var schema = doc.getSchema();
  return schema.isInstanceOf(node.type, type);
};

Annotator.changesAnnotations = function(doc, op, path) {
  var anno;
  if (op.type === "delete") {
    anno = op.val;
  } else {
    anno = doc.get(op.path[0]);
  }
  var result = false;

  if (_isInstanceOf(doc, anno, "annotation")) {
    // any annotation operation with appropriate path
    if (_.isEqual(path, anno.path)) {
      result = true;
    }
    // ... or those who are changing the path to that
    else if (op.type === "set" && op.path[1] === "path" && (_.isEqual(path, op.original)|| _.isEqual(path, op.val))) {
      result = true;
    }
  }

  return result;
};

// A static helper to create a document index for annotations
Annotator.createIndex = function(doc) {
  return doc.addIndex("annotations", {types: ["annotation"], property: "path"});
};

_getConfig = function(doc) {
  // Note: this is rather experimental
  // It is important to inverse the control over the annotation behavior,
  // i.e., which annotations exist and how they should be handled
  var annotationBehavior = doc.getAnnotationBehavior();
  if (!annotationBehavior) {
    throw new Error("No Annotation behavior specified.");
  }
  return annotationBehavior;
};

module.exports = Annotator;
<|endoftext|>


<commit_before>import { AmbientLight, DirectionalLight } from 'three';

const three = window.THREE
  ? window.THREE // Prefer consumption from global THREE, if exists
  : { AmbientLight, DirectionalLight };

import ThreeDragControls from 'three-dragcontrols';
import ThreeForceGraph from 'three-forcegraph';
import ThreeRenderObjects from 'three-render-objects';

import accessorFn from 'accessor-fn';
import Kapsule from 'kapsule';

import linkKapsule from './kapsule-link.js';

//

const CAMERA_DISTANCE2NODES_FACTOR = 150;

//

// Expose config from forceGraph
const bindFG = linkKapsule('forceGraph', ThreeForceGraph);
const linkedFGProps = Object.assign(...[
  'jsonUrl',
  'graphData',
  'numDimensions',
  'dagMode',
  'dagLevelDistance',
  'nodeRelSize',
  'nodeId',
  'nodeVal',
  'nodeResolution',
  'nodeColor',
  'nodeAutoColorBy',
  'nodeOpacity',
  'nodeThreeObject',
  'linkSource',
  'linkTarget',
  'linkVisibility',
  'linkColor',
  'linkAutoColorBy',
  'linkOpacity',
  'linkWidth',
  'linkResolution',
  'linkCurvature',
  'linkCurveRotation',
  'linkMaterial',
  'linkThreeObject',
  'linkPositionUpdate',
  'linkDirectionalArrowLength',
  'linkDirectionalArrowColor',
  'linkDirectionalArrowRelPos',
  'linkDirectionalArrowResolution',
  'linkDirectionalParticles',
  'linkDirectionalParticleSpeed',
  'linkDirectionalParticleWidth',
  'linkDirectionalParticleColor',
  'linkDirectionalParticleResolution',
  'forceEngine',
  'd3AlphaDecay',
  'd3VelocityDecay',
  'warmupTicks',
  'cooldownTicks',
  'cooldownTime',
  'onEngineTick',
  'onEngineStop'
].map(p => ({ [p]: bindFG.linkProp(p)})));
const linkedFGMethods = Object.assign(...[
  'd3Force'
].map(p => ({ [p]: bindFG.linkMethod(p)})));

// Expose config from renderObjs
const bindRenderObjs = linkKapsule('renderObjs', ThreeRenderObjects);
const linkedRenderObjsProps = Object.assign(...[
  'width',
  'height',
  'backgroundColor',
  'showNavInfo',
  'enablePointerInteraction'
].map(p => ({ [p]: bindRenderObjs.linkProp(p)})));
const linkedRenderObjsMethods = Object.assign(...[
  'cameraPosition'
].map(p => ({ [p]: bindRenderObjs.linkMethod(p)})));

//

export default Kapsule({

  props: {
    nodeLabel: { default: 'name', triggerUpdate: false },
    linkLabel: { default: 'name', triggerUpdate: false },
    linkHoverPrecision: { default: 1, onChange: (p, state) => state.renderObjs.lineHoverPrecision(p), triggerUpdate: false },
    enableNavigationControls: {
      default: true,
      onChange(enable, state) {
        const controls = state.renderObjs.controls();
        if (controls) {
          controls.enabled = enable;
        }
      },
      triggerUpdate: false
    },
    enableNodeDrag: { default: true, triggerUpdate: false },
    onNodeDrag: { default: () => {}, triggerUpdate: false },
    onNodeDragEnd: { default: () => {}, triggerUpdate: false },
    onNodeClick: { default: () => {}, triggerUpdate: false },
    onNodeRightClick: { default: () => {}, triggerUpdate: false },
    onNodeHover: { default: () => {}, triggerUpdate: false },
    onLinkClick: { default: () => {}, triggerUpdate: false },
    onLinkRightClick: { default: () => {}, triggerUpdate: false },
    onLinkHover: { default: () => {}, triggerUpdate: false },
    ...linkedFGProps,
    ...linkedRenderObjsProps
  },

  aliases: { // Prop names supported for backwards compatibility
    nameField: 'nodeLabel',
    idField: 'nodeId',
    valField: 'nodeVal',
    colorField: 'nodeColor',
    autoColorBy: 'nodeAutoColorBy',
    linkSourceField: 'linkSource',
    linkTargetField: 'linkTarget',
    linkColorField: 'linkColor',
    lineOpacity: 'linkOpacity',
    stopAnimation: 'pauseAnimation'
  },

  methods: {
    pauseAnimation: function(state) {
      if (state.animationFrameRequestId !== null) {
        cancelAnimationFrame(state.animationFrameRequestId);
        state.animationFrameRequestId = null;
      }
      return this;
    },

    resumeAnimation: function(state) {
      if (state.animationFrameRequestId === null) {
        this._animationCycle();
      }
      return this;
    },
    _animationCycle(state) {
      if (state.enablePointerInteraction) {
        // reset canvas cursor (override dragControls cursor)
        this.renderer().domElement.style.cursor = null;
      }

      // Frame cycle
      state.forceGraph.tickFrame();
      state.renderObjs.tick();
      state.animationFrameRequestId = requestAnimationFrame(this._animationCycle);
    },
    scene: state => state.renderObjs.scene(), // Expose scene
    camera: state => state.renderObjs.camera(), // Expose camera
    renderer: state => state.renderObjs.renderer(), // Expose renderer
    controls: state => state.renderObjs.controls(), // Expose controls
    tbControls: state => state.renderObjs.tbControls(), // To be deprecated
    ...linkedFGMethods,
    ...linkedRenderObjsMethods
  },

  stateInit: ({ controlType, rendererConfig }) => ({
    forceGraph: new ThreeForceGraph(),
    renderObjs: ThreeRenderObjects({ controlType, rendererConfig })
  }),

  init: function(domNode, state) {
    // Wipe DOM
    domNode.innerHTML = '';

    // Add relative container
    domNode.appendChild(state.container = document.createElement('div'));
    state.container.style.position = 'relative';

    // Add renderObjs
    const roDomNode = document.createElement('div');
    state.container.appendChild(roDomNode);
    state.renderObjs(roDomNode);
    const camera = state.renderObjs.camera();
    const renderer = state.renderObjs.renderer();
    const controls = state.renderObjs.controls();
    controls.enabled = !!state.enableNavigationControls;
    state.lastSetCameraZ = camera.position.z;

    // Add info space
    let infoElem;
    state.container.appendChild(infoElem = document.createElement('div'));
    infoElem.className = 'graph-info-msg';
    infoElem.textContent = '';

    // config forcegraph
    state.forceGraph.onLoading(() => { infoElem.textContent = 'Loading...' });
    state.forceGraph.onFinishLoading(() => {
      infoElem.textContent = '';

      // sync graph data structures
      state.graphData = state.forceGraph.graphData();

      // re-aim camera, if still in default position (not user modified)
      if (camera.position.x === 0 && camera.position.y === 0 && camera.position.z === state.lastSetCameraZ) {
        camera.lookAt(state.forceGraph.position);
        state.lastSetCameraZ = camera.position.z = Math.cbrt(state.graphData.nodes.length) * CAMERA_DISTANCE2NODES_FACTOR;
      }

      // Setup node drag interaction
      if (state.enableNodeDrag && state.enablePointerInteraction && state.forceEngine === 'd3') { // Can't access node positions programatically in ngraph
        const dragControls = new ThreeDragControls(
          state.graphData.nodes.map(node => node.__threeObj),
          camera,
          renderer.domElement
        );

        dragControls.addEventListener('dragstart', function (event) {
          controls.enabled = false; // Disable controls while dragging

          const node = event.object.__data;
          node.__initialFixedPos = {fx: node.fx, fy: node.fy, fz: node.fz};

          // lock node
          ['x', 'y', 'z'].forEach(c => node[`f${c}`] = node[c]);

          // keep engine running at low intensity throughout drag
          state.forceGraph.d3AlphaTarget(0.3);

          // drag cursor
          renderer.domElement.classList.add('grabbable');
        });

        dragControls.addEventListener('drag', function (event) {
          state.ignoreOneClick = true; // Don't click the node if it's being dragged

          const node = event.object.__data;

          // Move fx/fy/fz (and x/y/z) of nodes based on object new position
          ['x', 'y', 'z'].forEach(c => node[`f${c}`] = node[c] = event.object.position[c]);

          // prevent freeze while dragging
          state.forceGraph.resetCountdown();

          state.onNodeDrag(node);
        });

        dragControls.addEventListener('dragend', function (event) {
          const node = event.object.__data;
          const initPos = node.__initialFixedPos;

          if (initPos) {
            ['x', 'y', 'z'].forEach(c => {
              const fc = `f${c}`;
              if (initPos[fc] === undefined) {
                node[fc] = undefined
              }
            });
            delete(node.__initialFixedPos);

            state.onNodeDragEnd(node);
          }

          state.forceGraph
            .d3AlphaTarget(0)   // release engine low intensity
            .resetCountdown();  // let the engine readjust after releasing fixed nodes

          if (state.enableNavigationControls) {
            controls.enabled = true; // Re-enable controls
          }

          // clear cursor
          renderer.domElement.classList.remove('grabbable');
        });
      }
    });

    // config renderObjs
    const getGraphObj = object => {
      let obj = object;
      // recurse up object chain until finding the graph object
      while (obj && !obj.hasOwnProperty('__graphObjType')) {
        obj = obj.parent;
      }
      return obj;
    };

    state.renderObjs
      .objects([ // Populate scene
        new three.AmbientLight(0xbbbbbb),
        new three.DirectionalLight(0xffffff, 0.6),
        state.forceGraph
      ])
      .hoverOrderComparator((a, b) => {
        // Prioritize graph objects
        const aObj = getGraphObj(a);
        if (!aObj) return 1;
        const bObj = getGraphObj(b);
        if (!bObj) return -1;

        // Prioritize nodes over links
        const isNode = o => o.__graphObjType === 'node';
        return isNode(bObj) - isNode(aObj);
      })
      .tooltipContent(obj => {
        const graphObj = getGraphObj(obj);
        return graphObj ? accessorFn(state[`${graphObj.__graphObjType}Label`])(graphObj.__data) || '' : '';
      })
      .onHover(obj => {
        // Update tooltip and trigger onHover events
        const hoverObj = getGraphObj(obj);

        if (hoverObj !== state.hoverObj) {
          const prevObjType = state.hoverObj ? state.hoverObj.__graphObjType : null;
          const prevObjData = state.hoverObj ? state.hoverObj.__data : null;
          const objType = hoverObj ? hoverObj.__graphObjType : null;
          const objData = hoverObj ? hoverObj.__data : null;
          if (prevObjType && prevObjType !== objType) {
            // Hover out
            state[`on${prevObjType === 'node' ? 'Node' : 'Link'}Hover`](null, prevObjData);
          }
          if (objType) {
            // Hover in
            state[`on${objType === 'node' ? 'Node' : 'Link'}Hover`](objData, prevObjType === objType ? prevObjData : null);
          }

          state.hoverObj = hoverObj;
        }
      })
      .onClick(obj => {
        // Handle click events on objects
        if (state.ignoreOneClick) {
          // f.e. because of dragend event
          state.ignoreOneClick = false;
          return;
        }

        const graphObj = getGraphObj(obj);
        if (graphObj) {
          state[`on${graphObj.__graphObjType === 'node' ? 'Node' : 'Link'}Click`](graphObj.__data);
        }
      })
      .onRightClick(obj => {
        // Handle right-click events
        const graphObj = getGraphObj(obj);
        if (graphObj) {
          state[`on${graphObj.__graphObjType === 'node' ? 'Node' : 'Link'}RightClick`](graphObj.__data);
        }
      });

    //

    // Kick-off renderer
    this._animationCycle();
  }
});<commit_msg>Add _destructor<commit_after>import { AmbientLight, DirectionalLight } from 'three';

const three = window.THREE
  ? window.THREE // Prefer consumption from global THREE, if exists
  : { AmbientLight, DirectionalLight };

import ThreeDragControls from 'three-dragcontrols';
import ThreeForceGraph from 'three-forcegraph';
import ThreeRenderObjects from 'three-render-objects';

import accessorFn from 'accessor-fn';
import Kapsule from 'kapsule';

import linkKapsule from './kapsule-link.js';

//

const CAMERA_DISTANCE2NODES_FACTOR = 150;

//

// Expose config from forceGraph
const bindFG = linkKapsule('forceGraph', ThreeForceGraph);
const linkedFGProps = Object.assign(...[
  'jsonUrl',
  'graphData',
  'numDimensions',
  'dagMode',
  'dagLevelDistance',
  'nodeRelSize',
  'nodeId',
  'nodeVal',
  'nodeResolution',
  'nodeColor',
  'nodeAutoColorBy',
  'nodeOpacity',
  'nodeThreeObject',
  'linkSource',
  'linkTarget',
  'linkVisibility',
  'linkColor',
  'linkAutoColorBy',
  'linkOpacity',
  'linkWidth',
  'linkResolution',
  'linkCurvature',
  'linkCurveRotation',
  'linkMaterial',
  'linkThreeObject',
  'linkPositionUpdate',
  'linkDirectionalArrowLength',
  'linkDirectionalArrowColor',
  'linkDirectionalArrowRelPos',
  'linkDirectionalArrowResolution',
  'linkDirectionalParticles',
  'linkDirectionalParticleSpeed',
  'linkDirectionalParticleWidth',
  'linkDirectionalParticleColor',
  'linkDirectionalParticleResolution',
  'forceEngine',
  'd3AlphaDecay',
  'd3VelocityDecay',
  'warmupTicks',
  'cooldownTicks',
  'cooldownTime',
  'onEngineTick',
  'onEngineStop'
].map(p => ({ [p]: bindFG.linkProp(p)})));
const linkedFGMethods = Object.assign(...[
  'd3Force'
].map(p => ({ [p]: bindFG.linkMethod(p)})));

// Expose config from renderObjs
const bindRenderObjs = linkKapsule('renderObjs', ThreeRenderObjects);
const linkedRenderObjsProps = Object.assign(...[
  'width',
  'height',
  'backgroundColor',
  'showNavInfo',
  'enablePointerInteraction'
].map(p => ({ [p]: bindRenderObjs.linkProp(p)})));
const linkedRenderObjsMethods = Object.assign(...[
  'cameraPosition'
].map(p => ({ [p]: bindRenderObjs.linkMethod(p)})));

//

export default Kapsule({

  props: {
    nodeLabel: { default: 'name', triggerUpdate: false },
    linkLabel: { default: 'name', triggerUpdate: false },
    linkHoverPrecision: { default: 1, onChange: (p, state) => state.renderObjs.lineHoverPrecision(p), triggerUpdate: false },
    enableNavigationControls: {
      default: true,
      onChange(enable, state) {
        const controls = state.renderObjs.controls();
        if (controls) {
          controls.enabled = enable;
        }
      },
      triggerUpdate: false
    },
    enableNodeDrag: { default: true, triggerUpdate: false },
    onNodeDrag: { default: () => {}, triggerUpdate: false },
    onNodeDragEnd: { default: () => {}, triggerUpdate: false },
    onNodeClick: { default: () => {}, triggerUpdate: false },
    onNodeRightClick: { default: () => {}, triggerUpdate: false },
    onNodeHover: { default: () => {}, triggerUpdate: false },
    onLinkClick: { default: () => {}, triggerUpdate: false },
    onLinkRightClick: { default: () => {}, triggerUpdate: false },
    onLinkHover: { default: () => {}, triggerUpdate: false },
    ...linkedFGProps,
    ...linkedRenderObjsProps
  },

  aliases: { // Prop names supported for backwards compatibility
    nameField: 'nodeLabel',
    idField: 'nodeId',
    valField: 'nodeVal',
    colorField: 'nodeColor',
    autoColorBy: 'nodeAutoColorBy',
    linkSourceField: 'linkSource',
    linkTargetField: 'linkTarget',
    linkColorField: 'linkColor',
    lineOpacity: 'linkOpacity',
    stopAnimation: 'pauseAnimation'
  },

  methods: {
    pauseAnimation: function(state) {
      if (state.animationFrameRequestId !== null) {
        cancelAnimationFrame(state.animationFrameRequestId);
        state.animationFrameRequestId = null;
      }
      return this;
    },

    resumeAnimation: function(state) {
      if (state.animationFrameRequestId === null) {
        this._animationCycle();
      }
      return this;
    },
    _animationCycle(state) {
      if (state.enablePointerInteraction) {
        // reset canvas cursor (override dragControls cursor)
        this.renderer().domElement.style.cursor = null;
      }

      // Frame cycle
      state.forceGraph.tickFrame();
      state.renderObjs.tick();
      state.animationFrameRequestId = requestAnimationFrame(this._animationCycle);
    },
    scene: state => state.renderObjs.scene(), // Expose scene
    camera: state => state.renderObjs.camera(), // Expose camera
    renderer: state => state.renderObjs.renderer(), // Expose renderer
    controls: state => state.renderObjs.controls(), // Expose controls
    tbControls: state => state.renderObjs.tbControls(), // To be deprecated
    _destructor: function() {
      this.pauseAnimation();
      this.graphData({ nodes: [], links: []});
    },
    ...linkedFGMethods,
    ...linkedRenderObjsMethods
  },

  stateInit: ({ controlType, rendererConfig }) => ({
    forceGraph: new ThreeForceGraph(),
    renderObjs: ThreeRenderObjects({ controlType, rendererConfig })
  }),

  init: function(domNode, state) {
    // Wipe DOM
    domNode.innerHTML = '';

    // Add relative container
    domNode.appendChild(state.container = document.createElement('div'));
    state.container.style.position = 'relative';

    // Add renderObjs
    const roDomNode = document.createElement('div');
    state.container.appendChild(roDomNode);
    state.renderObjs(roDomNode);
    const camera = state.renderObjs.camera();
    const renderer = state.renderObjs.renderer();
    const controls = state.renderObjs.controls();
    controls.enabled = !!state.enableNavigationControls;
    state.lastSetCameraZ = camera.position.z;

    // Add info space
    let infoElem;
    state.container.appendChild(infoElem = document.createElement('div'));
    infoElem.className = 'graph-info-msg';
    infoElem.textContent = '';

    // config forcegraph
    state.forceGraph.onLoading(() => { infoElem.textContent = 'Loading...' });
    state.forceGraph.onFinishLoading(() => {
      infoElem.textContent = '';

      // sync graph data structures
      state.graphData = state.forceGraph.graphData();

      // re-aim camera, if still in default position (not user modified)
      if (camera.position.x === 0 && camera.position.y === 0 && camera.position.z === state.lastSetCameraZ) {
        camera.lookAt(state.forceGraph.position);
        state.lastSetCameraZ = camera.position.z = Math.cbrt(state.graphData.nodes.length) * CAMERA_DISTANCE2NODES_FACTOR;
      }

      // Setup node drag interaction
      if (state.enableNodeDrag && state.enablePointerInteraction && state.forceEngine === 'd3') { // Can't access node positions programatically in ngraph
        const dragControls = new ThreeDragControls(
          state.graphData.nodes.map(node => node.__threeObj),
          camera,
          renderer.domElement
        );

        dragControls.addEventListener('dragstart', function (event) {
          controls.enabled = false; // Disable controls while dragging

          const node = event.object.__data;
          node.__initialFixedPos = {fx: node.fx, fy: node.fy, fz: node.fz};

          // lock node
          ['x', 'y', 'z'].forEach(c => node[`f${c}`] = node[c]);

          // keep engine running at low intensity throughout drag
          state.forceGraph.d3AlphaTarget(0.3);

          // drag cursor
          renderer.domElement.classList.add('grabbable');
        });

        dragControls.addEventListener('drag', function (event) {
          state.ignoreOneClick = true; // Don't click the node if it's being dragged

          const node = event.object.__data;

          // Move fx/fy/fz (and x/y/z) of nodes based on object new position
          ['x', 'y', 'z'].forEach(c => node[`f${c}`] = node[c] = event.object.position[c]);

          // prevent freeze while dragging
          state.forceGraph.resetCountdown();

          state.onNodeDrag(node);
        });

        dragControls.addEventListener('dragend', function (event) {
          const node = event.object.__data;
          const initPos = node.__initialFixedPos;

          if (initPos) {
            ['x', 'y', 'z'].forEach(c => {
              const fc = `f${c}`;
              if (initPos[fc] === undefined) {
                node[fc] = undefined
              }
            });
            delete(node.__initialFixedPos);

            state.onNodeDragEnd(node);
          }

          state.forceGraph
            .d3AlphaTarget(0)   // release engine low intensity
            .resetCountdown();  // let the engine readjust after releasing fixed nodes

          if (state.enableNavigationControls) {
            controls.enabled = true; // Re-enable controls
          }

          // clear cursor
          renderer.domElement.classList.remove('grabbable');
        });
      }
    });

    // config renderObjs
    const getGraphObj = object => {
      let obj = object;
      // recurse up object chain until finding the graph object
      while (obj && !obj.hasOwnProperty('__graphObjType')) {
        obj = obj.parent;
      }
      return obj;
    };

    state.renderObjs
      .objects([ // Populate scene
        new three.AmbientLight(0xbbbbbb),
        new three.DirectionalLight(0xffffff, 0.6),
        state.forceGraph
      ])
      .hoverOrderComparator((a, b) => {
        // Prioritize graph objects
        const aObj = getGraphObj(a);
        if (!aObj) return 1;
        const bObj = getGraphObj(b);
        if (!bObj) return -1;

        // Prioritize nodes over links
        const isNode = o => o.__graphObjType === 'node';
        return isNode(bObj) - isNode(aObj);
      })
      .tooltipContent(obj => {
        const graphObj = getGraphObj(obj);
        return graphObj ? accessorFn(state[`${graphObj.__graphObjType}Label`])(graphObj.__data) || '' : '';
      })
      .onHover(obj => {
        // Update tooltip and trigger onHover events
        const hoverObj = getGraphObj(obj);

        if (hoverObj !== state.hoverObj) {
          const prevObjType = state.hoverObj ? state.hoverObj.__graphObjType : null;
          const prevObjData = state.hoverObj ? state.hoverObj.__data : null;
          const objType = hoverObj ? hoverObj.__graphObjType : null;
          const objData = hoverObj ? hoverObj.__data : null;
          if (prevObjType && prevObjType !== objType) {
            // Hover out
            state[`on${prevObjType === 'node' ? 'Node' : 'Link'}Hover`](null, prevObjData);
          }
          if (objType) {
            // Hover in
            state[`on${objType === 'node' ? 'Node' : 'Link'}Hover`](objData, prevObjType === objType ? prevObjData : null);
          }

          state.hoverObj = hoverObj;
        }
      })
      .onClick(obj => {
        // Handle click events on objects
        if (state.ignoreOneClick) {
          // f.e. because of dragend event
          state.ignoreOneClick = false;
          return;
        }

        const graphObj = getGraphObj(obj);
        if (graphObj) {
          state[`on${graphObj.__graphObjType === 'node' ? 'Node' : 'Link'}Click`](graphObj.__data);
        }
      })
      .onRightClick(obj => {
        // Handle right-click events
        const graphObj = getGraphObj(obj);
        if (graphObj) {
          state[`on${graphObj.__graphObjType === 'node' ? 'Node' : 'Link'}RightClick`](graphObj.__data);
        }
      });

    //

    // Kick-off renderer
    this._animationCycle();
  }
});<|endoftext|>


<commit_before>//Counts the words in my body paragraphs
Article.numWordsAll = function() {
  console.log(Article.all);
  console.log('doesthiswork????????');
  return Article.all.map(function(article) {
    return article.body.split(' ').length;
    console.log('doesthisworknow');
  })
.reduce(function(a, b) {
  console.log(a+b + 'fduhdfghufd');
  return a+b;
});
};

//Appends the number of words in my body paragraphs
function timerOn () {
  $( document ).ready(function() {
    $('#reduce').text('fun fact, there are ' + Article.numWordsAll() + ' words in my articles!');
  });
};
window.setTimeout(timerOn, 10);
<commit_msg>deleting reduce from my project<commit_after><|endoftext|>


<commit_before>'use strict'

let whitelistArray = new Array()
let caffeinatedAudio = false
let thumbnails = false

chrome.storage.sync.get(function(items) {
	if (items.whitelist) whitelistArray = items.whitelist.split('\n')
	if (items.audible !== undefined) caffeinatedAudio = items.audible
	if (items.thumbnails !== undefined) thumbnails = items.thumbnails
})

chrome.storage.onChanged.addListener(function(changes, area) {
	if (area === 'sync') {
		chrome.storage.sync.get(function(items) {
			console.table(items)
			if (items.whitelist) whitelistArray = items.whitelist.split('\n')
			if (items.audible !== undefined) caffeinatedAudio = items.audible
			if (items.thumbnails !== undefined) thumbnails = items.thumbnails
		})
	}
})

chrome.runtime.onInstalled.addListener(function() {
	chrome.contextMenus.create({
		id : 'SleepTab',
		title : chrome.i18n.getMessage('contextMenuTitle'),
		contexts : ['page']
	})
})

function inWhitelist(url) {
	whitelistArray.forEach(function(item) {
		if (url.startsWith(item)) {
			return true
		}
	})
	return false
}

function sleepTab(html, tab, img) {
	const pageInfo = {
		url: tab.url,
		title: tab.title,
		hibernationInfo: chrome.i18n.getMessage('hibernationPageInfo'),
		buttonText: chrome.i18n.getMessage('hibernationPageButton'),
		favIconUrl: tab.favIconUrl
	}

	const pageHtml = html.replace(/\{\/\*pageInfoObject\*\/\}/, JSON.stringify(pageInfo))

	if (img && thumbnails) {
		const canvas = document.createElement('canvas')
		const ctx = canvas.getContext('2d')

		const i = new Image()
		i.src = img
		i.onload = function() {
			canvas.width = 300
			canvas.height = canvas.width * (i.height / i.width)
			ctx.drawImage(i, 0, 0, canvas.width, canvas.height)
			img = canvas.toDataURL('image/webp')

			const dataURL = 'data:text/html;charset=UTF-8,' + encodeURIComponent(pageHtml.replace(/__WEBP__/, img))
			chrome.tabs.update(tab.id, {url: dataURL, autoDiscardable: true})
		}
		return
	}
	const dataURL = 'data:text/html;charset=UTF-8,' + encodeURIComponent(pageHtml.replace(/__WEBP__/, 'data:'))
	chrome.tabs.update(tab.id, {url: dataURL, autoDiscardable: true})
}

function sleepSingle(tab) {
	const xmlHttp = new XMLHttpRequest()
	xmlHttp.open('GET', chrome.runtime.getURL('hibernationPage/index.html'), true)
	xmlHttp.onload = function() {
		chrome.tabs.captureVisibleTab({format: 'png'}, function(img) {
			sleepTab(xmlHttp.responseText, tab, img)
		})
	}
	xmlHttp.send(null)
}

chrome.browserAction.onClicked.addListener(function() {
	const xmlHttp = new XMLHttpRequest()
	xmlHttp.open('GET', chrome.runtime.getURL('hibernationPage/index.html'), true)
	xmlHttp.onload = function() {
		chrome.tabs.query({
			active: false,
			pinned: false,
			highlighted: false,
			status: 'complete',
			url: ['http://*/*', 'https://*/*', 'ftp://*/*', 'file://*/*']
		}, function(tabs) {
			tabs.forEach(function(tab) {
				if (inWhitelist(tab.url)) return
				if (tab.audible && caffeinatedAudio) return
				sleepTab(xmlHttp.responseText, tab)
			})
		})
	}
	xmlHttp.send(null)
})

chrome.contextMenus.onClicked.addListener(function(info, tab) {
	sleepSingle(tab)
})

chrome.commands.onCommand.addListener(function() {
	chrome.tabs.query({active: true, lastFocusedWindow: true}, function(tabs) {
		sleepSingle(tabs[0])
	})
})
<commit_msg>remove console.table() call…<commit_after>'use strict'

let whitelistArray = new Array()
let caffeinatedAudio = false
let thumbnails = false

chrome.storage.sync.get(function(items) {
	if (items.whitelist) whitelistArray = items.whitelist.split('\n')
	if (items.audible !== undefined) caffeinatedAudio = items.audible
	if (items.thumbnails !== undefined) thumbnails = items.thumbnails
})

chrome.storage.onChanged.addListener(function(changes, area) {
	if (area === 'sync') {
		chrome.storage.sync.get(function(items) {
			if (items.whitelist) whitelistArray = items.whitelist.split('\n')
			if (items.audible !== undefined) caffeinatedAudio = items.audible
			if (items.thumbnails !== undefined) thumbnails = items.thumbnails
		})
	}
})

chrome.runtime.onInstalled.addListener(function() {
	chrome.contextMenus.create({
		id : 'SleepTab',
		title : chrome.i18n.getMessage('contextMenuTitle'),
		contexts : ['page']
	})
})

function inWhitelist(url) {
	whitelistArray.forEach(function(item) {
		if (url.startsWith(item)) {
			return true
		}
	})
	return false
}

function sleepTab(html, tab, img) {
	const pageInfo = {
		url: tab.url,
		title: tab.title,
		hibernationInfo: chrome.i18n.getMessage('hibernationPageInfo'),
		buttonText: chrome.i18n.getMessage('hibernationPageButton'),
		favIconUrl: tab.favIconUrl
	}

	const pageHtml = html.replace(/\{\/\*pageInfoObject\*\/\}/, JSON.stringify(pageInfo))

	if (img && thumbnails) {
		const canvas = document.createElement('canvas')
		const ctx = canvas.getContext('2d')

		const i = new Image()
		i.src = img
		i.onload = function() {
			canvas.width = 300
			canvas.height = canvas.width * (i.height / i.width)
			ctx.drawImage(i, 0, 0, canvas.width, canvas.height)
			img = canvas.toDataURL('image/webp')

			const dataURL = 'data:text/html;charset=UTF-8,' + encodeURIComponent(pageHtml.replace(/__WEBP__/, img))
			chrome.tabs.update(tab.id, {url: dataURL, autoDiscardable: true})
		}
		return
	}
	const dataURL = 'data:text/html;charset=UTF-8,' + encodeURIComponent(pageHtml.replace(/__WEBP__/, 'data:'))
	chrome.tabs.update(tab.id, {url: dataURL, autoDiscardable: true})
}

function sleepSingle(tab) {
	const xmlHttp = new XMLHttpRequest()
	xmlHttp.open('GET', chrome.runtime.getURL('hibernationPage/index.html'), true)
	xmlHttp.onload = function() {
		chrome.tabs.captureVisibleTab({format: 'png'}, function(img) {
			sleepTab(xmlHttp.responseText, tab, img)
		})
	}
	xmlHttp.send(null)
}

chrome.browserAction.onClicked.addListener(function() {
	const xmlHttp = new XMLHttpRequest()
	xmlHttp.open('GET', chrome.runtime.getURL('hibernationPage/index.html'), true)
	xmlHttp.onload = function() {
		chrome.tabs.query({
			active: false,
			pinned: false,
			highlighted: false,
			status: 'complete',
			url: ['http://*/*', 'https://*/*', 'ftp://*/*', 'file://*/*']
		}, function(tabs) {
			tabs.forEach(function(tab) {
				if (inWhitelist(tab.url)) return
				if (tab.audible && caffeinatedAudio) return
				sleepTab(xmlHttp.responseText, tab)
			})
		})
	}
	xmlHttp.send(null)
})

chrome.contextMenus.onClicked.addListener(function(info, tab) {
	sleepSingle(tab)
})

chrome.commands.onCommand.addListener(function() {
	chrome.tabs.query({active: true, lastFocusedWindow: true}, function(tabs) {
		sleepSingle(tabs[0])
	})
})
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import CommonQueries from './common/CommonGraphQLQueries';
import DataIndex from './common/DataIndex';
import Utilities from './common/Utilities';
import Table from './Table';

class Report extends Component {

	constructor(props) {
		super(props);
		this._initReport = this._initReport.bind(this);
		this._handleData = this._handleData.bind(this);
		this.SERVICE_STATUS_OPTIONS = {},
		this.SERVICE_CLASSIFICATION_OPTIONS = {},
		this.SERVICE_ORIGIN_OPTIONS = {}
		this.state = {
			setup: null,
			data: []
		};
	}

	componentDidMount() {
		lx.init().then(this._initReport);
	}

	_initReport(setup) {
		lx.ready(this._createConfig());
		lx.showSpinner('Loading data ...');
		this.setState({
			setup: setup
		});
		// get options from data model
		const factsheetModel = setup.settings.dataModel.factSheets.CSM;
		this.SERVICE_STATUS_OPTIONS = Utilities.createOptionsObjFrom(
			factsheetModel, 'fields.serviceStatus.values');
		this.SERVICE_CLASSIFICATION_OPTIONS = Utilities.createOptionsObjFrom(
			factsheetModel, 'fields.serviceClassification.values');
		this.SERVICE_ORIGIN_OPTIONS = Utilities.createOptionsObjFrom(
			factsheetModel, 'fields.serviceOrigin.values');
		// get all tags, then the data
		lx.executeGraphQL(CommonQueries.tagGroups).then((tagGroups) => {
			const index = new DataIndex();
			index.put(tagGroups);
			const csmId = index.getFirstTagID('CSM Type', 'CSM');
			const cimId = index.getFirstTagID('Category', 'CIM');
			const appMapId = index.getFirstTagID('BC Type', 'AppMap');
			const tmfId = index.getFirstTagID('CSM Type', 'TMF Open API');
			const platformId = index.getFirstTagID('BC Type', 'Platform');
			lx.executeGraphQL(this._createQuery(csmId, cimId, appMapId, tmfId, platformId)).then((data) => {
				index.put(data);
				this._handleData(index, csmId, cimId, appMapId, tmfId, platformId);
			});
		});
	}

	_createConfig() {
		return {
			allowEditing: false
		};
	}

	_createQuery(csmId, cimId, appMapId, tmfId, platformId) {
		// initial assume tagGroup.name changed or the id couldn't be determined otherwise
		let idFilter = { csm: '', cim: '', appMap: '', tmf: '', platform: '' };
		// initial assume to get it
		let tagNameDef = {
			csm: 'tags { name }',
			cim: 'tags { name }',
			appMap: 'tags { name }',
			tmf: 'tags { name }',
			platform: 'tags { name }'
		};
		if (csmId) {
			idFilter.csm = `, {facetKey: "CSM Type", keys: ["${csmId}"]}`;
			tagNameDef.csm = '';
		}
		if (cimId) {
			idFilter.cim = `, {facetKey: "Category", keys: ["${cimId}"]}`;
			tagNameDef.cim = '';
		}
		if (appMapId) {
			idFilter.appMap = `, {facetKey: "BC Type", keys: ["${appMapId}"]}`;
			tagNameDef.appMap = '';
		}
		if (tmfId) {
			idFilter.tmf = `, {facetKey: "CSM Type", keys: ["${tmfId}"]}`;
			tagNameDef.tmf = '';
		}
		if (platformId) {
			idFilter.platform = `, {facetKey: "BC Type", keys: ["${platformId}"]}`;
			tagNameDef.platform = '';
		}
		return `{csm: allFactSheets(
					sort: { mode: BY_FIELD, key: "displayName", order: asc },
					filter: {facetFilters: [
						{facetKey: "FactSheetTypes", keys: ["CSM"]},
						{facetKey: "hierarchyLevel", keys: ["2"]}
						${idFilter.csm}
					]}
				) {
					edges { node {
						id name description ${tagNameDef.csm}
						... on CSM {
							serviceStatus serviceClassification serviceOrigin
							relToParent { edges { node { factSheet { id name } } } }
							relToRequires (facetFilters: [
								{facetKey: "FactSheetTypes", keys: ["CSM"]}
							]) {
								edges { node { factSheet { id } } }
							}
							relToRequiredBy (facetFilters: [
								{facetKey: "FactSheetTypes", keys: ["BusinessCapability"]}
							]) {
								edges { node { factSheet { id } } }
							}
							relCSMToDataObject { edges { node { factSheet { id } } } }
						}
					}}
				}
				csmTmf: allFactSheets(
					filter: {facetFilters: [
						{facetKey: "FactSheetTypes", keys: ["CSM"]}
						${idFilter.tmf}
					]}
				) {
					edges { node {
						id name ${tagNameDef.tmf}
					}}
				}
				doCim: allFactSheets(
					filter: {facetFilters: [
						{facetKey: "FactSheetTypes", keys: ["DataObject"]}
						${idFilter.cim}
					]}
				) {
					edges { node {
						id name ${tagNameDef.cim}
						... on DataObject {
							relDataObjectToBusinessCapability { edges { node { factSheet { id } } } }
						}
					}}
				}
				bcAppMap: allFactSheets(
					filter: {facetFilters: [
						{facetKey: "FactSheetTypes", keys: ["BusinessCapability"]}
						${idFilter.appMap}
					]}
				) {
					edges { node {
						id name ${tagNameDef.appMap}
						... on BusinessCapability {
							relBusinessCapabilityToBCA { edges { node { factSheet { id name } } } }
							relBusinessCapabilityToPlatform { edges { node { factSheet { id name } } } }
						}
					}}
				}
				bcPlatform: allFactSheets(
					filter: {facetFilters: [
						{facetKey: "FactSheetTypes", keys: ["BusinessCapability"]}
						${idFilter.platform}
					]}
				) {
					edges { node {
						id name ${tagNameDef.platform}
					}}
				}}`;
	}

	_handleData(index, csmId, cimId, appMapId, tmfId, platformId) {
		const tableData = [];
		index.csm.nodes.forEach((csmL2) => {
			if (!csmId && !index.includesTag(csmL2, 'CSM')) {
				return;
			}
			const csmL1 = csmL2.relToParent ? csmL2.relToParent.nodes[0] : undefined;
			const tmfApps = [];
			const subIndexRequires = csmL2.relToRequires;
			if (subIndexRequires) {
				subIndexRequires.nodes.forEach((e) => {
					// access csmTmf
					const tmf = index.csmTmf.byID[e.id];
					if (!tmf || (!tmfId && !index.includesTag(tmf, 'TMF Open API'))) {
						return;
					}
					tmfApps.push(tmf);
				});
			}
			const cimDOs = [];
			const subIndexCIM = csmL2.relCSMToDataObject;
			if (subIndexCIM) {
				subIndexCIM.nodes.forEach((e) => {
					// access doCim
					const cim = index.doCim.byID[e.id];
					if (!cim || (!cimId && !index.includesTag(cim, 'CIM'))) {
						return;
					}
					cimDOs.push(cim);
				});
			}
			const platfConsBCs = [];
			const subIndexPlatformCons = csmL2.relToRequiredBy;
			if (subIndexPlatformCons) {
				subIndexPlatformCons.nodes.forEach((e) => {
					// access bcPlatform
					const platform = index.bcPlatform.byID[e.id];
					if (!platform || (!platformId && !index.includesTag(platform, 'Platform'))) {
						return;
					}
					platfConsBCs.push(platform);
				});
			}
			const appMapBCsSet = {};
			const platformBCsSet = {};
			const bcaBCsSet = {};
			cimDOs.forEach((cim) => {
				const appMapBCs = [];
				const subIndexAppMapBCs = cim.relDataObjectToBusinessCapability;
				if (subIndexAppMapBCs) {
					subIndexAppMapBCs.nodes.forEach((e) => {
						// access bcAppMap
						const appMap = index.byID[e.id];
						if (!appMap || (!appMapId && !index.includesTag(appMap, 'AppMap'))) {
							return;
						}
						appMapBCsSet[appMap.id] = appMap;
						const subIndexBcaBCs = appMap.relBusinessCapabilityToBCA;
						if (subIndexBcaBCs) {
							subIndexBcaBCs.nodes.forEach((e2) => {
								bcaBCsSet[e2.id] = e2;
							});
						}
						const subIndexPlatformBCs = appMap.relBusinessCapabilityToPlatform;
						if (subIndexPlatformBCs) {
							subIndexPlatformBCs.nodes.forEach((e2) => {
								platformBCsSet[e2.id] = e2;
							});
						}
					});
				}
			});
			const appMapBCs = [];
			for (let key in appMapBCsSet) {
				appMapBCs.push(appMapBCsSet[key]);
			}
			const platformBCs = [];
			for (let key in platformBCsSet) {
				platformBCs.push(platformBCsSet[key]);
			}
			const bcaBCs = [];
			for (let key in bcaBCsSet) {
				bcaBCs.push(bcaBCsSet[key]);
			}
			tableData.push({
				csmL1Id: csmL1 ? csmL1.id : '',
				csmL1Name: csmL1 ? csmL1.name : '',
				csmL2Id: csmL2.id,
				csmL2Name: csmL2.name,
				csmL2Desc: csmL2.description,
				serviceStatus: this._getOptionKeyFromValue(
					this.SERVICE_STATUS_OPTIONS, csmL2.serviceStatus),
				serviceClass: this._getOptionKeyFromValue(
					this.SERVICE_CLASSIFICATION_OPTIONS, csmL2.serviceClassification),
				serviceOrigin: this._getOptionKeyFromValue(
					this.SERVICE_ORIGIN_OPTIONS, csmL2.serviceOrigin),
				platformBCsIds: platformBCs.map((e) => {
					return e.id;
				}),
				platformBCsNames: platformBCs.map((e) => {
					return e.name;
				}),
				bcaBCsIds: bcaBCs.map((e) => {
					return e.id;
				}),
				bcaBCsNames: bcaBCs.map((e) => {
					return e.name;
				}),
				tmfAppIds: tmfApps.map((e) => {
					return e.id;
				}),
				tmfAppNames: tmfApps.map((e) => {
					return e.name;
				}),
				cimDOsIds: cimDOs.map((e) => {
					return e.id;
				}),
				cimDOsNames: cimDOs.map((e) => {
					return e.name;
				}),
				appMapBCsIds: appMapBCs.map((e) => {
					return e.id;
				}),
				appMapBCsNames: appMapBCs.map((e) => {
					return e.name;
				}),
				platfConsBCsIds: platfConsBCs.map((e) => {
					return e.id;
				}),
				platfConsBCsNames: platfConsBCs.map((e) => {
					return e.name;
				})
			});
		});
		lx.hideSpinner();
		this.setState({
			data: tableData
		});
	}

	_getOptionKeyFromValue(options, value) {
		if (!value) {
			return undefined;
		}
		const key = Utilities.getKeyToValue(options, value);
		return key !== undefined && key !== null ? parseInt(key, 10) : undefined;
	}

	render() {
		if (this.state.data.length === 0) {
			return (
Loading data ...
);
		}
		return (

		);
	}
}

export default Report;
<commit_msg>csmservices relCSMToPlatform<commit_after>import React, { Component } from 'react';
import CommonQueries from './common/CommonGraphQLQueries';
import DataIndex from './common/DataIndex';
import Utilities from './common/Utilities';
import Table from './Table';

class Report extends Component {

	constructor(props) {
		super(props);
		this._initReport = this._initReport.bind(this);
		this._handleData = this._handleData.bind(this);
		this.SERVICE_STATUS_OPTIONS = {},
		this.SERVICE_CLASSIFICATION_OPTIONS = {},
		this.SERVICE_ORIGIN_OPTIONS = {}
		this.state = {
			setup: null,
			data: []
		};
	}

	componentDidMount() {
		lx.init().then(this._initReport);
	}

	_initReport(setup) {
		lx.ready(this._createConfig());
		lx.showSpinner('Loading data ...');
		this.setState({
			setup: setup
		});
		// get options from data model
		const factsheetModel = setup.settings.dataModel.factSheets.CSM;
		this.SERVICE_STATUS_OPTIONS = Utilities.createOptionsObjFrom(
			factsheetModel, 'fields.serviceStatus.values');
		this.SERVICE_CLASSIFICATION_OPTIONS = Utilities.createOptionsObjFrom(
			factsheetModel, 'fields.serviceClassification.values');
		this.SERVICE_ORIGIN_OPTIONS = Utilities.createOptionsObjFrom(
			factsheetModel, 'fields.serviceOrigin.values');
		// get all tags, then the data
		lx.executeGraphQL(CommonQueries.tagGroups).then((tagGroups) => {
			const index = new DataIndex();
			index.put(tagGroups);
			const csmId = index.getFirstTagID('CSM Type', 'CSM');
			const cimId = index.getFirstTagID('Category', 'CIM');
			const appMapId = index.getFirstTagID('BC Type', 'AppMap');
			const tmfId = index.getFirstTagID('CSM Type', 'TMF Open API');
			const platformId = index.getFirstTagID('BC Type', 'Platform');
			lx.executeGraphQL(this._createQuery(csmId, cimId, appMapId, tmfId, platformId)).then((data) => {
				index.put(data);
				this._handleData(index, csmId, cimId, appMapId, tmfId, platformId);
			});
		});
	}

	_createConfig() {
		return {
			allowEditing: false
		};
	}

	_createQuery(csmId, cimId, appMapId, tmfId, platformId) {
		// initial assume tagGroup.name changed or the id couldn't be determined otherwise
		let idFilter = { csm: '', cim: '', appMap: '', tmf: '', platform: '' };
		// initial assume to get it
		let tagNameDef = {
			csm: 'tags { name }',
			cim: 'tags { name }',
			appMap: 'tags { name }',
			tmf: 'tags { name }',
			platform: 'tags { name }'
		};
		if (csmId) {
			idFilter.csm = `, {facetKey: "CSM Type", keys: ["${csmId}"]}`;
			tagNameDef.csm = '';
		}
		if (cimId) {
			idFilter.cim = `, {facetKey: "Category", keys: ["${cimId}"]}`;
			tagNameDef.cim = '';
		}
		if (appMapId) {
			idFilter.appMap = `, {facetKey: "BC Type", keys: ["${appMapId}"]}`;
			tagNameDef.appMap = '';
		}
		if (tmfId) {
			idFilter.tmf = `, {facetKey: "CSM Type", keys: ["${tmfId}"]}`;
			tagNameDef.tmf = '';
		}
		if (platformId) {
			idFilter.platform = `, {facetKey: "BC Type", keys: ["${platformId}"]}`;
			tagNameDef.platform = '';
		}
		return `{csm: allFactSheets(
					sort: { mode: BY_FIELD, key: "displayName", order: asc },
					filter: {facetFilters: [
						{facetKey: "FactSheetTypes", keys: ["CSM"]},
						{facetKey: "hierarchyLevel", keys: ["2"]}
						${idFilter.csm}
					]}
				) {
					edges { node {
						id name description ${tagNameDef.csm}
						... on CSM {
							serviceStatus serviceClassification serviceOrigin
							relToParent { edges { node { factSheet { id name } } } }
							relToRequires (facetFilters: [
								{facetKey: "FactSheetTypes", keys: ["CSM"]}
							]) {
								edges { node { factSheet { id } } }
							}
							relCSMToPlatform { edges { node { factSheet { id } } }
							}
							relCSMToDataObject { edges { node { factSheet { id } } } }
						}
					}}
				}
				csmTmf: allFactSheets(
					filter: {facetFilters: [
						{facetKey: "FactSheetTypes", keys: ["CSM"]}
						${idFilter.tmf}
					]}
				) {
					edges { node {
						id name ${tagNameDef.tmf}
					}}
				}
				doCim: allFactSheets(
					filter: {facetFilters: [
						{facetKey: "FactSheetTypes", keys: ["DataObject"]}
						${idFilter.cim}
					]}
				) {
					edges { node {
						id name ${tagNameDef.cim}
						... on DataObject {
							relDataObjectToBusinessCapability { edges { node { factSheet { id } } } }
						}
					}}
				}
				bcAppMap: allFactSheets(
					filter: {facetFilters: [
						{facetKey: "FactSheetTypes", keys: ["BusinessCapability"]}
						${idFilter.appMap}
					]}
				) {
					edges { node {
						id name ${tagNameDef.appMap}
						... on BusinessCapability {
							relBusinessCapabilityToBCA { edges { node { factSheet { id name } } } }
							relBusinessCapabilityToPlatform { edges { node { factSheet { id name } } } }
						}
					}}
				}
				bcPlatform: allFactSheets(
					filter: {facetFilters: [
						{facetKey: "FactSheetTypes", keys: ["BusinessCapability"]}
						${idFilter.platform}
					]}
				) {
					edges { node {
						id name ${tagNameDef.platform}
					}}
				}}`;
	}

	_handleData(index, csmId, cimId, appMapId, tmfId, platformId) {
		const tableData = [];
		index.csm.nodes.forEach((csmL2) => {
			if (!csmId && !index.includesTag(csmL2, 'CSM')) {
				return;
			}
			const csmL1 = csmL2.relToParent ? csmL2.relToParent.nodes[0] : undefined;
			const tmfApps = [];
			const subIndexRequires = csmL2.relToRequires;
			if (subIndexRequires) {
				subIndexRequires.nodes.forEach((e) => {
					// access csmTmf
					const tmf = index.csmTmf.byID[e.id];
					if (!tmf || (!tmfId && !index.includesTag(tmf, 'TMF Open API'))) {
						return;
					}
					tmfApps.push(tmf);
				});
			}
			const cimDOs = [];
			const subIndexCIM = csmL2.relCSMToDataObject;
			if (subIndexCIM) {
				subIndexCIM.nodes.forEach((e) => {
					// access doCim
					const cim = index.doCim.byID[e.id];
					if (!cim || (!cimId && !index.includesTag(cim, 'CIM'))) {
						return;
					}
					cimDOs.push(cim);
				});
			}
			const platfConsBCs = [];
			const subIndexPlatformCons = csmL2.relCSMToPlatform;
			if (subIndexPlatformCons) {
				subIndexPlatformCons.nodes.forEach((e) => {
					// access bcPlatform
					const platform = index.bcPlatform.byID[e.id];
					if (!platform || (!platformId && !index.includesTag(platform, 'Platform'))) {
						return;
					}
					platfConsBCs.push(platform);
				});
			}
			const appMapBCsSet = {};
			const platformBCsSet = {};
			const bcaBCsSet = {};
			cimDOs.forEach((cim) => {
				const appMapBCs = [];
				const subIndexAppMapBCs = cim.relDataObjectToBusinessCapability;
				if (subIndexAppMapBCs) {
					subIndexAppMapBCs.nodes.forEach((e) => {
						// access bcAppMap
						const appMap = index.byID[e.id];
						if (!appMap || (!appMapId && !index.includesTag(appMap, 'AppMap'))) {
							return;
						}
						appMapBCsSet[appMap.id] = appMap;
						const subIndexBcaBCs = appMap.relBusinessCapabilityToBCA;
						if (subIndexBcaBCs) {
							subIndexBcaBCs.nodes.forEach((e2) => {
								bcaBCsSet[e2.id] = e2;
							});
						}
						const subIndexPlatformBCs = appMap.relBusinessCapabilityToPlatform;
						if (subIndexPlatformBCs) {
							subIndexPlatformBCs.nodes.forEach((e2) => {
								platformBCsSet[e2.id] = e2;
							});
						}
					});
				}
			});
			const appMapBCs = [];
			for (let key in appMapBCsSet) {
				appMapBCs.push(appMapBCsSet[key]);
			}
			const platformBCs = [];
			for (let key in platformBCsSet) {
				platformBCs.push(platformBCsSet[key]);
			}
			const bcaBCs = [];
			for (let key in bcaBCsSet) {
				bcaBCs.push(bcaBCsSet[key]);
			}
			tableData.push({
				csmL1Id: csmL1 ? csmL1.id : '',
				csmL1Name: csmL1 ? csmL1.name : '',
				csmL2Id: csmL2.id,
				csmL2Name: csmL2.name,
				csmL2Desc: csmL2.description,
				serviceStatus: this._getOptionKeyFromValue(
					this.SERVICE_STATUS_OPTIONS, csmL2.serviceStatus),
				serviceClass: this._getOptionKeyFromValue(
					this.SERVICE_CLASSIFICATION_OPTIONS, csmL2.serviceClassification),
				serviceOrigin: this._getOptionKeyFromValue(
					this.SERVICE_ORIGIN_OPTIONS, csmL2.serviceOrigin),
				platformBCsIds: platformBCs.map((e) => {
					return e.id;
				}),
				platformBCsNames: platformBCs.map((e) => {
					return e.name;
				}),
				bcaBCsIds: bcaBCs.map((e) => {
					return e.id;
				}),
				bcaBCsNames: bcaBCs.map((e) => {
					return e.name;
				}),
				tmfAppIds: tmfApps.map((e) => {
					return e.id;
				}),
				tmfAppNames: tmfApps.map((e) => {
					return e.name;
				}),
				cimDOsIds: cimDOs.map((e) => {
					return e.id;
				}),
				cimDOsNames: cimDOs.map((e) => {
					return e.name;
				}),
				appMapBCsIds: appMapBCs.map((e) => {
					return e.id;
				}),
				appMapBCsNames: appMapBCs.map((e) => {
					return e.name;
				}),
				platfConsBCsIds: platfConsBCs.map((e) => {
					return e.id;
				}),
				platfConsBCsNames: platfConsBCs.map((e) => {
					return e.name;
				})
			});
		});
		lx.hideSpinner();
		this.setState({
			data: tableData
		});
	}

	_getOptionKeyFromValue(options, value) {
		if (!value) {
			return undefined;
		}
		const key = Utilities.getKeyToValue(options, value);
		return key !== undefined && key !== null ? parseInt(key, 10) : undefined;
	}

	render() {
		if (this.state.data.length === 0) {
			return (
Loading data ...
);
		}
		return (

		);
	}
}

export default Report;
<|endoftext|>


<commit_before><commit_msg>Deleted duplicate footer js file<commit_after><|endoftext|>


<commit_before>const https = require('https')
const mkdirp = require('mkdirp')
const tar = require('tar')
const npmrc = require('../utils/npmrc')
const URL = require('url').URL
const path = require('path')
const fs = require('fs')
const nm = path.join(process.env.PWD, 'node_modules')

const installer = (keys, tree, base, cb) => {
  mkdirp.sync(nm)
  mkdirp.sync(path.join(nm, '.bin'))
  return keys.map((key) => {
    const hasDeps = typeof tree[key] === 'object' && tree[key].dependencies
    const target = base ? path.join(nm, base, 'node_modules', key) : path.join(nm, key)
    const url = new URL(tree[key].tarball)
    mkdirp.sync(target)
    global.dependenciesCount += 1
    return new Promise((resolve, reject) => {
      if (hasDeps) {
        installer(
          Object.keys(tree[key].dependencies),
          tree[key].dependencies,
          key,
          resolve
        )
      }
      https.get({
        host: url.host,
        path: url.pathname,
        headers: npmrc.userAgent
      }, (res) => {
        res.pipe(
          tar.x({
            cwd: target,
            sync: true,
            strip: 1
          })
        )
        res.on('end', () => {
          const pkgJSON = require(path.join(target, 'package.json'))
          if (!pkgJSON.bin) return resolve()
          if (typeof pkgJSON.bin === 'string') {
            fs.symlinkSync(
              path.join(target, pkgJSON.bin),
              path.join(nm, '.bin', key)
            )
          } else if (typeof pkgJSON.bin === 'object') {
            Object.keys(pkgJSON.bin).forEach((cmd) => {
              fs.symlinkSync(
                path.join(target, pkgJSON.bin[cmd]),
                path.join(nm, '.bin', cmd)
              )
            })
          }
          if (!hasDeps) return resolve()
          if (!cb) return resolve()
          cb()
        })
      }).on('error', reject)
    })
  })
}

module.exports = installer
<commit_msg>add exists check<commit_after>const https = require('https')
const mkdirp = require('mkdirp')
const tar = require('tar')
const npmrc = require('../utils/npmrc')
const URL = require('url').URL
const path = require('path')
const fs = require('fs')
const nm = path.join(process.env.PWD, 'node_modules')

const installer = (keys, tree, base, cb) => {
  mkdirp.sync(nm)
  mkdirp.sync(path.join(nm, '.bin'))
  return keys.map((key) => {
    const hasDeps = typeof tree[key] === 'object' && tree[key].dependencies
    const target = base ? path.join(nm, base, 'node_modules', key) : path.join(nm, key)
    const url = new URL(tree[key].tarball)
    mkdirp.sync(target)
    global.dependenciesCount += 1
    return new Promise((resolve, reject) => {
      if (hasDeps) {
        installer(
          Object.keys(tree[key].dependencies),
          tree[key].dependencies,
          key,
          resolve
        )
      }
      https.get({
        host: url.host,
        path: url.pathname,
        headers: npmrc.userAgent
      }, (res) => {
        res.pipe(
          tar.x({
            cwd: target,
            sync: true,
            strip: 1
          })
        )
        res.on('end', () => {
          const pkgJSON = require(path.join(target, 'package.json'))
          if (!pkgJSON.bin) return resolve()
          if (typeof pkgJSON.bin === 'string') {
            try {
              fs.unlinkSync(path.join(nm, '.bin', key))
            } catch (e) {}
            fs.symlinkSync(
              path.join(target, pkgJSON.bin),
              path.join(nm, '.bin', key)
            )
          } else if (typeof pkgJSON.bin === 'object') {
            Object.keys(pkgJSON.bin).forEach((cmd) => {
              try {
                fs.unlinkSync(path.join(nm, '.bin', cmd))
              } catch (e) {}
              fs.symlinkSync(
                path.join(target, pkgJSON.bin[cmd]),
                path.join(nm, '.bin', cmd)
              )
            })
          }
          if (!hasDeps) return resolve()
          if (!cb) return resolve()
          cb()
        })
      }).on('error', reject)
    })
  })
}

module.exports = installer
<|endoftext|>


<commit_before>/* @flow */
'use strict';

import Relay from 'react-relay';
import React from 'react';
import { ScrollView, StyleSheet, View, Dimensions } from 'react-native';

import colors from '../../../../data/colors';
import SerifText from '../../text/serif';
import ImageView from '../../opaque_image_view';
import RelatedArtist from './related_artist'

const window = Dimensions.get('window')

class RelatedArtists extends React.Component {
    constructor(props) {
    super(props);
    this.state = { columns: this.columnCount()};
    this.onLayout = this.onLayout.bind(this);
  }

  columnCount() {
    const isPhone = window.width < 700;
    const isPadHorizontal = window.width > 1000;
    return !isPhone ? (isPadHorizontal ? 4 : 3) : 2;
  }

  onLayout(e) {
    this.setState({
    columns: this.columnCount()
  });
    // TODO ensure that on the initial render, and when it calls this callback, setting the state again should not
    // actually cause another render, as the number of columns should remain the same.
  }

  render() {
    const artists = this.props.artists;
    return (
      <View style={styles.container}>
        <SerifText style={styles.heading}>Related Artists</SerifText>
        <View style={styles.artistContainer}>
          { this.renderArtists() }
        </View>
      </View>
    );
  }

  renderArtists() {
    const artists = this.props.artists;
    var artistViews = artists.map(artist => <RelatedArtist key={artist.id} artist={artist} />);
    const extraRequiredViews = artists.length % this.state.columns;
    for (var i = 0; i < extraRequiredViews; i++) {
      artistViews.push(<RelatedArtist key={'dummy'+i.toString()} artist={null}/>);
    }
    return artistViews;
  }
}

const styles = StyleSheet.create({
  container: {
    flexDirection: 'column',
  },
  artistContainer: {
    flexWrap: 'wrap',
    flexDirection: 'row',
    justifyContent: 'space-around',
    marginTop: 20,
    marginLeft: -10,
    marginRight: -10,
  },
  heading: {
    fontSize: 20,
  }
});

export default Relay.createContainer(RelatedArtists, {
  fragments: {
    artists: () => Relay.QL`
      fragment on Artist @relay(plural: true) {
        id
        ${RelatedArtist.getFragment('artist')}
      }
    `
  }
})<commit_msg>[RelatedArtists] Fix logic for calculating dummy views to add<commit_after>/* @flow */
'use strict';

import Relay from 'react-relay';
import React from 'react';
import { ScrollView, StyleSheet, View, Dimensions } from 'react-native';

import colors from '../../../../data/colors';
import SerifText from '../../text/serif';
import ImageView from '../../opaque_image_view';
import RelatedArtist from './related_artist'

const window = Dimensions.get('window')

class RelatedArtists extends React.Component {
    constructor(props) {
    super(props);
    this.state = { columns: this.columnCount()};
    this.onLayout = this.onLayout.bind(this);
  }

  columnCount() {
    const isPhone = window.width < 700;
    const isPadHorizontal = window.width > 1000;
    return !isPhone ? (isPadHorizontal ? 4 : 3) : 2;
  }

  onLayout(e) {
    this.setState({
    columns: this.columnCount()
  });
    // TODO ensure that on the initial render, and when it calls this callback, setting the state again should not
    // actually cause another render, as the number of columns should remain the same.
  }

  render() {
    const artists = this.props.artists;
    return (
      <View style={styles.container}>
        <SerifText style={styles.heading}>Related Artists</SerifText>
        <View style={styles.artistContainer}>
          { this.renderArtists() }
        </View>
      </View>
    );
  }

  renderArtists() {
    const artists = this.props.artists;
    var artistViews = artists.map(artist => <RelatedArtist key={artist.id} artist={artist} />);
    const extraRequiredViews = this.state.columns - (artists.length % this.state.columns);
    for (var i = 0; i < extraRequiredViews; i++) {
      debugger;
      artistViews.push(<RelatedArtist key={'dummy'+i.toString()} artist={null}/>);
    }
    return artistViews;
  }
}

const styles = StyleSheet.create({
  container: {
    flexDirection: 'column',
  },
  artistContainer: {
    flexWrap: 'wrap',
    flexDirection: 'row',
    justifyContent: 'space-around',
    marginTop: 20,
    marginLeft: -10,
    marginRight: -10,
  },
  heading: {
    fontSize: 20,
  }
});

export default Relay.createContainer(RelatedArtists, {
  fragments: {
    artists: () => Relay.QL`
      fragment on Artist @relay(plural: true) {
        id
        ${RelatedArtist.getFragment('artist')}
      }
    `
  }
})<|endoftext|>