<commit_before>lobby.transporter.module.create('outliners', function(thisModule) {

thisModule.addSlots(lobby, function(add) {

  add.method('OutlinerMorph', function OutlinerMorph() { Class.initializer.apply(this, arguments); }, {category: ['outliners']});

});

thisModule.addSlots(OutlinerMorph, function(add) {

  add.data('superclass', ColumnMorph);

  add.creator('prototype', Object.create(ColumnMorph.prototype), {}, {copyDownParents: [{parent: CanHaveArrowsAttachedToIt}, {parent: CategoryMorphMixin}]});

  add.data('type', OutlinerMorph);

});

thisModule.addSlots(OutlinerMorph.prototype, function(add) {

  add.data('constructor', OutlinerMorph);

  add.method('initialize', function ($super, m) {
    $super();
    this._mirror = m;

    this.setPadding({top: 2, bottom: 2, left: 4, right: 4, between: 2});
    this.shape.roundEdgesBy(10);

    this._slotMorphs     = bloodyHashTable.copyRemoveAll();
    this._categoryMorphs = bloodyHashTable.copyRemoveAll();

    this.initializeCategoryUI();

    this._evaluatorsPanel = new ColumnMorph().beInvisible();
    this._evaluatorsPanel.horizontalLayoutMode = LayoutModes.SpaceFill;

    this.titleLabel = createLabel(function() {return m.inspect();});
    this.titleLabel.setEmphasis({style: 'bold'});

    this.commentButton   = createButton("'...'", function(evt) { this.toggleComment(evt); }.bind(this), 1);
    this.evaluatorButton = createButton("E", function(evt) { this.openEvaluator(evt); }.bind(this), 1);
    this.dismissButton   = this.createDismissButton();

    this.createHeaderRow();

    this.replaceThingiesWith([this._headerRow, this._evaluatorsPanel]);

    this.startPeriodicallyUpdating();
  }, {category: ['creating']});

  add.method('mirror', function () { return this._mirror; }, {category: ['accessing']});

  add.method('outliner', function () { return this; }, {comment: 'For compatibility for CategoryMorph.', category: ['accessing']});

  add.method('category', function () { return Category.root(); }, {category: ['accessing']});

  add.method('createHeaderRow', function () {
    var r = this._headerRow = new RowMorph().beInvisible(); // aaa - put underscores in front of the instvars
    this._headerRowSpacer = createSpacer();
    r.setPadding({top: 0, bottom: 0, left: 0, right: 0, between: 3});
    r.horizontalLayoutMode = LayoutModes.SpaceFill;
    r.inspect = function() {return "the header row";};
    r.refreshContent = function() { this.refreshHeaderRow(); }.bind(this);
    this.refreshHeaderRow();
    return r;
  }, {category: ['initializing']});

  add.method('refreshHeaderRow', function () {
    var ms = [this._expander, this.titleLabel];
    if (this._shouldShowComment || (this.mirror().comment && this.mirror().comment())) { ms.push(this.commentButton); }
    ms.push(this._headerRowSpacer);
    ms.push(this.evaluatorButton);
    ms.push(this.dismissButton);
    this._headerRow.replaceThingiesWith(ms);
  }, {category: ['updating']});

  add.method('annotationMorph', function () {
    var m = this._annotationMorph;
    if (m) { return m; }
    m = this._annotationMorph = new ColumnMorph(this).beInvisible();
    m.horizontalLayoutMode = LayoutModes.SpaceFill;

    // aaa - shouldn't really be a string; do something nicer, some way of specifying a list
    this._copyDownParentsLabel = createInputBox(this.copyDownParentsString.bind(this), this.setCopyDownParentsString.bind(this));
    m.addRow(createLabelledNode("Copy-down parents", this._copyDownParentsLabel));
    return m;
  }, {category: ['annotation']});

  add.method('toggleAnnotation', function (evt) {
    this._shouldShowAnnotation = !this._shouldShowAnnotation;
    this.updateExpandedness();
  }, {category: ['annotation']});

  add.method('copyDownParentsString', function () {
    return reflect(this.mirror().copyDownParents()).expressionEvaluatingToMe();
  }, {category: ['annotation']});

  add.method('setCopyDownParentsString', function (str) {
    MessageNotifierMorph.showIfErrorDuring(function() {
      this.mirror().setCopyDownParents(eval(str));
    }.bind(this), createFakeEvent());
    this.updateAppearance();
  }, {category: ['annotation']});

  add.method('updateAppearance', function () {
    if (! this.world()) {return;}
    this.populateSlotsPanel();
    this._slotsPanel.submorphs.each(function(m) { m.updateAppearance(); }); // aaa is this gonna cause us to redo a lot of work?
    this.refillWithAppropriateColor();
    this.titleLabel.refreshText();
    this._modulesLabel.refreshText();
    if (this._copyDownParentsLabel) {this._copyDownParentsLabel.refreshText();}
    this._headerRow.refreshContent();
    this.minimumExtentChanged();
  }, {category: ['updating']});

  add.method('startPeriodicallyUpdating', function () {
    this._updater = new PeriodicalExecuter(function(pe) { this.updateAppearance(); }.bind(this), 8);
  }, {category: ['updating']});

  add.method('inspect', function () {return this.mirror().inspect();}, {category: ['printing']});

  add.method('updateExpandedness', function () {
    if (! this.world()) {return;}
    var thingies = [this._headerRow];
    if (this._shouldShowAnnotation) { thingies.push(this.annotationMorph()); }
    if (this._shouldShowComment   ) { thingies.push(this.   commentMorph()); }
    if (this.expander().isExpanded()) { thingies.push(this.slotsPanel()); }
    thingies.push(this._evaluatorsPanel);
    this.replaceThingiesWith(thingies);
  }, {category: ['updating']});

  add.method('expandCategory', function (c) {
    var expander = c.isRoot() ? this.expander() : this.categoryMorphFor(c).expander();
    expander.expand();
  }, {category: ['categories']});

  add.method('eachSlot', function (f) {
    this.mirror().eachFakeSlot(f);
    this.mirror().eachSlotInCategory(this.category(), f);
  }, {category: ['iterating']});

  add.method('slotMorphFor', function (s) {
    return this._slotMorphs.getOrIfAbsentPut(s.name(), function() { return new SlotMorph(s); });
  }, {category: ['slots panel']});

  add.method('existingCategoryMorphFor', function (c) {
    return this._categoryMorphs.get(c.fullName());
  }, {category: ['categories']});

  add.method('categoryMorphFor', function (c) {
    return this._categoryMorphs.getOrIfAbsentPut(c.fullName(), function() { return new CategoryMorph(this, c); }.bind(this));
  }, {category: ['categories']});

  add.method('commentMorph', function () {
    var m = this._commentMorph;
    if (m) { return m; }
    var thisOutliner = this;
    return this._commentMorph = createInputBox(function( ) { return thisOutliner.mirror().comment(); },
                                               function(c) { thisOutliner.mirror().setComment(c); });
  }, {category: ['comment']});

  add.method('toggleComment', function (evt) {
    this._shouldShowComment = !this._shouldShowComment;
    this.updateExpandedness();
    this.updateAppearance();
    if (this._shouldShowComment) { this.commentMorph().requestKeyboardFocus(evt.hand); }
  }, {category: ['comment']});

  add.method('openEvaluator', function (evt) {
    var e = new EvaluatorMorph(this);
    this._evaluatorsPanel.addRow(e);
    e.textMorph().requestKeyboardFocus(evt.hand);
  }, {category: ['evaluators']});

  add.method('morphMenu', function (evt) {
    var menu = new MenuMorph([], this);

    if (this.mirror().canHaveSlots()) {
      menu.addSection([["add slot",     function(evt) { this.addSlot    (evt); }.bind(this)]]);
      menu.addSection([["add category", function(evt) { this.addCategory(evt); }.bind(this)]]);
    }
    if (this.mirror().canHaveChildren()) {
      menu.addSection([["create child", function(evt) { this.createChild(evt); }.bind(this)]]);
    }

    if (this.mirror().canHaveAnnotation()) {
      menu.addLine();

      if (this.mirror().comment) {
        menu.addItem([this._shouldShowComment ? "hide comment" : "show comment", function(evt) { this.toggleComment(evt); }.bind(this)]);
      }

      menu.addItem([this._shouldShowAnnotation ? "hide annotation" : "show annotation", function(evt) { this.toggleAnnotation(evt); }.bind(this)]);

      menu.addItem(["set module...", function(evt) {
        var all = {};
        var chooseTargetModule = function(sourceModuleName, evt) {
          transporter.chooseOrCreateAModule(evt, this, "To which module?", function(targetModule, evt) {
            this.mirror().eachNormalSlot(function(slot) {
              if (! slot.isFromACopyDownParent()) {
                if (sourceModuleName === all || (!slot.module() && sourceModuleName === '-') || (slot.module() && slot.module().name() === sourceModuleName)) {
                  slot.setModule(targetModule);
                }
              }
            }.bind(this));
          }.bind(this));
        }.bind(this);

        var whichSlotsMenu = new MenuMorph([], this);
        whichSlotsMenu.addItem(["All", function(evt) {chooseTargetModule(all, evt);}.bind(this)]);
        whichSlotsMenu.addLine();
        this.modules().map(function(m) { return m ? m.name() : '-'; }).sort().each(function(moduleName) {
          whichSlotsMenu.addItem([moduleName, function(evt) {chooseTargetModule(moduleName, evt);}.bind(this)]);
        }.bind(this));
        whichSlotsMenu.openIn(this.world(), evt.point(), false, "Of which slots?");
      }.bind(this)]);
    }

    menu.addLine();

    menu.addItem(["well-known references", function(evt) {
      evt.hand.world().outlinerFor(reflect(this.mirror().wellKnownReferences())).grabMe(evt);
    }.bind(this)]);

    menu.addItem(["well-known children", function(evt) {
      evt.hand.world().outlinerFor(reflect(this.mirror().wellKnownChildren())).grabMe(evt);
    }.bind(this)]);

    return menu;
  }, {category: ['menu']});

  add.method('createChild', function (evt) {
    var child = this.mirror().createChild();
    var childOutliner = this.world().outlinerFor(child);
    childOutliner.grabMe(evt);

    // might as well show the arrow from the child to the parent
    childOutliner.expander().expand();
    var parentSlotMorph = childOutliner.slotMorphFor(child.parentSlot());
    parentSlotMorph.contentsPointer().getModel().setValue(false);
  }, {category: ['creating children']});

  add.method('acceptsDropping', function (m) { // aaa - could this be generalized?
    return typeof(m.wasJustDroppedOnOutliner) === 'function';
  }, {category: ['drag and drop']});

  add.method('justReceivedDrop', function (m) {
    if (this.acceptsDropping(m)) {
      m.wasJustDroppedOnOutliner(this);
    }
  }, {category: ['drag and drop']});

});

thisModule.addSlots(WorldMorph.prototype, function(add) {

  add.method('outliners', function () {
    return this._outliners || (this._outliners = bloodyHashTable.copyRemoveAll());
  });

  add.method('existingOutlinerFor', function (mir) {
    return this.outliners().get(mir);
  });

  add.method('outlinerFor', function (mir) {
    return this.outliners().getOrIfAbsentPut(mir, function() {return new OutlinerMorph(mir);});
  });

  add.method('cleanUpOutliners', function (evt) {
    var outlinersToMove = [];
    this.outliners().eachValue(function(outliner) {
      if (outliner.world() === this) { // not null
        outlinersToMove.push(outliner);
      }
    }.bind(this));

    var sortedOutlinersToMove = outlinersToMove.sort(function(o1, o2) {
      var n1 = o1.inspect();
      var n2 = o2.inspect();
      return n1 < n2 ? -1 : n1 === n2 ? 0 : 1;
    });

    var pos = pt(20,20);
    var widest = 0;
    for (var i = 0; i < sortedOutlinersToMove.length; ++i) {
      var outliner = sortedOutlinersToMove[i];
      outliner.expander().collapse();
      outliner.startZoomingTo(pos);
      var extent = outliner.getExtent();
      pos = pos.withY(pos.y + extent.y);
      widest = Math.max(widest, extent.x);
      if (pos.y >= this.getExtent().y - 30) { pos = pt(pos.x + widest + 20, 20); }
    }
  });

  add.method('acceptsDropping', function (m) {
    return typeof m.wasJustDroppedOnWorld === 'function';
  });

  add.method('justReceivedDrop', function (m) {
    if (this.acceptsDropping(m)) {
      m.wasJustDroppedOnWorld(this);
    }
  });

  add.method('livelyOutlinersWorldMenu', function (evt) {
    var menu = new MenuMorph([], this);
    menu.addItem(["create new object", function(evt) {
      this.outlinerFor(reflect({})).grabMe(evt);
    }]);

    menu.addItem(["get the Global object", function(evt) {
      this.outlinerFor(reflect(Global)).grabMe(evt);
    }]);

    menu.addLine();

    menu.addItem(["file in module...", function(evt) {
      var filenames = new FileDirectory(lobby.transporter.module.urlForModuleDirectory()).filenames().select(function(n) {return n.endsWith(".js");});

      var modulesMenu = new MenuMorph(filenames.map(function(n) {return [n, function(evt) {
        var moduleName = n.substring(0, n.length - 3);
        MessageNotifierMorph.showIfErrorDuring(function() { lobby.transporter.module.fileIn(moduleName); }, evt);
      }];}), this);

      modulesMenu.openIn(this, evt.point());
    }.bind(this)]);

    menu.addItem(["file out module...", function(evt) {
      var modulesMenu = new MenuMorph([], this);
      lobby.transporter.module.eachModule(function(m) {
        modulesMenu.addItem([m.name(), function(evt) {
          MessageNotifierMorph.showIfErrorDuring(function() { m.fileOut(); }, evt);
        }.bind(this)]);
      }.bind(this));
      modulesMenu.openIn(this, evt.point());
    }.bind(this)]);

    menu.addLine();

    menu.addItem(["clean up", function(evt) {
      this.cleanUpOutliners(evt);
    }.bind(this)]);

    return menu;
  });

});

thisModule.addSlots(transporter, function(add) {

    add.method('chooseOrCreateAModule', function (evt, targetMorph, menuCaption, callback) {
    var modulesMenu = new MenuMorph([], targetMorph);
    modulesMenu.addItem(["new module...", function(evt) {
      evt.hand.world().prompt("Module name?", function(name) {
        if (name) {
          if (lobby.modules[name]) {
            throw "There is already a module named " + name;
          }
          callback(lobby.transporter.module.named(name), evt);
        }
      });
    }]);
    modulesMenu.addLine();
    lobby.transporter.module.eachModule(function(m) {
      modulesMenu.addItem([m.name(), function(evt) {
        callback(m, evt);
      }]);
    });
    modulesMenu.openIn(targetMorph.world(), evt.point(), false, menuCaption);
  });

});

});
<commit_msg>Might as well not have "file in module..." show up on the demo server, since it doesn't work anyway.<commit_after>lobby.transporter.module.create('outliners', function(thisModule) {

thisModule.addSlots(lobby, function(add) {

  add.method('OutlinerMorph', function OutlinerMorph() { Class.initializer.apply(this, arguments); }, {category: ['outliners']});

});

thisModule.addSlots(OutlinerMorph, function(add) {

  add.data('superclass', ColumnMorph);

  add.creator('prototype', Object.create(ColumnMorph.prototype), {}, {copyDownParents: [{parent: CanHaveArrowsAttachedToIt}, {parent: CategoryMorphMixin}]});

  add.data('type', OutlinerMorph);

});

thisModule.addSlots(OutlinerMorph.prototype, function(add) {

  add.data('constructor', OutlinerMorph);

  add.method('initialize', function ($super, m) {
    $super();
    this._mirror = m;

    this.setPadding({top: 2, bottom: 2, left: 4, right: 4, between: 2});
    this.shape.roundEdgesBy(10);

    this._slotMorphs     = bloodyHashTable.copyRemoveAll();
    this._categoryMorphs = bloodyHashTable.copyRemoveAll();

    this.initializeCategoryUI();

    this._evaluatorsPanel = new ColumnMorph().beInvisible();
    this._evaluatorsPanel.horizontalLayoutMode = LayoutModes.SpaceFill;

    this.titleLabel = createLabel(function() {return m.inspect();});
    // this.titleLabel.setFontFamily('serif'); // not sure I like it
    this.titleLabel.setEmphasis({style: 'bold'});

    this.commentButton   = createButton("'...'", function(evt) { this.toggleComment(evt); }.bind(this), 1);
    this.evaluatorButton = createButton("E", function(evt) { this.openEvaluator(evt); }.bind(this), 1);
    this.dismissButton   = this.createDismissButton();

    this.createHeaderRow();

    this.replaceThingiesWith([this._headerRow, this._evaluatorsPanel]);

    this.startPeriodicallyUpdating();
  }, {category: ['creating']});

  add.method('mirror', function () { return this._mirror; }, {category: ['accessing']});

  add.method('outliner', function () { return this; }, {comment: 'For compatibility for CategoryMorph.', category: ['accessing']});

  add.method('category', function () { return Category.root(); }, {category: ['accessing']});

  add.method('createHeaderRow', function () {
    var r = this._headerRow = new RowMorph().beInvisible(); // aaa - put underscores in front of the instvars
    this._headerRowSpacer = createSpacer();
    r.setPadding({top: 0, bottom: 0, left: 0, right: 0, between: 3});
    r.horizontalLayoutMode = LayoutModes.SpaceFill;
    r.inspect = function() {return "the header row";};
    r.refreshContent = function() { this.refreshHeaderRow(); }.bind(this);
    this.refreshHeaderRow();
    return r;
  }, {category: ['initializing']});

  add.method('refreshHeaderRow', function () {
    var ms = [this._expander, this.titleLabel];
    if (this._shouldShowComment || (this.mirror().comment && this.mirror().comment())) { ms.push(this.commentButton); }
    ms.push(this._headerRowSpacer);
    ms.push(this.evaluatorButton);
    ms.push(this.dismissButton);
    this._headerRow.replaceThingiesWith(ms);
  }, {category: ['updating']});

  add.method('annotationMorph', function () {
    var m = this._annotationMorph;
    if (m) { return m; }
    m = this._annotationMorph = new ColumnMorph(this).beInvisible();
    m.horizontalLayoutMode = LayoutModes.SpaceFill;

    // aaa - shouldn't really be a string; do something nicer, some way of specifying a list
    this._copyDownParentsLabel = createInputBox(this.copyDownParentsString.bind(this), this.setCopyDownParentsString.bind(this));
    m.addRow(createLabelledNode("Copy-down parents", this._copyDownParentsLabel));
    return m;
  }, {category: ['annotation']});

  add.method('toggleAnnotation', function (evt) {
    this._shouldShowAnnotation = !this._shouldShowAnnotation;
    this.updateExpandedness();
  }, {category: ['annotation']});

  add.method('copyDownParentsString', function () {
    return reflect(this.mirror().copyDownParents()).expressionEvaluatingToMe();
  }, {category: ['annotation']});

  add.method('setCopyDownParentsString', function (str) {
    MessageNotifierMorph.showIfErrorDuring(function() {
      this.mirror().setCopyDownParents(eval(str));
    }.bind(this), createFakeEvent());
    this.updateAppearance();
  }, {category: ['annotation']});

  add.method('updateAppearance', function () {
    if (! this.world()) {return;}
    this.populateSlotsPanel();
    this._slotsPanel.submorphs.each(function(m) { m.updateAppearance(); }); // aaa is this gonna cause us to redo a lot of work?
    this.refillWithAppropriateColor();
    this.titleLabel.refreshText();
    this._modulesLabel.refreshText();
    if (this._copyDownParentsLabel) {this._copyDownParentsLabel.refreshText();}
    this._headerRow.refreshContent();
    this.minimumExtentChanged();
  }, {category: ['updating']});

  add.method('startPeriodicallyUpdating', function () {
    this._updater = new PeriodicalExecuter(function(pe) { this.updateAppearance(); }.bind(this), 8);
  }, {category: ['updating']});

  add.method('inspect', function () {return this.mirror().inspect();}, {category: ['printing']});

  add.method('updateExpandedness', function () {
    if (! this.world()) {return;}
    var thingies = [this._headerRow];
    if (this._shouldShowAnnotation) { thingies.push(this.annotationMorph()); }
    if (this._shouldShowComment   ) { thingies.push(this.   commentMorph()); }
    if (this.expander().isExpanded()) { thingies.push(this.slotsPanel()); }
    thingies.push(this._evaluatorsPanel);
    this.replaceThingiesWith(thingies);
  }, {category: ['updating']});

  add.method('expandCategory', function (c) {
    var expander = c.isRoot() ? this.expander() : this.categoryMorphFor(c).expander();
    expander.expand();
  }, {category: ['categories']});

  add.method('eachSlot', function (f) {
    this.mirror().eachFakeSlot(f);
    this.mirror().eachSlotInCategory(this.category(), f);
  }, {category: ['iterating']});

  add.method('slotMorphFor', function (s) {
    return this._slotMorphs.getOrIfAbsentPut(s.name(), function() { return new SlotMorph(s); });
  }, {category: ['slots panel']});

  add.method('existingCategoryMorphFor', function (c) {
    return this._categoryMorphs.get(c.fullName());
  }, {category: ['categories']});

  add.method('categoryMorphFor', function (c) {
    return this._categoryMorphs.getOrIfAbsentPut(c.fullName(), function() { return new CategoryMorph(this, c); }.bind(this));
  }, {category: ['categories']});

  add.method('commentMorph', function () {
    var m = this._commentMorph;
    if (m) { return m; }
    var thisOutliner = this;
    return this._commentMorph = createInputBox(function( ) { return thisOutliner.mirror().comment(); },
                                               function(c) { thisOutliner.mirror().setComment(c); });
  }, {category: ['comment']});

  add.method('toggleComment', function (evt) {
    this._shouldShowComment = !this._shouldShowComment;
    this.updateExpandedness();
    this.updateAppearance();
    if (this._shouldShowComment) { this.commentMorph().requestKeyboardFocus(evt.hand); }
  }, {category: ['comment']});

  add.method('openEvaluator', function (evt) {
    var e = new EvaluatorMorph(this);
    this._evaluatorsPanel.addRow(e);
    e.textMorph().requestKeyboardFocus(evt.hand);
  }, {category: ['evaluators']});

  add.method('morphMenu', function (evt) {
    var menu = new MenuMorph([], this);

    if (this.mirror().canHaveSlots()) {
      menu.addSection([["add slot",     function(evt) { this.addSlot    (evt); }.bind(this)]]);
      menu.addSection([["add category", function(evt) { this.addCategory(evt); }.bind(this)]]);
    }
    if (this.mirror().canHaveChildren()) {
      menu.addSection([["create child", function(evt) { this.createChild(evt); }.bind(this)]]);
    }

    if (this.mirror().canHaveAnnotation()) {
      menu.addLine();

      if (this.mirror().comment) {
        menu.addItem([this._shouldShowComment ? "hide comment" : "show comment", function(evt) { this.toggleComment(evt); }.bind(this)]);
      }

      menu.addItem([this._shouldShowAnnotation ? "hide annotation" : "show annotation", function(evt) { this.toggleAnnotation(evt); }.bind(this)]);

      menu.addItem(["set module...", function(evt) {
        var all = {};
        var chooseTargetModule = function(sourceModuleName, evt) {
          transporter.chooseOrCreateAModule(evt, this, "To which module?", function(targetModule, evt) {
            this.mirror().eachNormalSlot(function(slot) {
              if (! slot.isFromACopyDownParent()) {
                if (sourceModuleName === all || (!slot.module() && sourceModuleName === '-') || (slot.module() && slot.module().name() === sourceModuleName)) {
                  slot.setModule(targetModule);
                }
              }
            }.bind(this));
          }.bind(this));
        }.bind(this);

        var whichSlotsMenu = new MenuMorph([], this);
        whichSlotsMenu.addItem(["All", function(evt) {chooseTargetModule(all, evt);}.bind(this)]);
        whichSlotsMenu.addLine();
        this.modules().map(function(m) { return m ? m.name() : '-'; }).sort().each(function(moduleName) {
          whichSlotsMenu.addItem([moduleName, function(evt) {chooseTargetModule(moduleName, evt);}.bind(this)]);
        }.bind(this));
        whichSlotsMenu.openIn(this.world(), evt.point(), false, "Of which slots?");
      }.bind(this)]);
    }

    menu.addLine();

    menu.addItem(["well-known references", function(evt) {
      evt.hand.world().outlinerFor(reflect(this.mirror().wellKnownReferences())).grabMe(evt);
    }.bind(this)]);

    menu.addItem(["well-known children", function(evt) {
      evt.hand.world().outlinerFor(reflect(this.mirror().wellKnownChildren())).grabMe(evt);
    }.bind(this)]);

    return menu;
  }, {category: ['menu']});

  add.method('createChild', function (evt) {
    var child = this.mirror().createChild();
    var childOutliner = this.world().outlinerFor(child);
    childOutliner.grabMe(evt);

    // might as well show the arrow from the child to the parent
    childOutliner.expander().expand();
    var parentSlotMorph = childOutliner.slotMorphFor(child.parentSlot());
    parentSlotMorph.contentsPointer().getModel().setValue(false);
  }, {category: ['creating children']});

  add.method('acceptsDropping', function (m) { // aaa - could this be generalized?
    return typeof(m.wasJustDroppedOnOutliner) === 'function';
  }, {category: ['drag and drop']});

  add.method('justReceivedDrop', function (m) {
    if (this.acceptsDropping(m)) {
      m.wasJustDroppedOnOutliner(this);
    }
  }, {category: ['drag and drop']});

});

thisModule.addSlots(WorldMorph.prototype, function(add) {

  add.method('outliners', function () {
    return this._outliners || (this._outliners = bloodyHashTable.copyRemoveAll());
  });

  add.method('existingOutlinerFor', function (mir) {
    return this.outliners().get(mir);
  });

  add.method('outlinerFor', function (mir) {
    return this.outliners().getOrIfAbsentPut(mir, function() {return new OutlinerMorph(mir);});
  });

  add.method('cleanUpOutliners', function (evt) {
    var outlinersToMove = [];
    this.outliners().eachValue(function(outliner) {
      if (outliner.world() === this) { // not null
        outlinersToMove.push(outliner);
      }
    }.bind(this));

    var sortedOutlinersToMove = outlinersToMove.sort(function(o1, o2) {
      var n1 = o1.inspect();
      var n2 = o2.inspect();
      return n1 < n2 ? -1 : n1 === n2 ? 0 : 1;
    });

    var pos = pt(20,20);
    var widest = 0;
    for (var i = 0; i < sortedOutlinersToMove.length; ++i) {
      var outliner = sortedOutlinersToMove[i];
      outliner.expander().collapse();
      outliner.startZoomingTo(pos);
      var extent = outliner.getExtent();
      pos = pos.withY(pos.y + extent.y);
      widest = Math.max(widest, extent.x);
      if (pos.y >= this.getExtent().y - 30) { pos = pt(pos.x + widest + 20, 20); }
    }
  });

  add.method('acceptsDropping', function (m) {
    return typeof m.wasJustDroppedOnWorld === 'function';
  });

  add.method('justReceivedDrop', function (m) {
    if (this.acceptsDropping(m)) {
      m.wasJustDroppedOnWorld(this);
    }
  });

  add.method('livelyOutlinersWorldMenu', function (evt) {
    var menu = new MenuMorph([], this);
    menu.addItem(["create new object", function(evt) {
      this.outlinerFor(reflect({})).grabMe(evt);
    }]);

    menu.addItem(["get the Global object", function(evt) {
      this.outlinerFor(reflect(Global)).grabMe(evt);
    }]);

    menu.addLine();

    // aaa - hack because I haven't managed to get WebDAV working on adamspitz.com yet
    if (! URL.source.hostname.include("adamspitz.com")) {

    menu.addItem(["file in module...", function(evt) {
      var filenames = new FileDirectory(lobby.transporter.module.urlForModuleDirectory()).filenames().select(function(n) {return n.endsWith(".js");});

      var modulesMenu = new MenuMorph(filenames.map(function(n) {return [n, function(evt) {
        var moduleName = n.substring(0, n.length - 3);
        MessageNotifierMorph.showIfErrorDuring(function() { lobby.transporter.module.fileIn(moduleName); }, evt);
      }];}), this);

      modulesMenu.openIn(this, evt.point());
    }.bind(this)]);

    }

    menu.addItem(["file out module...", function(evt) {
      var modulesMenu = new MenuMorph([], this);
      lobby.transporter.module.eachModule(function(m) {
        modulesMenu.addItem([m.name(), function(evt) {
          MessageNotifierMorph.showIfErrorDuring(function() { m.fileOut(); }, evt);
        }.bind(this)]);
      }.bind(this));
      modulesMenu.openIn(this, evt.point());
    }.bind(this)]);

    menu.addLine();

    menu.addItem(["clean up", function(evt) {
      this.cleanUpOutliners(evt);
    }.bind(this)]);

    return menu;
  });

});

thisModule.addSlots(transporter, function(add) {

    add.method('chooseOrCreateAModule', function (evt, targetMorph, menuCaption, callback) {
    var modulesMenu = new MenuMorph([], targetMorph);
    modulesMenu.addItem(["new module...", function(evt) {
      evt.hand.world().prompt("Module name?", function(name) {
        if (name) {
          if (lobby.modules[name]) {
            throw "There is already a module named " + name;
          }
          callback(lobby.transporter.module.named(name), evt);
        }
      });
    }]);
    modulesMenu.addLine();
    lobby.transporter.module.eachModule(function(m) {
      modulesMenu.addItem([m.name(), function(evt) {
        callback(m, evt);
      }]);
    });
    modulesMenu.openIn(targetMorph.world(), evt.point(), false, menuCaption);
  });

});

});
<|endoftext|>


<commit_before>import isArray from '../utils/is-array';
import hasOwnProp from '../utils/has-own-prop';
import isUndefined from '../utils/is-undefined';
import compareArrays from '../utils/compare-arrays';
import { deprecateSimple } from '../utils/deprecate';
import { mergeConfigs } from './set';
import { Locale } from './constructor';
import keys from '../utils/keys';

import { baseConfig } from './base-config';

// internal storage for locale config files
var locales = {};
var localeFamilies = {};
var globalLocale;

function normalizeLocale(key) {
    return key ? key.toLowerCase().replace('_', '-') : key;
}

// pick the locale from the array
// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
function chooseLocale(names) {
    var i = 0, j, next, locale, split;

    while (i < names.length) {
        split = normalizeLocale(names[i]).split('-');
        j = split.length;
        next = normalizeLocale(names[i + 1]);
        next = next ? next.split('-') : null;
        while (j > 0) {
            locale = loadLocale(split.slice(0, j).join('-'));
            if (locale) {
                return locale;
            }
            if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
                //the next array item is better than a shallower substring of this one
                break;
            }
            j--;
        }
        i++;
    }
    return null;
}

function loadLocale(name) {
    var oldLocale = null;
    // TODO: Find a better way to register and load all the locales in Node
    if (!locales[name] && (typeof module !== 'undefined') &&
            module && module.exports) {
        try {
            oldLocale = globalLocale._abbr;
            require('./locale/' + name);
            // because defineLocale currently also sets the global locale, we
            // want to undo that for lazy loaded locales
            getSetGlobalLocale(oldLocale);
        } catch (e) { }
    }
    return locales[name];
}

// This function will load locale and then set the global locale.  If
// no arguments are passed in, it will simply return the current global
// locale key.
export function getSetGlobalLocale (key, values) {
    var data;
    if (key) {
        if (isUndefined(values)) {
            data = getLocale(key);
        }
        else {
            data = defineLocale(key, values);
        }

        if (data) {
            // moment.duration._locale = moment._locale = data;
            globalLocale = data;
        }
    }

    return globalLocale._abbr;
}

export function defineLocale (name, config) {
    if (config !== null) {
        var parentConfig = baseConfig;
        config.abbr = name;
        if (locales[name] != null) {
            deprecateSimple('defineLocaleOverride',
                    'use moment.updateLocale(localeName, config) to change ' +
                    'an existing locale. moment.defineLocale(localeName, ' +
                    'config) should only be used for creating a new locale ' +
                    'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
            parentConfig = locales[name]._config;
        } else if (config.parentLocale != null) {
            if (locales[config.parentLocale] != null) {
                parentConfig = locales[config.parentLocale]._config;
            } else {
                if (!localeFamilies[config.parentLocale]) {
                    localeFamilies[config.parentLocale] = [];
                }
                localeFamilies[config.parentLocale].push({
                    name: name,
                    config: config
                });
                return null;
            }
        }
        locales[name] = new Locale(mergeConfigs(parentConfig, config));

        if (localeFamilies[name]) {
            localeFamilies[name].forEach(function (x) {
                defineLocale(x.name, x.config);
            });
        }

        // backwards compat for now: also set the locale
        // make sure we set the locale AFTER all child locales have been
        // created, so we won't end up with the child locale set.
        getSetGlobalLocale(name);

        return locales[name];
    } else {
        // useful for testing
        delete locales[name];
        return null;
    }
}

export function updateLocale(name, config) {
    if (config != null) {
        var locale, parentConfig = baseConfig;
        // MERGE
        if (locales[name] != null) {
            parentConfig = locales[name]._config;
        }
        config = mergeConfigs(parentConfig, config);
        locale = new Locale(config);
        locale.parentLocale = locales[name];
        locales[name] = locale;

        // backwards compat for now: also set the locale
        getSetGlobalLocale(name);
    } else {
        // pass null for config to unupdate, useful for tests
        if (locales[name] != null) {
            if (locales[name].parentLocale != null) {
                locales[name] = locales[name].parentLocale;
            } else if (locales[name] != null) {
                delete locales[name];
            }
        }
    }
    return locales[name];
}

// returns locale data
export function getLocale (key) {
    var locale;

    if (key && key._locale && key._locale._abbr) {
        key = key._locale._abbr;
    }

    if (!key) {
        return globalLocale;
    }

    if (!isArray(key)) {
        //short-circuit everything else
        locale = loadLocale(key);
        if (locale) {
            return locale;
        }
        key = [key];
    }

    return chooseLocale(key);
}

export function listLocales() {
    return keys(locales);
}
<commit_msg>Fix #4031 - Missing ES6 module during tree shaking<commit_after>import isArray from '../utils/is-array';
import hasOwnProp from '../utils/has-own-prop';
import isUndefined from '../utils/is-undefined';
import compareArrays from '../utils/compare-arrays';
import { deprecateSimple } from '../utils/deprecate';
import { mergeConfigs } from './set';
import { Locale } from './constructor';
import keys from '../utils/keys';

import { baseConfig } from './base-config';

// internal storage for locale config files
var locales = {};
var localeFamilies = {};
var globalLocale;

function normalizeLocale(key) {
    return key ? key.toLowerCase().replace('_', '-') : key;
}

// pick the locale from the array
// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
function chooseLocale(names) {
    var i = 0, j, next, locale, split;

    while (i < names.length) {
        split = normalizeLocale(names[i]).split('-');
        j = split.length;
        next = normalizeLocale(names[i + 1]);
        next = next ? next.split('-') : null;
        while (j > 0) {
            locale = loadLocale(split.slice(0, j).join('-'));
            if (locale) {
                return locale;
            }
            if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
                //the next array item is better than a shallower substring of this one
                break;
            }
            j--;
        }
        i++;
    }
    return null;
}

function loadLocale(name) {
    var oldLocale = null;
    // TODO: Find a better way to register and load all the locales in Node
    if (!locales[name] && (typeof module !== 'undefined') &&
            module && module.exports) {
        oldLocale = globalLocale._abbr;
        try {
            require('moment/locale/' + name);
        } catch (e) {
            // In the test environment, the external module 'moment'
            // can't be resolved because we're running inside it.
            // Fallback to using the old relative import
            try {
                require('./locale/' + name);
            } catch (e) { }
        }

        // because defineLocale currently also sets the global locale, we
        // want to undo that for lazy loaded locales
        getSetGlobalLocale(oldLocale);
    }
    return locales[name];
}

// This function will load locale and then set the global locale.  If
// no arguments are passed in, it will simply return the current global
// locale key.
export function getSetGlobalLocale (key, values) {
    var data;
    if (key) {
        if (isUndefined(values)) {
            data = getLocale(key);
        }
        else {
            data = defineLocale(key, values);
        }

        if (data) {
            // moment.duration._locale = moment._locale = data;
            globalLocale = data;
        }
    }

    return globalLocale._abbr;
}

export function defineLocale (name, config) {
    if (config !== null) {
        var parentConfig = baseConfig;
        config.abbr = name;
        if (locales[name] != null) {
            deprecateSimple('defineLocaleOverride',
                    'use moment.updateLocale(localeName, config) to change ' +
                    'an existing locale. moment.defineLocale(localeName, ' +
                    'config) should only be used for creating a new locale ' +
                    'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
            parentConfig = locales[name]._config;
        } else if (config.parentLocale != null) {
            if (locales[config.parentLocale] != null) {
                parentConfig = locales[config.parentLocale]._config;
            } else {
                if (!localeFamilies[config.parentLocale]) {
                    localeFamilies[config.parentLocale] = [];
                }
                localeFamilies[config.parentLocale].push({
                    name: name,
                    config: config
                });
                return null;
            }
        }
        locales[name] = new Locale(mergeConfigs(parentConfig, config));

        if (localeFamilies[name]) {
            localeFamilies[name].forEach(function (x) {
                defineLocale(x.name, x.config);
            });
        }

        // backwards compat for now: also set the locale
        // make sure we set the locale AFTER all child locales have been
        // created, so we won't end up with the child locale set.
        getSetGlobalLocale(name);

        return locales[name];
    } else {
        // useful for testing
        delete locales[name];
        return null;
    }
}

export function updateLocale(name, config) {
    if (config != null) {
        var locale, parentConfig = baseConfig;
        // MERGE
        if (locales[name] != null) {
            parentConfig = locales[name]._config;
        }
        config = mergeConfigs(parentConfig, config);
        locale = new Locale(config);
        locale.parentLocale = locales[name];
        locales[name] = locale;

        // backwards compat for now: also set the locale
        getSetGlobalLocale(name);
    } else {
        // pass null for config to unupdate, useful for tests
        if (locales[name] != null) {
            if (locales[name].parentLocale != null) {
                locales[name] = locales[name].parentLocale;
            } else if (locales[name] != null) {
                delete locales[name];
            }
        }
    }
    return locales[name];
}

// returns locale data
export function getLocale (key) {
    var locale;

    if (key && key._locale && key._locale._abbr) {
        key = key._locale._abbr;
    }

    if (!key) {
        return globalLocale;
    }

    if (!isArray(key)) {
        //short-circuit everything else
        locale = loadLocale(key);
        if (locale) {
            return locale;
        }
        key = [key];
    }

    return chooseLocale(key);
}

export function listLocales() {
    return keys(locales);
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Group from 'react-group';
import Arguments from 'rsg-components/Arguments';
import Code from 'rsg-components/Code';
import JsDoc from 'rsg-components/JsDoc';
import Markdown from 'rsg-components/Markdown';
import Name from 'rsg-components/Name';
import Styled from 'rsg-components/Styled';
import map from 'lodash/map';
import { unquote, getType, showSpaces } from './util';

const styles = ({ space, color, fontFamily, fontSize }) => ({
	table: {
		width: '100%',
		borderCollapse: 'collapse',
	},
	tableHead: {
		borderBottom: [[1, color.border, 'solid']],
	},
	cell: {
		color: color.base,
		paddingRight: space[2],
		paddingTop: space[1],
		verticalAlign: 'top',
		fontFamily: fontFamily.base,
		fontSize: fontSize.small,
	},
	cellHeading: {
		color: color.base,
		paddingRight: space[2],
		paddingBottom: space[1],
		textAlign: 'left',
		fontFamily: fontFamily.base,
		fontWeight: 'bold',
		fontSize: fontSize.small,
	},
	cellDesc: {
		color: color.base,
		width: '99%',
		paddingLeft: space[2],
		paddingRight: 0,
	},
	required: {
		fontFamily: fontFamily.base,
		fontSize: fontSize.small,
		color: color.light,
	},
	type: {
		fontSize: fontSize.small,
		color: color.type,
	},
	function: {
		fontFamily: fontFamily.base,
		fontSize: fontSize.small,
		color: color.light,
		borderBottom: [[1, 'dotted', color.lightest]],
	},
	heading: {
		marginBottom: 3,
		fontWeight: 'bold',
		fontSize: 13,
	},
	para: {
		marginBottom: 15,
		fontSize: 13,
	},
});

function renderType(type) {
	if (!type) {
		return 'unknown';
	}

	const { name } = type;

	switch (name) {
		case 'arrayOf':
			return `${type.value.name}[]`;
		case 'objectOf':
			return `{${renderType(type.value)}}`;
		case 'instanceOf':
			return type.value;
		default:
			return name;
	}
}

function renderEnum(prop) {
	if (!Array.isArray(getType(prop).value)) {
		return {getType(prop).value};
	}

	const values = getType(prop).value.map(({ value }) => (
		{showSpaces(unquote(value))}
	));
	return (
		One of: <Group separator=", " inline>{values}</Group>
	);
}

export function PropsRenderer({ classes, props }) {
	function renderRow(prop, name) {
		const deprecated = !!(prop.tags && prop.tags.deprecated);
		return (

					<Name name={name} deprecated={deprecated} />

					{renderType(getType(prop))}

				{renderDefault(prop)}
				{renderDescription(prop)}

		);
	}

	function renderDefault(prop) {
		if (prop.required) {
			return (
				Required
			);
		}
		else if (prop.defaultValue) {
			if (prop.type && prop.type.name === 'func') {
				return (
					Function
				);
			}

			return (
				{showSpaces(unquote(prop.defaultValue.value))}
			);
		}
		return '';
	}

	function renderDescription(prop) {
		const { description, tags = {} } = prop;
		const extra = renderExtra(prop);
		const args = [...tags.arg || [], ...tags.argument || [], ...tags.param || []];
		return (

				{description && <Markdown text={description} />}
				{extra &&
{extra}
}
				<JsDoc {...tags} />
				{args.length > 0 && (

Arguments

						<Arguments args={args} />

				)}

		);
	}

	function renderExtra(prop) {
		const type = getType(prop);

		if (!type) {
			return null;
		}
		switch (type.name) {
			case 'enum':
				return renderEnum(prop);
			case 'union':
				return renderUnion(prop);
			case 'shape':
				return renderShape(prop.type.value);
			case 'arrayOf':
				if (type.value.name === 'shape') {
					return renderShape(prop.type.value.value);
				}
				return null;
			case 'objectOf':
				if (type.value.name === 'shape') {
					return renderShape(prop.type.value.value);
				}
				return null;
			default:
				return null;
		}
	}

	function renderUnion(prop) {
		if (!Array.isArray(getType(prop).value)) {
			return {getType(prop).value};
		}

		const values = getType(prop).value.map(value => (
			{renderType(value)}
		));
		return (
			One of type: <Group separator=", " inline>{values}</Group>
		);
	}

	function renderShape(props) {
		const rows = [];
		for (const name in props) {
			const prop = props[name];
			const defaultValue = renderDefault(prop);
			const description = prop.description;
			rows.push(

					{name}{': '}
					{renderType(prop)}
					{defaultValue && ' — '}{defaultValue}
					{description && ' — '}
					{description && <Markdown text={description} inline />}

			);
		}
		return rows;
	}

	return (

					Name
					Type
					Default
					Description

				{map(props, renderRow)}

	);
}

PropsRenderer.propTypes = {
	classes: PropTypes.object.isRequired,
	props: PropTypes.object.isRequired,
};

export default Styled(styles)(PropsRenderer);
<commit_msg>Refactor: Extract spacing and fonts to theme (#432)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Group from 'react-group';
import Arguments from 'rsg-components/Arguments';
import Code from 'rsg-components/Code';
import JsDoc from 'rsg-components/JsDoc';
import Markdown from 'rsg-components/Markdown';
import Name from 'rsg-components/Name';
import Styled from 'rsg-components/Styled';
import map from 'lodash/map';
import { unquote, getType, showSpaces } from './util';

const styles = ({ space, color, fontFamily, fontSize }) => ({
	table: {
		width: '100%',
		borderCollapse: 'collapse',
	},
	tableHead: {
		borderBottom: [[1, color.border, 'solid']],
	},
	cell: {
		color: color.base,
		paddingRight: space[2],
		paddingTop: space[1],
		verticalAlign: 'top',
		fontFamily: fontFamily.base,
		fontSize: fontSize.small,
	},
	cellHeading: {
		color: color.base,
		paddingRight: space[2],
		paddingBottom: space[1],
		textAlign: 'left',
		fontFamily: fontFamily.base,
		fontWeight: 'bold',
		fontSize: fontSize.small,
	},
	cellDesc: {
		color: color.base,
		width: '99%',
		paddingLeft: space[2],
		paddingRight: 0,
	},
	required: {
		fontFamily: fontFamily.base,
		fontSize: fontSize.small,
		color: color.light,
	},
	name: {
		fontSize: fontSize.small,
		color: color.name,
	},
	type: {
		fontSize: fontSize.small,
		color: color.type,
	},
	function: {
		fontFamily: fontFamily.base,
		fontSize: fontSize.small,
		color: color.light,
		borderBottom: [[1, 'dotted', color.lightest]],
	},
	heading: {
		marginBottom: 3,
		fontWeight: 'bold',
		fontSize: 13,
	},
	para: {
		marginBottom: 15,
		fontSize: 13,
	},
});

function renderType(type) {
	if (!type) {
		return 'unknown';
	}

	const { name } = type;

	switch (name) {
		case 'arrayOf':
			return `${type.value.name}[]`;
		case 'objectOf':
			return `{${renderType(type.value)}}`;
		case 'instanceOf':
			return type.value;
		default:
			return name;
	}
}

function renderEnum(prop) {
	if (!Array.isArray(getType(prop).value)) {
		return {getType(prop).value};
	}

	const values = getType(prop).value.map(({ value }) => (
		{showSpaces(unquote(value))}
	));
	return (
		One of: <Group separator=", " inline>{values}</Group>
	);
}

export function PropsRenderer({ classes, props }) {
	function renderRow(prop, name) {
		const deprecated = !!(prop.tags && prop.tags.deprecated);
		return (

					<Name name={name} deprecated={deprecated} />

					{renderType(getType(prop))}

				{renderDefault(prop)}
				{renderDescription(prop)}

		);
	}

	function renderDefault(prop) {
		if (prop.required) {
			return (
				Required
			);
		}
		else if (prop.defaultValue) {
			if (prop.type && prop.type.name === 'func') {
				return (
					Function
				);
			}

			return (
				{showSpaces(unquote(prop.defaultValue.value))}
			);
		}
		return '';
	}

	function renderDescription(prop) {
		const { description, tags = {} } = prop;
		const extra = renderExtra(prop);
		const args = [...tags.arg || [], ...tags.argument || [], ...tags.param || []];
		return (

				{description && <Markdown text={description} />}
				{extra &&
{extra}
}
				<JsDoc {...tags} />
				{args.length > 0 && (

Arguments

						<Arguments args={args} />

				)}

		);
	}

	function renderExtra(prop) {
		const type = getType(prop);

		if (!type) {
			return null;
		}
		switch (type.name) {
			case 'enum':
				return renderEnum(prop);
			case 'union':
				return renderUnion(prop);
			case 'shape':
				return renderShape(prop.type.value);
			case 'arrayOf':
				if (type.value.name === 'shape') {
					return renderShape(prop.type.value.value);
				}
				return null;
			case 'objectOf':
				if (type.value.name === 'shape') {
					return renderShape(prop.type.value.value);
				}
				return null;
			default:
				return null;
		}
	}

	function renderUnion(prop) {
		if (!Array.isArray(getType(prop).value)) {
			return {getType(prop).value};
		}

		const values = getType(prop).value.map(value => (
			{renderType(value)}
		));
		return (
			One of type: <Group separator=", " inline>{values}</Group>
		);
	}

	function renderShape(props) {
		const rows = [];
		for (const name in props) {
			const prop = props[name];
			const defaultValue = renderDefault(prop);
			const description = prop.description;
			rows.push(

					{name}{': '}
					{renderType(prop)}
					{defaultValue && ' — '}{defaultValue}
					{description && ' — '}
					{description && <Markdown text={description} inline />}

			);
		}
		return rows;
	}

	return (

					Name
					Type
					Default
					Description

				{map(props, renderRow)}

	);
}

PropsRenderer.propTypes = {
	classes: PropTypes.object.isRequired,
	props: PropTypes.object.isRequired,
};

export default Styled(styles)(PropsRenderer);
<|endoftext|>


<commit_before>(function() {
  "use strict";
  window.GOVUK = window.GOVUK || {};

  // For usage and initialisation see:
  // https://github.com/alphagov/govuk_frontend_toolkit/blob/master/docs/analytics.md#create-an-analytics-tracker

  var Tracker = function(config) {
    this.trackers = [];
    if (typeof config.universalId != 'undefined') {
      this.trackers.push(new GOVUK.GoogleAnalyticsUniversalTracker(config.universalId, config.cookieDomain));
    }
  };

  Tracker.load = function() {
    GOVUK.GoogleAnalyticsUniversalTracker.load();
  };

  Tracker.prototype.trackPageview = function(path, title) {
    for (var i=0; i < this.trackers.length; i++) {
      this.trackers[i].trackPageview(path, title);
    }
  };

  /*
    https://developers.google.com/analytics/devguides/collection/analyticsjs/events
    options.label – Useful for categorizing events (eg nav buttons)
    options.value – Values must be non-negative. Useful to pass counts
    options.nonInteraction – Prevent event from impacting bounce rate
  */
  Tracker.prototype.trackEvent = function(category, action, options) {
    for (var i=0; i < this.trackers.length; i++) {
      this.trackers[i].trackEvent(category, action, options);
    }
  };

  Tracker.prototype.trackShare = function(network) {
    var target = location.pathname;
    for (var i=0; i < this.trackers.length; i++) {
      this.trackers[i].trackSocial(network, 'share', target);
    }
  };

  /*
    Assumes that the index of the dimension is the same for both classic and universal.
    Check this for your app before using this
   */
  Tracker.prototype.setDimension = function(index, value) {
    for (var i=0; i < this.trackers.length; i++) {
      this.trackers[i].setDimension(index, value);
    }
  };

  /*
   Add a beacon to track a page in another GA account on another domain.
   */
  Tracker.prototype.addLinkedTrackerDomain = function(trackerId, name, domain) {
    for (var i=0; i < this.trackers.length; i++) {
      this.trackers[i].addLinkedTrackerDomain(trackerId, name, domain);
    }
  };

  GOVUK.Tracker = Tracker;
})();
<commit_msg>Create sendToTrackers helper<commit_after>(function() {
  "use strict";
  window.GOVUK = window.GOVUK || {};

  // For usage and initialisation see:
  // https://github.com/alphagov/govuk_frontend_toolkit/blob/master/docs/analytics.md#create-an-analytics-tracker

  var Tracker = function(config) {
    this.trackers = [];
    if (typeof config.universalId != 'undefined') {
      this.trackers.push(new GOVUK.GoogleAnalyticsUniversalTracker(config.universalId, config.cookieDomain));
    }
  };

  Tracker.prototype.sendToTrackers = function(method, args) {
    for (var i = 0, l = this.trackers.length; i < l; i++) {
      var tracker = this.trackers[i],
          fn = tracker[method];

      if (typeof fn === "function") {
        fn.apply(tracker, args);
      }
    }
  };

  Tracker.load = function() {
    GOVUK.GoogleAnalyticsUniversalTracker.load();
  };

  Tracker.prototype.trackPageview = function(path, title) {
    this.sendToTrackers('trackPageview', arguments);
  };

  /*
    https://developers.google.com/analytics/devguides/collection/analyticsjs/events
    options.label – Useful for categorizing events (eg nav buttons)
    options.value – Values must be non-negative. Useful to pass counts
    options.nonInteraction – Prevent event from impacting bounce rate
  */
  Tracker.prototype.trackEvent = function(category, action, options) {
    this.sendToTrackers('trackEvent', arguments);
  };

  Tracker.prototype.trackShare = function(network) {
    this.sendToTrackers('trackSocial', [network, 'share', location.pathname]);
  };

  /*
    Assumes that the index of the dimension is the same for both classic and universal.
    Check this for your app before using this
   */
  Tracker.prototype.setDimension = function(index, value) {
    this.sendToTrackers('setDimension', arguments);
  };

  /*
   Add a beacon to track a page in another GA account on another domain.
   */
  Tracker.prototype.addLinkedTrackerDomain = function(trackerId, name, domain) {
    this.sendToTrackers('addLinkedTrackerDomain', arguments);
  };

  GOVUK.Tracker = Tracker;
})();
<|endoftext|>


<commit_before>
exports = module.exports = lifecycle
exports.cmd = cmd

var log = require("./log")
  , exec = require("./exec")
  , npm = require("../../npm")
  , path = require("path")
  , readJson = require("./read-json")
  , fs = require("./graceful-fs")
  , chain = require("./chain")
  , asyncMap = require("./async-map")
  , constants = require("constants")

function lifecycle (pkg, stage, wd, cb) {
  while (pkg && pkg._data) pkg = pkg._data
  if (typeof wd === "function") cb = wd , wd = null
  if (!pkg) return cb(new Error("Invalid package data"))
  log(pkg._id, stage)

  wd = validWd(wd || path.join(npm.dir, pkg.name, pkg.version, "package"))

  // set the env variables, then run scripts as a child process.
  var env = makeEnv(pkg)
  env.npm_lifecycle_event = stage
  if (!npm.config.get("unsafe-perm")) env.TMPDIR = wd
  var pkgDir = path.join(npm.dir, pkg.name, pkg.version)

  if (wd.indexOf(npm.dir) !== 0) {
    pkg._link = true
  }

  if (pkg.version.match(/^9999.0.0-LINK-[0-9a-f]{8}$/)
      && !pkg.hasOwnProperty("_link")) {
    return checkForLink(pkg, function (er, l) {
      pkg._link = l
      lifecycle_(pkg, stage, wd, pkgDir, env, cb)
    })
  }
  lifecycle_(pkg, stage, wd, pkgDir, env, cb)
}

function checkForLink (pkg, cb) {
  var f = path.join(npm.dir, pkg.name, pkg.version, "package")
  fs.lstat(f, function (er, s) {
    cb(null, !(er || !s.isSymbolicLink()))
  })
}

function lifecycle_ (pkg, stage, wd, pkgDir, env, cb) {
  env.PATH = path.join(pkgDir, "node_modules", ".bin")
           + ":" + path.join(pkgDir, "dep-bin")
           + (env.PATH ? ":" + env.PATH : "")

  var packageLifecycle = pkg.scripts && (stage in pkg.scripts)

  if (packageLifecycle) {
    // define this here so it's available to all scripts.
    env.npm_lifecycle_script = pkg.scripts[stage]
  }

  chain
    ( [addDepsToEnv, pkg, env, wd]
    , packageLifecycle && [runPackageLifecycle, pkg, env, wd]
    , [runHookLifecycle, pkg, env, wd]
    , cb
    )
}

// attempt to resolve installed deps, and attach to env.
// if it fails, then just leave it blank.
function addDepsToEnv (pkg, env, wd, cb_) {
  if (pkg._resolvedDeps && pkg._bundledDeps) {
    return cb(null, pkg._resolvedDeps, pkg._bundledDeps)
  }
  else readDeps(pkg, wd, cb)
  function cb (er, deps, bundled) {
    if (er) return cb_(er)
    pkg._bundledDeps = bundled
    var bundledir = path.join(wd, "node_modules", npm.config.get('dotnpm'))
    deps.forEach(add(npm.dir, "npm_dependency_"))
    bundled.forEach(add(bundledir, "npm_dependency_"))
    bundled.forEach(add(bundledir, "npm_bundle_"))
    function add (root, prefix) { return function (d) {
      env[prefix+(d.name)] = d.version
      env[prefix+(d.name)+"_path"] =
        path.join(root, d.name, d.version, "package")
    }}
    cb_()
  }
}

function readDeps (pkg, wd, cb) {
  var pkgdir = path.join(npm.dir, pkg.name, pkg.version)
    , depdir = path.join(pkgdir, "dependson")
    , bundledir = path.join(wd, "node_modules", npm.config.get('dotnpm'))
  fs.readdir(depdir, function (er, dn) {
    done(er ? [] : dn.map(function (dn) {
      dn = dn.split("@")
      var n = dn.shift()
        , v = dn.join("@")
      return {name:n, version:v}
    }))
  }, cb)
  fs.readdir(bundledir, function (er, bn) {
    if (er) return done(null, [])
    asyncMap(bn || [], function (bn, cb) {
      fs.readlink(path.join(bundledir, bn, "active"), function (er, a) {
        if (er || !a) return cb()
        cb(null, {name: bn, version: a.replace(/^\.\//, "")})
      })
    }, function (er, b) {
      return done(null, b || [])
    })
  })
  var deps
    , bundled
  function done (d, b) {
    deps = deps || d
    bundled = bundled || b
    if (deps && bundled) {
      return cb(null, deps, bundled)
    }
  }
}

function validWd (d) {
  var cwd = process.cwd()
  while (d) {
    try {
      process.chdir(d)
      break
    } catch (ex) {
      d = path.dirname(d)
    }
  }
  process.chdir(cwd)
  return d
}

function runPackageLifecycle (pkg, env, wd, cb) {
  // run package lifecycle scripts in the package root, or the nearest parent.
  var stage = env.npm_lifecycle_event
    , up = npm.config.get("unsafe-perm")
    , user = pkg._link || up ? null : npm.config.get("user")
    , group = pkg._link || up ? null : npm.config.get("group")
    , cmd = env.npm_lifecycle_script

  log.verbose(up, "unsafe-perm in lifecycle")
  exec( "sh", ["-c", cmd], env, true, wd
      , user, group
      , function (er, code, stdout, stderr) {
    if (er && !npm.ROLLBACK) {
      log("Failed to exec "+stage+" script", pkg._id)
      er.message = pkg._id + " "
                 + stage + ": `" + env.npm_lifecycle_script+"`\n"
                 + er.message
      if (er.errno !== constants.EPERM) {
        er.errno = npm.ELIFECYCLE
      }
      er.pkgid = pkg._id
      er.stage = stage
      er.script = env.npm_lifecycle_script
      er.pkgname = pkg.name
      return cb(er)
    } else if (er) {
      log.error(er, pkg._id+"."+stage)
      log.error("failed, but continuing anyway", pkg._id+"."+stage)
      return cb()
    }
    cb(er)
  })
}

function runHookLifecycle (pkg, env, wd, cb) {
  // check for a hook script, run if present.
  var stage = env.npm_lifecycle_event
    , up = npm.config.get("unsafe-perm")
    , hook = path.join(npm.dir, ".hooks", stage)
    , user = pkg._link || up ? null : npm.config.get("user")
    , group = pkg._link || up ? null : npm.config.get("group")
    , cmd = hook

  fs.stat(hook, function (er) {
    if (er) return cb()

    exec( "sh", ["-c", cmd], env, true, wd
        , user, group
        , function (er) {
      if (er) {
        er.message += "\nFailed to exec "+stage+" hook script"
        log(er, pkg._id)
      }
      if (npm.ROLLBACK) return cb()
      cb(er)
    })
  })
}

function makeEnv (data, prefix, env) {
  prefix = prefix || "npm_package_"
  if (!env) {
    env = {}
    for (var i in process.env) env[i] = process.env[i]
  } else if (!data.hasOwnProperty("_lifecycleEnv")) {
    Object.defineProperty(data, "_lifecycleEnv",
      { value : env
      , enumerable : false
      })
  }
  for (var i in data) if (i.charAt(0) !== "_") {
    var envKey = (prefix+i).replace(/[^a-zA-Z0-9_]/g, '_')
    if (data[i] && typeof(data[i]) === "object") {
      env[envKey] = JSON.stringify(data[i])
      makeEnv(data[i], envKey+"_", env)
    } else {
      env[envKey] = String(data[i])
    }
  }
  if (prefix !== "npm_package_") return env
  prefix = "npm_config_"
  var conf = npm.config.get()
    , pkgConfig = {}
    , pkgVerConfig = {}
    , namePref = data.name + ":"
    , verPref = data.name + "@" + data.version + ":"
  for (var i in conf) if (i.charAt(0) !== "_") {
    var value = String(conf[i])
    if (i.indexOf(namePref) === 0) {
      pkgConfig[ i.substr(namePref.length) ] = value
    } else if (i.indexOf(verPref) === 0) {
      pkgVerConfig[ i.substr(verPref.length) ] = value
    }
    var envKey = (prefix+i)
    env[envKey] = value
  }
  prefix = "npm_package_config_"
  ;[pkgConfig, pkgVerConfig].forEach(function (conf) {
    for (var i in conf) {
      var envKey = (prefix+i)
      env[envKey] = conf[i]
    }
  })
  return env
}

function cmd (stage) {
  function CMD (args, cb) {
    chain(args.map(function (p) {
      return [npm.commands, "run-script", [p, stage]]
    }).concat(cb))
  }
  CMD.usage = "npm "+stage+" <name>[@<version>] [<name>[@<version>] ...]"
  CMD.completion = function (args, index, cb) {
    var installedPkgs = require("./utils/completion/installed-packages")
    installedPkgs(args, index, true, true, cb)
  }
  return CMD
}
<commit_msg>Don't try to chdir when it'll fail<commit_after>
exports = module.exports = lifecycle
exports.cmd = cmd

var log = require("./log")
  , exec = require("./exec")
  , npm = require("../../npm")
  , path = require("path")
  , readJson = require("./read-json")
  , fs = require("./graceful-fs")
  , chain = require("./chain")
  , asyncMap = require("./async-map")
  , constants = require("constants")

function lifecycle (pkg, stage, wd, cb) {
  while (pkg && pkg._data) pkg = pkg._data
  if (typeof wd === "function") cb = wd , wd = null
  if (!pkg) return cb(new Error("Invalid package data"))
  log(pkg._id, stage)

  var pkgdir = path.join(npm.dir, pkg.name, pkg.version, "package")
  wd = validWd(wd || pkgdir)
  if (wd !== pkgdir && !npm.config.get("unsafe-perm")) {
    log.warn(pkg._id, "Skipping "+stage+" script, since it'll fail.")
    return cb()
  }

  // set the env variables, then run scripts as a child process.
  var env = makeEnv(pkg)
  env.npm_lifecycle_event = stage
  if (!npm.config.get("unsafe-perm")) env.TMPDIR = wd
  var pkgDir = path.join(npm.dir, pkg.name, pkg.version)

  if (wd.indexOf(npm.dir) !== 0) {
    pkg._link = true
  }

  if (pkg.version.match(/^9999.0.0-LINK-[0-9a-f]{8}$/)
      && !pkg.hasOwnProperty("_link")) {
    return checkForLink(pkg, function (er, l) {
      pkg._link = l
      lifecycle_(pkg, stage, wd, pkgDir, env, cb)
    })
  }
  lifecycle_(pkg, stage, wd, pkgDir, env, cb)
}

function checkForLink (pkg, cb) {
  var f = path.join(npm.dir, pkg.name, pkg.version, "package")
  fs.lstat(f, function (er, s) {
    cb(null, !(er || !s.isSymbolicLink()))
  })
}

function lifecycle_ (pkg, stage, wd, pkgDir, env, cb) {
  env.PATH = path.join(pkgDir, "node_modules", ".bin")
           + ":" + path.join(pkgDir, "dep-bin")
           + (env.PATH ? ":" + env.PATH : "")

  var packageLifecycle = pkg.scripts && (stage in pkg.scripts)

  if (packageLifecycle) {
    // define this here so it's available to all scripts.
    env.npm_lifecycle_script = pkg.scripts[stage]
  }

  chain
    ( [addDepsToEnv, pkg, env, wd]
    , packageLifecycle && [runPackageLifecycle, pkg, env, wd]
    , [runHookLifecycle, pkg, env, wd]
    , cb
    )
}

// attempt to resolve installed deps, and attach to env.
// if it fails, then just leave it blank.
function addDepsToEnv (pkg, env, wd, cb_) {
  if (pkg._resolvedDeps && pkg._bundledDeps) {
    return cb(null, pkg._resolvedDeps, pkg._bundledDeps)
  }
  else readDeps(pkg, wd, cb)
  function cb (er, deps, bundled) {
    if (er) return cb_(er)
    pkg._bundledDeps = bundled
    var bundledir = path.join(wd, "node_modules", npm.config.get('dotnpm'))
    deps.forEach(add(npm.dir, "npm_dependency_"))
    bundled.forEach(add(bundledir, "npm_dependency_"))
    bundled.forEach(add(bundledir, "npm_bundle_"))
    function add (root, prefix) { return function (d) {
      env[prefix+(d.name)] = d.version
      env[prefix+(d.name)+"_path"] =
        path.join(root, d.name, d.version, "package")
    }}
    cb_()
  }
}

function readDeps (pkg, wd, cb) {
  var pkgdir = path.join(npm.dir, pkg.name, pkg.version)
    , depdir = path.join(pkgdir, "dependson")
    , bundledir = path.join(wd, "node_modules", npm.config.get('dotnpm'))
  fs.readdir(depdir, function (er, dn) {
    done(er ? [] : dn.map(function (dn) {
      dn = dn.split("@")
      var n = dn.shift()
        , v = dn.join("@")
      return {name:n, version:v}
    }))
  }, cb)
  fs.readdir(bundledir, function (er, bn) {
    if (er) return done(null, [])
    asyncMap(bn || [], function (bn, cb) {
      fs.readlink(path.join(bundledir, bn, "active"), function (er, a) {
        if (er || !a) return cb()
        cb(null, {name: bn, version: a.replace(/^\.\//, "")})
      })
    }, function (er, b) {
      return done(null, b || [])
    })
  })
  var deps
    , bundled
  function done (d, b) {
    deps = deps || d
    bundled = bundled || b
    if (deps && bundled) {
      return cb(null, deps, bundled)
    }
  }
}

function validWd (d) {
  var cwd = process.cwd()
  while (d) {
    try {
      process.chdir(d)
      break
    } catch (ex) {
      d = path.dirname(d)
    }
  }
  process.chdir(cwd)
  return d
}

function runPackageLifecycle (pkg, env, wd, cb) {
  // run package lifecycle scripts in the package root, or the nearest parent.
  var stage = env.npm_lifecycle_event
    , up = npm.config.get("unsafe-perm")
    , user = pkg._link || up ? null : npm.config.get("user")
    , group = pkg._link || up ? null : npm.config.get("group")
    , cmd = env.npm_lifecycle_script
    , cwd = process.cwd()

  log.verbose(up, "unsafe-perm in lifecycle")
  process.chdir(wd)
  exec( "sh", ["-c", cmd], env, true, wd
      , user, group
      , function (er, code, stdout, stderr) {
    process.chdir(cwd)
    if (er && !npm.ROLLBACK) {
      log("Failed to exec "+stage+" script", pkg._id)
      er.message = pkg._id + " "
                 + stage + ": `" + env.npm_lifecycle_script+"`\n"
                 + er.message
      if (er.errno !== constants.EPERM) {
        er.errno = npm.ELIFECYCLE
      }
      er.pkgid = pkg._id
      er.stage = stage
      er.script = env.npm_lifecycle_script
      er.pkgname = pkg.name
      return cb(er)
    } else if (er) {
      log.error(er, pkg._id+"."+stage)
      log.error("failed, but continuing anyway", pkg._id+"."+stage)
      return cb()
    }
    cb(er)
  })
}

function runHookLifecycle (pkg, env, wd, cb) {
  // check for a hook script, run if present.
  var stage = env.npm_lifecycle_event
    , up = npm.config.get("unsafe-perm")
    , hook = path.join(npm.dir, ".hooks", stage)
    , user = pkg._link || up ? null : npm.config.get("user")
    , group = pkg._link || up ? null : npm.config.get("group")
    , cmd = hook

  fs.stat(hook, function (er) {
    if (er) return cb()

    var cwd = process.cwd()
    process.chdir(wd)
    exec( "sh", ["-c", cmd], env, true, wd
        , user, group
        , function (er) {
      process.chdir(cwd)
      if (er) {
        er.message += "\nFailed to exec "+stage+" hook script"
        log(er, pkg._id)
      }
      if (npm.ROLLBACK) return cb()
      cb(er)
    })
  })
}

function makeEnv (data, prefix, env) {
  prefix = prefix || "npm_package_"
  if (!env) {
    env = {}
    for (var i in process.env) env[i] = process.env[i]
  } else if (!data.hasOwnProperty("_lifecycleEnv")) {
    Object.defineProperty(data, "_lifecycleEnv",
      { value : env
      , enumerable : false
      })
  }
  for (var i in data) if (i.charAt(0) !== "_") {
    var envKey = (prefix+i).replace(/[^a-zA-Z0-9_]/g, '_')
    if (data[i] && typeof(data[i]) === "object") {
      env[envKey] = JSON.stringify(data[i])
      makeEnv(data[i], envKey+"_", env)
    } else {
      env[envKey] = String(data[i])
    }
  }
  if (prefix !== "npm_package_") return env
  prefix = "npm_config_"
  var conf = npm.config.get()
    , pkgConfig = {}
    , pkgVerConfig = {}
    , namePref = data.name + ":"
    , verPref = data.name + "@" + data.version + ":"
  for (var i in conf) if (i.charAt(0) !== "_") {
    var value = String(conf[i])
    if (i.indexOf(namePref) === 0) {
      pkgConfig[ i.substr(namePref.length) ] = value
    } else if (i.indexOf(verPref) === 0) {
      pkgVerConfig[ i.substr(verPref.length) ] = value
    }
    var envKey = (prefix+i)
    env[envKey] = value
  }
  prefix = "npm_package_config_"
  ;[pkgConfig, pkgVerConfig].forEach(function (conf) {
    for (var i in conf) {
      var envKey = (prefix+i)
      env[envKey] = conf[i]
    }
  })
  return env
}

function cmd (stage) {
  function CMD (args, cb) {
    chain(args.map(function (p) {
      return [npm.commands, "run-script", [p, stage]]
    }).concat(cb))
  }
  CMD.usage = "npm "+stage+" <name>[@<version>] [<name>[@<version>] ...]"
  CMD.completion = function (args, index, cb) {
    var installedPkgs = require("./utils/completion/installed-packages")
    installedPkgs(args, index, true, true, cb)
  }
  return CMD
}
<|endoftext|>


<commit_before>import React, { Component } from "react";
import Typography from "@material-ui/core/Typography";
import { withStyles } from "@material-ui/core/styles";
import VegaLite from "react-vega-lite";
import { Handler } from "vega-tooltip";

import "./HistogramFacet.css";
import * as Style from "libs/style";
import FacetHeader from "components/facets/FacetHeader";

const styles = {
  histogramFacet: {
    ...Style.elements.card,
    margin: "2%",
    paddingBottom: "8px",
    // Grid is defined in TextFacet so facet value counts can appear on right,
    // in addition to total facet value count.
    // Also define here to be consistent.
    display: "grid",
    gridTemplateColumns: "auto 50px",
    maxHeight: "400px",
    overflowY: "auto"
  },
  vega: {
    gridColumn: "1 / 3",
    textAlign: "center"
  }
};

const baseSpec = {
  $schema: "https://vega.github.io/schema/vega-lite/v3.json",
  mark: "bar",
  encoding: {
    color: {
      field: "dimmed",
      type: "nominal",
      scale: {
        // First color is default bar color. Second color for unselected bars.
        range: ["#707986", "#cccfd4"]
      },
      legend: null
    },
    tooltip: {
      field: "text",
      type: "nominal"
    },
    x: {},
    y: {},
    // opacity is needed for creating transparent bars.
    opacity: {
      field: "opaque",
      type: "nominal",
      scale: {
        range: [0, 1]
      },
      legend: null
    }
  }
};

const facetValueCountAxis = {
  field: "count",
  type: "quantitative",
  title: "",
  stack: null,
  axis: {
    labelColor: "#000000de",
    labelFont: "Lato",
    labelFontSize: 11
  },
  scale: {
    rangeStep: 20
  }
};

function isCategorical(facet) {
  return (
    facet.es_field_type == "text" || facet.es_field_type == "samples_overview"
  );
}

class HistogramFacet extends Component {
  constructor(props) {
    super(props);
    this.isValueDimmed = this.isValueDimmed.bind(this);
    this.onNewView = this.onNewView.bind(this);
    this.onClick = this.onClick.bind(this);
  }

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

    const spec = Object.assign({}, baseSpec);
    let facetValueNames = this.props.facet.values.map(v => v.name);
    if (!isCategorical(this.props.facet)) {
      // For numeric facets, higher numbers should be higher on the y-axis
      facetValueNames.reverse();
    }
    const facetValueNameAxis = {
      field: "facet_value",
      type: "nominal",
      title: null,
      sort: facetValueNames,
      axis: {
        labelColor: "#000000de",
        labelFont: "Lato",
        labelFontSize: 11,
        labelLimit: 120
      },
      scale: {
        paddingInner: 0.02,
        rangeStep: 20
      }
    };
    // Make bars horizontal, to allow for more space for facet value names for
    // categorical facets.
    spec.encoding.x = facetValueCountAxis;
    spec.encoding.y = facetValueNameAxis;

    const data = {
      values: this.props.facet.values.map(v => {
        return {
          facet_value: v.name,
          count: v.count,
          dimmed: this.isValueDimmed(v),
          text: `${v.name}: ${v.count}`,
          opaque: true
        };
      })
    };

    // Create transparent bar that extends the entire length of the cart. This
    // makes tooltip/selection easier for facet values that have very low count.
    const maxFacetValue = Math.max(...data.values.map(v => v.count));
    data.values = data.values.concat(
      data.values.map(v => {
        const invisible = Object.assign({}, v);
        invisible.opaque = false;
        invisible.count = maxFacetValue;
        return invisible;
      })
    );

    return (

        <FacetHeader
          facet={this.props.facet}
          selectedValues={this.props.selectedValues}
        />

          <VegaLite
            spec={spec}
            data={data}
            tooltip={new Handler().call}
            onNewView={this.onNewView}
          />

    );
  }

  isValueDimmed(facetValue) {
    return (
      this.props.selectedValues != null &&
      this.props.selectedValues.length > 0 &&
      !this.props.selectedValues.includes(facetValue.name)
    );
  }

  onClick(event, item) {
    // Ignore clicks which are not located on histogram
    // bars.
    if (item && item.datum) {
      // facetValue is a string, eg "female"
      const facetValue = item.datum.facet_value;
      let isSelected;
      // this.props.selectedValues contains what was selected before the click.
      // isSelected contains if facet value was selected after the click.
      if (
        this.props.selectedValues != null &&
        this.props.selectedValues.length > 0 &&
        this.props.selectedValues.includes(facetValue)
      ) {
        isSelected = false;
      } else {
        isSelected = true;
      }

      this.props.updateFacets(
        this.props.facet.es_field_name,
        facetValue,
        isSelected
      );
    }
  }

  onNewView(view) {
    view.addEventListener("click", this.onClick);
  }
}

export default withStyles(styles)(HistogramFacet);
<commit_msg>Change mouse to pointer when hovering over bar (#278)<commit_after>import React, { Component } from "react";
import Typography from "@material-ui/core/Typography";
import { withStyles } from "@material-ui/core/styles";
import VegaLite from "react-vega-lite";
import { Handler } from "vega-tooltip";

import "./HistogramFacet.css";
import * as Style from "libs/style";
import FacetHeader from "components/facets/FacetHeader";

const styles = {
  histogramFacet: {
    ...Style.elements.card,
    margin: "2%",
    paddingBottom: "8px",
    // Grid is defined in TextFacet so facet value counts can appear on right,
    // in addition to total facet value count.
    // Also define here to be consistent.
    display: "grid",
    gridTemplateColumns: "auto 50px",
    maxHeight: "400px",
    overflowY: "auto"
  },
  vega: {
    gridColumn: "1 / 3",
    textAlign: "center"
  }
};

const baseSpec = {
  $schema: "https://vega.github.io/schema/vega-lite/v3.json",
  mark: {
    type: "bar",
    cursor: "pointer"
  },
  encoding: {
    color: {
      field: "dimmed",
      type: "nominal",
      scale: {
        // First color is default bar color. Second color for unselected bars.
        range: ["#707986", "#cccfd4"]
      },
      legend: null
    },
    tooltip: {
      field: "text",
      type: "nominal"
    },
    x: {},
    y: {},
    // opacity is needed for creating transparent bars.
    opacity: {
      field: "opaque",
      type: "nominal",
      scale: {
        range: [0, 1]
      },
      legend: null
    }
  }
};

const facetValueCountAxis = {
  field: "count",
  type: "quantitative",
  title: "",
  stack: null,
  axis: {
    labelColor: "#000000de",
    labelFont: "Lato",
    labelFontSize: 11
  },
  scale: {
    rangeStep: 20
  }
};

function isCategorical(facet) {
  return (
    facet.es_field_type == "text" || facet.es_field_type == "samples_overview"
  );
}

class HistogramFacet extends Component {
  constructor(props) {
    super(props);
    this.isValueDimmed = this.isValueDimmed.bind(this);
    this.onNewView = this.onNewView.bind(this);
    this.onClick = this.onClick.bind(this);
  }

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

    const spec = Object.assign({}, baseSpec);
    let facetValueNames = this.props.facet.values.map(v => v.name);
    if (!isCategorical(this.props.facet)) {
      // For numeric facets, higher numbers should be higher on the y-axis
      facetValueNames.reverse();
    }
    const facetValueNameAxis = {
      field: "facet_value",
      type: "nominal",
      title: null,
      sort: facetValueNames,
      axis: {
        labelColor: "#000000de",
        labelFont: "Lato",
        labelFontSize: 11,
        labelLimit: 120
      },
      scale: {
        paddingInner: 0.02,
        rangeStep: 20
      }
    };
    // Make bars horizontal, to allow for more space for facet value names for
    // categorical facets.
    spec.encoding.x = facetValueCountAxis;
    spec.encoding.y = facetValueNameAxis;

    const data = {
      values: this.props.facet.values.map(v => {
        return {
          facet_value: v.name,
          count: v.count,
          dimmed: this.isValueDimmed(v),
          text: `${v.name}: ${v.count}`,
          opaque: true
        };
      })
    };

    // Create transparent bar that extends the entire length of the cart. This
    // makes tooltip/selection easier for facet values that have very low count.
    const maxFacetValue = Math.max(...data.values.map(v => v.count));
    data.values = data.values.concat(
      data.values.map(v => {
        const invisible = Object.assign({}, v);
        invisible.opaque = false;
        invisible.count = maxFacetValue;
        return invisible;
      })
    );

    return (

        <FacetHeader
          facet={this.props.facet}
          selectedValues={this.props.selectedValues}
        />

          <VegaLite
            spec={spec}
            data={data}
            tooltip={new Handler().call}
            onNewView={this.onNewView}
          />

    );
  }

  isValueDimmed(facetValue) {
    return (
      this.props.selectedValues != null &&
      this.props.selectedValues.length > 0 &&
      !this.props.selectedValues.includes(facetValue.name)
    );
  }

  onClick(event, item) {
    // Ignore clicks which are not located on histogram
    // bars.
    if (item && item.datum) {
      // facetValue is a string, eg "female"
      const facetValue = item.datum.facet_value;
      let isSelected;
      // this.props.selectedValues contains what was selected before the click.
      // isSelected contains if facet value was selected after the click.
      if (
        this.props.selectedValues != null &&
        this.props.selectedValues.length > 0 &&
        this.props.selectedValues.includes(facetValue)
      ) {
        isSelected = false;
      } else {
        isSelected = true;
      }

      this.props.updateFacets(
        this.props.facet.es_field_name,
        facetValue,
        isSelected
      );
    }
  }

  onNewView(view) {
    view.addEventListener("click", this.onClick);
  }
}

export default withStyles(styles)(HistogramFacet);
<|endoftext|>


<commit_before>/* eslint-env browser, jquery */
/* global io */

const api = (method, endpoint, params = {}) => {
  const url = `/api${endpoint}`;

  if (method === 'GET') {
    return fetch(`${url}?${$.param(params)}`, {
      method: 'GET',
      credentials: 'include',
    }).then(res => res.json());
  } else if (method === 'POST') {
    const csrfToken = $('meta[name=csrf-token]').attr('content');
    params._csrf = csrfToken;

    return fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
      },
      body: $.param(params),
      credentials: 'include',
    }).then(res => res.json());
  }

  return Promise.reject();
};

const updateLanguages = () => {
  api.get('/languages').then((languages) => {
    $('.language').each((index, languageEl) => {
      const $language = $(languageEl);
      const language = languages.find(language => language.slug === $language.data('slug'));

      if (language && language.owner) {
        $language.css({
          backgroundColor: language.owner.color,
          color: 'white',
        });
      } else {
        $language.css({
          backgroundColor: '',
          color: '',
        });
      }
    });
  });
};

api.get = api.bind(null, 'GET');
api.post = api.bind(null, 'POST');

$(document).ready(() => {
  const $modal = $('#language-modal');
  let pendingSubmission = {};

  $('.language').each((index, language) => {
    const $language = $(language);

    $language.click(() => {
      // Set title
      $modal.find('.modal-title').text($language.find('.name').text());

      $modal.find('.code').val('').attr('readonly', true);
      $modal.find('.submit-code').attr('disabled', true);
      $modal.find('.result').removeClass('bg-warning bg-success').hide();
      $modal.data('language', $language.data('slug'));

      api.get(`/languages/${$language.data('slug')}`).then((language) => {
        if (language.engine === 'ideone') {
          $modal.find('.engine-name').attr('href', 'https://ideone.com/').text('ideone');
        } else {
          $modal.find('.engine-name').attr('href', `http://${language.id}.tryitonline.net/`).text('Try it online!');
        }

        if (language.solution === null) {
          $modal.find('.owner-name').text('Not Solved');
          $modal.find('.code').attr('readonly', false);
          $modal.find('.submit-code').attr('disabled', false);
        } else {
          $modal.find('.owner-name').text(language.solution.user);
          $modal.find('.code').val(language.solution.code);
        }
      });

      return true;
    });
  });

  $modal.find('.submit-code').click(() => {
    const language = $modal.data('language');
    $modal.find('.code').attr('readonly', true);
    $modal.find('.submit-code').attr('disabled', true);
    $modal.find('.result').removeClass('bg-warning bg-success').hide();

    api.post('/submission', {
      language,
      code: $modal.find('.code').val(),
    }).then((submission) => {
      if (submission.error) {
        $modal.find('.result').addClass('bg-warning').text(submission.error).show();
        $modal.find('.code').attr('readonly', true);
        $modal.find('.submit-code').attr('disabled', false);
      } else {
        pendingSubmission = submission;
      }
    });
  });

  updateLanguages();

  const socket = io.connect(window.location.href);

  socket.on('update-submission', (data) => {
    if (data._id === pendingSubmission._id) {
      pendingSubmission = {};

      const getSubmission = () => {
        api.get('/submission', { _id: data._id }).then((submission) => {
          // TODO: XSS
          if (submission.status === 'failed') {
            $modal.find('.result').addClass('bg-warning')
            .html(`<strong>Submission failed.</strong> Check out the detail <a href="${submission.url}" target="_blank">here</a>.`)
            .show();
            $modal.find('.submit-code').attr('disabled', false);
          } else if (submission.status === 'success') {
            $modal.find('.result').addClass('bg-success')
            .html(`<strong>You won the language!</strong> Check out the detail <a href="${submission.url}" target="_blank">here</a>.`)
            .show();
          } else if (submission.status === 'pending') {
            setTimeout(getSubmission, 1000);
          }
        });
      };

      getSubmission();
    }
  });

  socket.on('update-languages', () => {
    updateLanguages();
  });
});
<commit_msg>Only execute JS in root page<commit_after>/* eslint-env browser, jquery */
/* global io */

const api = (method, endpoint, params = {}) => {
  const url = `/api${endpoint}`;

  if (method === 'GET') {
    return fetch(`${url}?${$.param(params)}`, {
      method: 'GET',
      credentials: 'include',
    }).then(res => res.json());
  } else if (method === 'POST') {
    const csrfToken = $('meta[name=csrf-token]').attr('content');
    params._csrf = csrfToken;

    return fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
      },
      body: $.param(params),
      credentials: 'include',
    }).then(res => res.json());
  }

  return Promise.reject();
};

const updateLanguages = () => {
  api.get('/languages').then((languages) => {
    $('.language').each((index, languageEl) => {
      const $language = $(languageEl);
      const language = languages.find(language => language.slug === $language.data('slug'));

      if (language && language.owner) {
        $language.css({
          backgroundColor: language.owner.color,
          color: 'white',
        });
      } else {
        $language.css({
          backgroundColor: '',
          color: '',
        });
      }
    });
  });
};

api.get = api.bind(null, 'GET');
api.post = api.bind(null, 'POST');

$(document).ready(() => {
  if (location.pathname !== '/') {
    return;
  }

  const $modal = $('#language-modal');
  let pendingSubmission = {};

  $('.language').each((index, language) => {
    const $language = $(language);

    $language.click(() => {
      // Set title
      $modal.find('.modal-title').text($language.find('.name').text());

      $modal.find('.code').val('').attr('readonly', true);
      $modal.find('.submit-code').attr('disabled', true);
      $modal.find('.result').removeClass('bg-warning bg-success').hide();
      $modal.data('language', $language.data('slug'));

      api.get(`/languages/${$language.data('slug')}`).then((language) => {
        if (language.engine === 'ideone') {
          $modal.find('.engine-name').attr('href', 'https://ideone.com/').text('ideone');
        } else {
          $modal.find('.engine-name').attr('href', `http://${language.id}.tryitonline.net/`).text('Try it online!');
        }

        if (language.solution === null) {
          $modal.find('.owner-name').text('Not Solved');
          $modal.find('.code').attr('readonly', false);
          $modal.find('.submit-code').attr('disabled', false);
        } else {
          $modal.find('.owner-name').text(language.solution.user);
          $modal.find('.code').val(language.solution.code);
        }
      });

      return true;
    });
  });

  $modal.find('.submit-code').click(() => {
    const language = $modal.data('language');
    $modal.find('.code').attr('readonly', true);
    $modal.find('.submit-code').attr('disabled', true);
    $modal.find('.result').removeClass('bg-warning bg-success').hide();

    api.post('/submission', {
      language,
      code: $modal.find('.code').val(),
    }).then((submission) => {
      if (submission.error) {
        $modal.find('.result').addClass('bg-warning').text(submission.error).show();
        $modal.find('.code').attr('readonly', true);
        $modal.find('.submit-code').attr('disabled', false);
      } else {
        pendingSubmission = submission;
      }
    });
  });

  updateLanguages();

  const socket = io.connect(window.location.href);

  socket.on('update-submission', (data) => {
    if (data._id === pendingSubmission._id) {
      pendingSubmission = {};

      const getSubmission = () => {
        api.get('/submission', { _id: data._id }).then((submission) => {
          // TODO: XSS
          if (submission.status === 'failed') {
            $modal.find('.result').addClass('bg-warning')
            .html(`<strong>Submission failed.</strong> Check out the detail <a href="${submission.url}" target="_blank">here</a>.`)
            .show();
            $modal.find('.submit-code').attr('disabled', false);
          } else if (submission.status === 'success') {
            $modal.find('.result').addClass('bg-success')
            .html(`<strong>You won the language!</strong> Check out the detail <a href="${submission.url}" target="_blank">here</a>.`)
            .show();
          } else if (submission.status === 'pending') {
            setTimeout(getSubmission, 1000);
          }
        });
      };

      getSubmission();
    }
  });

  socket.on('update-languages', () => {
    updateLanguages();
  });
});
<|endoftext|>