<commit_before>import {inject, customElement, bindable} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';
import {BindingEngine} from 'aurelia-binding';
import d3 from 'd3';
import GraphModel from './graph-model';
import {GraphLibD3} from './graphlib-d3';
import {GraphFinder} from './graph-finder';
import {GraphModifier} from './graph-modifier';
import {ActionManager} from './action-manager';
import {GraphPresentationService} from './graph-presentation-service';
import {GraphPresentationModel} from '../models/graph-presentation-model';

@customElement('graph')
@inject(Element, EventAggregator, GraphLibD3, GraphModel, GraphFinder, GraphModifier, ActionManager, BindingEngine,
  GraphPresentationService, GraphPresentationModel)
export class Graph {
  @bindable data;
  @bindable graphInteractionModel;

  constructor(element, pubSub, graphLibD3, graphModel, graphFinder, graphModifier, actionManager, bindingEngine,
      graphPresentationService, graphPresentationModel) {
    this.element = element;
    this.pubSub = pubSub;
    this.graphLibD3 = graphLibD3;
    this.graphModel = graphModel;
    this.graphFinder = graphFinder;
    this.graphModifier = graphModifier;
    this.actionManager = actionManager;
    this.bindingEngine = bindingEngine;
    this.graphPresentationService = graphPresentationService;
    this.graphPresentationModel = graphPresentationModel;

    this.windowSizeAdapter();

    this.displayNodes = [];
    this.displayEdges = [];

    this.pubSub.subscribe('search.node', nodeId => {
      this.addNodeAction([nodeId], true);
    });
  }

  /**
   * Update graph dimensions. Since this is shared model data also needed
   * by node calculator, use a service to update the model.
   */
  windowSizeAdapter() {
    // TODO Also need to respond to window resize so that svg can resize.
    this.graphPresentationService.updateDimensions(
      window.innerWidth - 100,
      window.innerHeight - 112
    );
  }

  /**
   * A new global graph (i.e. project analysis) is available.
   * Create an empty display graph and initialize it with some
   * sample data from the global graph.
   */
  dataChanged(newVal) {
    let unusedTypes;

    if (newVal) {
      unusedTypes = this.graphFinder.findUnusedTypes(this.graphModel.globalGraphModel);
      this.graphModel.initRadii();
      this.displayGraph = this.graphModel.emptyGraph();
      this.addNodeAction([unusedTypes[0]], true);
    }
  }

  /**
   * Perform the add action, and register undo and redo handlers.
   */
  addNodeAction(nodeIds, withNeighbours) {
    this.addAndDisplay(nodeIds, withNeighbours);
    this.actionManager.addAction(this,
      this.removeAndDisplay, [nodeIds, withNeighbours],
      this.addAndDisplay, [nodeIds, withNeighbours]
    );
  }

  addAndDisplay(nodeIds, withNeighbours) {
    this.addToDisplayGraphModel(nodeIds, withNeighbours);
    this.updateForceLayout();
  }

  removeAndDisplay(nodeIds, withNeighbours) {
    this.removeFromDisplayGraphModel(nodeIds, withNeighbours);
    this.updateForceLayout();
  }

  /**
   * Modify the graph display model to include the given node id's.
   * If withNeihbours is true, then also include each node's neighbours.
   */
  addToDisplayGraphModel(nodeIds, withNeighbours) {
    nodeIds.forEach( nodeId => {
      if (withNeighbours) {
        this.graphModifier.addNodeEnv(this.displayGraph, nodeId, 1, this.svgText);
      } else {
        this.graphModifier.addNodeOnly(this.displayGraph, nodeId, this.svgText);
      }
    });
  }

  /**
   * Modify the graph display model to remove the given node id's.
   * If withNeihbours is true, then also remove each node's neighbours.
   */
  removeFromDisplayGraphModel(nodeIds, withNeighbours) {
    nodeIds.forEach( nodeId => {
      if (withNeighbours) {
        this.graphModifier.removeNodeEnv(this.displayGraph, nodeId, 1, this.svgText);
      } else {
        this.graphModifier.removeNodeOnly(this.displayGraph, nodeId, this.svgText);
      }
    });
  }

  /**
   * Use D3 force layout to calculate node positions.
   * A high negative charge value avoids node overlap.
   * Setting link distance as function of width creates
   * a visually pleasing, uncluttered effect.
   */
  updateForceLayout() {
    this.d3Data = this.graphLibD3.mapToD3(this.displayGraph);
    let currentNode;
    let numNodes = this.d3Data.nodes.length;

    let force = d3.layout.force()
       .nodes(this.d3Data.nodes)
       .links(this.d3Data.links)
       .size([this.graphPresentationModel.width, this.graphPresentationModel.height])
       .gravity(0.4)
       .linkDistance(200)
       .charge(-3000)
       .on('tick', this.tick.bind(this));

    this.computeLayout(force, numNodes);
    this.updateDisplayData(this.d3Data);
  }

  /**
   * Make nodes fit within bounding box represented by this.graphPresentationModel.
   * A heuristic is used where radius is estimated to be 45, it works well enough.
   * A complete solution would know each node's actual radius, but this is very
   * difficult from timing perspective because its not known until after its
   * in the DOM, and caluclated by NodeCustomElement in micro task queue.
   * http://mbostock.github.io/d3/talk/20110921/bounding.html
   */
  tick() {
    let heuristicRadius = 45;
    this.d3Data.nodes.forEach( node => {
      let curX = node.x;
      node.x = Math.max(heuristicRadius, Math.min(this.graphPresentationModel.width - heuristicRadius, curX));
      let curY = node.y;
      node.y = Math.max(heuristicRadius, Math.min(this.graphPresentationModel.height - heuristicRadius, node.y));
    });
  }

  /**
   * Technique to minimize D3 chaotic bouncing in force layout.
   * http://stackoverflow.com/questions/13463053/calm-down-initial-tick-of-a-force-layout
   */
  computeLayout(force, numIterations) {
    let safety = 0;

    force.start();

    while(force.alpha() > 0.05) {
      force.tick();
      if(safety++ > 500) {
        break;
      }
    }

    force.stop();

    if(safety >= 500) {
      console.warn('Unable to stabilize force layout.');
    }
  }

  /**
   * Update list of display nodes and edges from d3 data.
   */
  updateDisplayData(d3Data) {
    // add nodes that are in d3Data, but not in display
    d3Data.nodes.forEach( node => {
      if (!this.graphLibD3.containsNode(this.displayNodes, node)) {
        this.displayNodes.push(node);
      }
    });

    // add links that are in d3Data, but not in display
    d3Data.links.forEach( link => {
      if (!this.graphLibD3.containsEdge(this.displayEdges, link)) {
        this.displayEdges.push(link);
      }
    });

    // remove nodes that are in display, but not in d3Data (iterate in reverse because splice re-indexes the array)
    let numDisplayNodes = this.displayNodes.length;
    for (let i=numDisplayNodes-1; i >= 0; i--) {
      if (!this.graphLibD3.containsNode(d3Data.nodes, this.displayNodes[i])) {
        this.displayNodes.splice(i, 1);
      }
    }

    // remove edges that are in display, but not in d3Data (iterate in reverse because splice re-indexes the array)
    let numDisplayEdges = this.displayEdges.length;
    for (let j=numDisplayEdges-1; j >=0; j--) {
      if (!this.graphLibD3.containsEdge(d3Data.links, this.displayEdges[j])) {
        this.displayEdges.splice(j, 1);
      }
    }

  }

  /**
   * Based on the currently selected nodes, use 'interaction' and 'type'
   * to find nodes from globalGraph that should be added, but only if
   * they're not already in the display graph.
   */
  findNodesToAdd(interaction, type) {
    let selectedNodeIds,
      relationship,
      nodesByEdgeRelationship;

    selectedNodeIds = this.graphFinder.findSelectedNodeIds(this.displayGraph);
    relationship = type === 'of it' ? 'target' : 'source';
    nodesByEdgeRelationship = this.graphFinder.findNodesByEdgeRelationship(
      this.graphModel.globalGraphModel, selectedNodeIds, interaction, relationship
    );

    return this.graphFinder.filterAlreadyInGraph(nodesByEdgeRelationship, this.displayGraph);
  }

  /**
   * Determine nodes that should be added, and add them as an undo-able action.
   */
  addNodesToDisplay(interaction, type) {
    let nodesToAdd = this.findNodesToAdd(interaction, type);

    if (nodesToAdd.length > 0) {
      this.addNodeAction(nodesToAdd);
    } else {
      // TODO notify https://github.com/CANVE/canve-viz/issues/32
      console.warn('No interaction results');
    }
  }

  /**
   * Invoked by Aurelia when 'graphInteractionModel' binding value has changed.
   * In practice this object it set once in the parent view. After that,
   * register property observers to fire when specific properties of the interaction model change,
   * indicating that the graph should respond to the change by adding more nodes.
   */
  graphInteractionModelChanged(newValue) {
    if (newValue) {
      this.graphInteractionModel = newValue;

      this.bindingEngine.propertyObserver(this.graphInteractionModel, 'callsSelectedVal').subscribe((newValue, oldValue) => {
        this.addNodesToDisplay('uses', newValue);
      });

      this.bindingEngine.propertyObserver(this.graphInteractionModel, 'extensionsSelectedVal').subscribe((newValue, oldValue) => {
        this.addNodesToDisplay('extends', newValue);
      });

      this.bindingEngine.propertyObserver(this.graphInteractionModel, 'ownershipSelectedVal').subscribe((newValue, oldValue) => {
        this.addNodesToDisplay('declares member', newValue);
      });

      // TODO dispose in appropriate lifecycle method http://stackoverflow.com/questions/30283569/array-subscription-in-aurelia
    }
  }

}
<commit_msg>#37 collision detection<commit_after>import {inject, customElement, bindable} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';
import {BindingEngine} from 'aurelia-binding';
import d3 from 'd3';
import GraphModel from './graph-model';
import {GraphLibD3} from './graphlib-d3';
import {GraphFinder} from './graph-finder';
import {GraphModifier} from './graph-modifier';
import {ActionManager} from './action-manager';
import {GraphPresentationService} from './graph-presentation-service';
import {GraphPresentationModel} from '../models/graph-presentation-model';

@customElement('graph')
@inject(Element, EventAggregator, GraphLibD3, GraphModel, GraphFinder, GraphModifier, ActionManager, BindingEngine,
  GraphPresentationService, GraphPresentationModel)
export class Graph {
  @bindable data;
  @bindable graphInteractionModel;

  constructor(element, pubSub, graphLibD3, graphModel, graphFinder, graphModifier, actionManager, bindingEngine,
      graphPresentationService, graphPresentationModel) {
    this.element = element;
    this.pubSub = pubSub;
    this.graphLibD3 = graphLibD3;
    this.graphModel = graphModel;
    this.graphFinder = graphFinder;
    this.graphModifier = graphModifier;
    this.actionManager = actionManager;
    this.bindingEngine = bindingEngine;
    this.graphPresentationService = graphPresentationService;
    this.graphPresentationModel = graphPresentationModel;

    this.windowSizeAdapter();

    this.displayNodes = [];
    this.displayEdges = [];

    this.pubSub.subscribe('search.node', nodeId => {
      this.addNodeAction([nodeId], true);
    });
  }

  /**
   * Update graph dimensions. Since this is shared model data also needed
   * by node calculator, use a service to update the model.
   */
  windowSizeAdapter() {
    // TODO Also need to respond to window resize so that svg can resize.
    this.graphPresentationService.updateDimensions(
      window.innerWidth - 100,
      window.innerHeight - 112
    );
  }

  /**
   * A new global graph (i.e. project analysis) is available.
   * Create an empty display graph and initialize it with some
   * sample data from the global graph.
   */
  dataChanged(newVal) {
    let unusedTypes;

    if (newVal) {
      unusedTypes = this.graphFinder.findUnusedTypes(this.graphModel.globalGraphModel);
      this.graphModel.initRadii();
      this.displayGraph = this.graphModel.emptyGraph();
      this.addNodeAction([unusedTypes[0]], true);
    }
  }

  /**
   * Perform the add action, and register undo and redo handlers.
   */
  addNodeAction(nodeIds, withNeighbours) {
    this.addAndDisplay(nodeIds, withNeighbours);
    this.actionManager.addAction(this,
      this.removeAndDisplay, [nodeIds, withNeighbours],
      this.addAndDisplay, [nodeIds, withNeighbours]
    );
  }

  addAndDisplay(nodeIds, withNeighbours) {
    this.addToDisplayGraphModel(nodeIds, withNeighbours);
    this.updateForceLayout();
  }

  removeAndDisplay(nodeIds, withNeighbours) {
    this.removeFromDisplayGraphModel(nodeIds, withNeighbours);
    this.updateForceLayout();
  }

  /**
   * Modify the graph display model to include the given node id's.
   * If withNeihbours is true, then also include each node's neighbours.
   */
  addToDisplayGraphModel(nodeIds, withNeighbours) {
    nodeIds.forEach( nodeId => {
      if (withNeighbours) {
        this.graphModifier.addNodeEnv(this.displayGraph, nodeId, 1, this.svgText);
      } else {
        this.graphModifier.addNodeOnly(this.displayGraph, nodeId, this.svgText);
      }
    });
  }

  /**
   * Modify the graph display model to remove the given node id's.
   * If withNeihbours is true, then also remove each node's neighbours.
   */
  removeFromDisplayGraphModel(nodeIds, withNeighbours) {
    nodeIds.forEach( nodeId => {
      if (withNeighbours) {
        this.graphModifier.removeNodeEnv(this.displayGraph, nodeId, 1, this.svgText);
      } else {
        this.graphModifier.removeNodeOnly(this.displayGraph, nodeId, this.svgText);
      }
    });
  }

  /**
   * Use D3 force layout to calculate node positions.
   * A high negative charge value avoids node overlap.
   * a visually pleasing, uncluttered effect.
   */
  updateForceLayout() {
    this.d3Data = this.graphLibD3.mapToD3(this.displayGraph);
    let currentNode;
    let numNodes = this.d3Data.nodes.length;

    let force = d3.layout.force()
       .nodes(this.d3Data.nodes)
       .links(this.d3Data.links)
       .size([this.graphPresentationModel.width, this.graphPresentationModel.height])
       .gravity(0.4)
       .linkDistance(200)
       .charge(-4000)
       .on('tick', this.tick.bind(this));

    this.computeLayout(force, numNodes);
    this.updateDisplayData(this.d3Data);
  }

  /**
   * Make nodes fit within bounding box represented by this.graphPresentationModel.
   * A heuristic is used where radius is estimated to be 45, it works well enough.
   * A complete solution would know each node's actual radius, but this is very
   * difficult from timing perspective because its not known until after its
   * in the DOM, and caluclated by NodeCustomElement in micro task queue.
   * http://mbostock.github.io/d3/talk/20110921/bounding.html
   */
  tick() {
    // collision detection
    let q = d3.geom.quadtree(this.d3Data.nodes),
      i = 0,
      n = this.d3Data.nodes.length;

    while (++i < n) q.visit(this.collide(this.d3Data.nodes[i]));

    // bounding box
    let heuristicRadius = 45;
    this.d3Data.nodes.forEach( node => {
      let curX = node.x;
      node.x = Math.max(heuristicRadius, Math.min(this.graphPresentationModel.width - heuristicRadius, curX));
      let curY = node.y;
      node.y = Math.max(heuristicRadius, Math.min(this.graphPresentationModel.height - heuristicRadius, node.y));
    });
  }

  collide(node) {
    let r = 45,
      nx1 = node.x - r,
      nx2 = node.x + r,
      ny1 = node.y - r,
      ny2 = node.y + r;
    return function(quad, x1, y1, x2, y2) {
      if (quad.point && (quad.point !== node)) {
        let x = node.x - quad.point.x,
          y = node.y - quad.point.y,
          l = Math.sqrt(x * x + y * y),
          r = node.radius + quad.point.radius;
        if (l < r) {
          l = (l - r) / l * 0.5;
          node.x -= x *= l;
          node.y -= y *= l;
          quad.point.x += x;
          quad.point.y += y;
        }
      }
      return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
    };
  }

  /**
   * Technique to minimize D3 chaotic bouncing in force layout.
   * http://stackoverflow.com/questions/13463053/calm-down-initial-tick-of-a-force-layout
   */
  computeLayout(force, numIterations) {
    let safety = 0;

    force.start();

    while(force.alpha() > 0.05) {
      force.tick();
      if(safety++ > 500) {
        break;
      }
    }

    force.stop();

    if(safety >= 500) {
      console.warn('Unable to stabilize force layout.');
    }
  }

  /**
   * Update list of display nodes and edges from d3 data.
   */
  updateDisplayData(d3Data) {
    // add nodes that are in d3Data, but not in display
    d3Data.nodes.forEach( node => {
      if (!this.graphLibD3.containsNode(this.displayNodes, node)) {
        this.displayNodes.push(node);
      }
    });

    // add links that are in d3Data, but not in display
    d3Data.links.forEach( link => {
      if (!this.graphLibD3.containsEdge(this.displayEdges, link)) {
        this.displayEdges.push(link);
      }
    });

    // remove nodes that are in display, but not in d3Data (iterate in reverse because splice re-indexes the array)
    let numDisplayNodes = this.displayNodes.length;
    for (let i=numDisplayNodes-1; i >= 0; i--) {
      if (!this.graphLibD3.containsNode(d3Data.nodes, this.displayNodes[i])) {
        this.displayNodes.splice(i, 1);
      }
    }

    // remove edges that are in display, but not in d3Data (iterate in reverse because splice re-indexes the array)
    let numDisplayEdges = this.displayEdges.length;
    for (let j=numDisplayEdges-1; j >=0; j--) {
      if (!this.graphLibD3.containsEdge(d3Data.links, this.displayEdges[j])) {
        this.displayEdges.splice(j, 1);
      }
    }

  }

  /**
   * Based on the currently selected nodes, use 'interaction' and 'type'
   * to find nodes from globalGraph that should be added, but only if
   * they're not already in the display graph.
   */
  findNodesToAdd(interaction, type) {
    let selectedNodeIds,
      relationship,
      nodesByEdgeRelationship;

    selectedNodeIds = this.graphFinder.findSelectedNodeIds(this.displayGraph);
    relationship = type === 'of it' ? 'target' : 'source';
    nodesByEdgeRelationship = this.graphFinder.findNodesByEdgeRelationship(
      this.graphModel.globalGraphModel, selectedNodeIds, interaction, relationship
    );

    return this.graphFinder.filterAlreadyInGraph(nodesByEdgeRelationship, this.displayGraph);
  }

  /**
   * Determine nodes that should be added, and add them as an undo-able action.
   */
  addNodesToDisplay(interaction, type) {
    let nodesToAdd = this.findNodesToAdd(interaction, type);

    if (nodesToAdd.length > 0) {
      this.addNodeAction(nodesToAdd);
    } else {
      // TODO notify https://github.com/CANVE/canve-viz/issues/32
      console.warn('No interaction results');
    }
  }

  /**
   * Invoked by Aurelia when 'graphInteractionModel' binding value has changed.
   * In practice this object it set once in the parent view. After that,
   * register property observers to fire when specific properties of the interaction model change,
   * indicating that the graph should respond to the change by adding more nodes.
   */
  graphInteractionModelChanged(newValue) {
    if (newValue) {
      this.graphInteractionModel = newValue;

      this.bindingEngine.propertyObserver(this.graphInteractionModel, 'callsSelectedVal').subscribe((newValue, oldValue) => {
        this.addNodesToDisplay('uses', newValue);
      });

      this.bindingEngine.propertyObserver(this.graphInteractionModel, 'extensionsSelectedVal').subscribe((newValue, oldValue) => {
        this.addNodesToDisplay('extends', newValue);
      });

      this.bindingEngine.propertyObserver(this.graphInteractionModel, 'ownershipSelectedVal').subscribe((newValue, oldValue) => {
        this.addNodesToDisplay('declares member', newValue);
      });

      // TODO dispose in appropriate lifecycle method http://stackoverflow.com/questions/30283569/array-subscription-in-aurelia
    }
  }

}
<|endoftext|>


<commit_before>describe('dateClick', function() {
  pushOptions({
    defaultDate: '2014-05-27',
    selectable: false,
    timeZone: 'UTC'
  });

  [ 'ltr', 'rtl' ].forEach(function(dir) {
    describe('when dir is ' + dir, function() {

      pushOptions({ dir });

      [ false, true ].forEach(function(selectable) {
        describe('when selectable is ' + selectable, function() {
          pushOptions({
            selectable
          })

          describe('when in month view', function() {
            pushOptions({defaultView: 'month'})

            it('fires correctly when clicking on a cell', function(done) {
              var options = {}
              options.dateClick = function(arg) {
                expect(arg.date instanceof Date).toEqual(true)
                expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
                expect(typeof arg.view).toEqual('object') // "
                expect(arg.allDay).toEqual(true)
                expect(arg.date).toEqualDate('2014-05-07')
                expect(arg.dateStr).toEqual('2014-05-07')
                done()
              }

              initCalendar(options)

              var dayCell = $('.fc-day:eq(10)') // 2014-05-07 (regardless of dir)

              // for simulating the mousedown/mouseup/click (relevant for selectable)
              dayCell.simulate('drag')
            })
          })

          describe('when in agendaWeek view', function() {
            pushOptions({
              defaultView: 'agendaWeek'
            })

            it('fires correctly when clicking on an all-day slot', function(done) {
              var options = {}
              options.dateClick = function(arg) {
                expect(arg.date instanceof Date).toEqual(true)
                expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
                expect(typeof arg.view).toEqual('object') // "
                expect(arg.allDay).toEqual(true)
                expect(arg.date).toEqualDate('2014-05-28')
                expect(arg.dateStr).toEqual('2014-05-28')
                done()
              }

              initCalendar(options)

              // 2014-05-28 (regardless of dir)
              var dayContent = $('.fc-agenda-view .fc-day-grid .fc-day:eq(3)')

              // for simulating the mousedown/mouseup/click (relevant for selectable)
              dayContent.simulate('drag')
            })

            it('fires correctly when clicking on a timed slot', function(done) {
              var options = {}
              // make sure the click slot will be in scroll view
              options.contentHeight = 500
              options.scrollTime = '07:00:00'

              options.dateClick = function(arg) {
                expect(arg.date instanceof Date).toEqual(true)
                expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
                expect(typeof arg.view).toEqual('object') // "
                expect(arg.allDay).toEqual(false)
                expect(arg.date).toEqualDate('2014-05-28T09:00:00Z')
                expect(arg.dateStr).toEqual('2014-05-28T09:00:00Z')
                done()
              }

              initCalendar(options)

              // the middle is 2014-05-28T09:00:00 (regardless of dir)
              var slotRow = $('.fc-slats tr:eq(18) td:not(.fc-time)')

              // for simulating the mousedown/mouseup/click (relevant for selectable)
              slotRow.simulate('drag')
            })

            // issue 2217
            it('fires correctly when clicking on a timed slot, with minTime set', function(done) {
              var options = {}
              // make sure the click slot will be in scroll view
              options.contentHeight = 500
              options.scrollTime = '07:00:00'
              options.minTime = '02:00:00'

              options.dateClick = function(arg) {
                expect(arg.date instanceof Date).toEqual(true)
                expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
                expect(typeof arg.view).toEqual('object') // "
                expect(arg.allDay).toEqual(false)
                expect(arg.date).toEqualDate('2014-05-28T11:00:00Z')
                expect(arg.dateStr).toEqual('2014-05-28T11:00:00Z')
                done()
              }

              initCalendar(options)

              // the middle is 2014-05-28T11:00:00 (regardless of dir)
              var slotRow = $('.fc-slats tr:eq(18) td:not(.fc-time)')

              // for simulating the mousedown/mouseup/click (relevant for selectable)
              slotRow.simulate('drag')
            })
          })
        })
      })
    })
  })

  describe('when touch', function() {

    it('fires correctly when simulated short drag on a cell', function(done) {
      var options = {}
      options.dateClick = function(arg) {
        expect(arg.date instanceof Date).toEqual(true)
        expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
        expect(typeof arg.view).toEqual('object') // "
        expect(arg.allDay).toEqual(true)
        expect(arg.date).toEqualDate('2014-05-07')
        expect(arg.dateStr).toEqual('2014-05-07')
        done()
      }

      initCalendar(options)

      var dayCell = $('.fc-day:eq(10)') // 2014-05-07 (regardless of dir)

      // for simulating the mousedown/mouseup/click (relevant for selectable)
      dayCell.simulate('drag', {
        isTouch: true
      })
    })

    it('won\'t fire if touch moves outside of date cell', function(done) {
      var options = {}
      options.dateClick = function(arg) {}
      spyOn(options, 'dateClick').and.callThrough()

      initCalendar(options)

      var startCell = $('.fc-day[data-date="2014-05-07"]')
      var endCell = $('.fc-day[data-date="2014-05-08"]')

      startCell.simulate('drag', {
        // FYI, when debug:true, not a good representation because the minimal  delay is required
        // to recreate bug #3332
        isTouch: true,
        end: endCell,
        callback: function() {
          expect(options.dateClick).not.toHaveBeenCalled()
          done()
        }
      })
    })

    it('fires correctly when simulated click on a cell', function(done) {
      var options = {}
      options.dateClick = function(arg) {
        expect(arg.date instanceof Date).toEqual(true)
        expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
        expect(typeof arg.view).toEqual('object') // "
        expect(arg.allDay).toEqual(true)
        expect(arg.date).toEqualDate('2014-05-07')
        expect(arg.dateStr).toEqual('2014-05-07')
        done()
      }

      initCalendar(options)

      var dayCell = $('.fc-day:eq(10)') // 2014-05-07 (regardless of dir)
      $.simulateTouchClick(dayCell)
    })
  })
})
<commit_msg>proof that clicking on bgevents fires dateClick<commit_after>describe('dateClick', function() {
  pushOptions({
    defaultDate: '2014-05-27',
    selectable: false,
    timeZone: 'UTC'
  });

  [ 'ltr', 'rtl' ].forEach(function(dir) {
    describe('when dir is ' + dir, function() {

      pushOptions({ dir });

      [ false, true ].forEach(function(selectable) {
        describe('when selectable is ' + selectable, function() {
          pushOptions({
            selectable
          })

          describe('when in month view', function() {
            pushOptions({defaultView: 'month'})

            it('fires correctly when clicking on a cell', function(done) {
              var options = {}
              options.dateClick = function(arg) {
                expect(arg.date instanceof Date).toEqual(true)
                expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
                expect(typeof arg.view).toEqual('object') // "
                expect(arg.allDay).toEqual(true)
                expect(arg.date).toEqualDate('2014-05-07')
                expect(arg.dateStr).toEqual('2014-05-07')
                done()
              }

              initCalendar(options)

              var dayCell = $('.fc-day:eq(10)') // 2014-05-07 (regardless of dir)

              // for simulating the mousedown/mouseup/click (relevant for selectable)
              dayCell.simulate('drag')
            })
          })

          describe('when in agendaWeek view', function() {
            pushOptions({
              defaultView: 'agendaWeek'
            })

            it('fires correctly when clicking on an all-day slot', function(done) {
              var options = {}
              options.dateClick = function(arg) {
                expect(arg.date instanceof Date).toEqual(true)
                expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
                expect(typeof arg.view).toEqual('object') // "
                expect(arg.allDay).toEqual(true)
                expect(arg.date).toEqualDate('2014-05-28')
                expect(arg.dateStr).toEqual('2014-05-28')
                done()
              }

              initCalendar(options)

              // 2014-05-28 (regardless of dir)
              var dayContent = $('.fc-agenda-view .fc-day-grid .fc-day:eq(3)')

              // for simulating the mousedown/mouseup/click (relevant for selectable)
              dayContent.simulate('drag')
            })

            it('fires correctly when clicking on a timed slot', function(done) {
              var options = {}
              // make sure the click slot will be in scroll view
              options.contentHeight = 500
              options.scrollTime = '07:00:00'

              options.dateClick = function(arg) {
                expect(arg.date instanceof Date).toEqual(true)
                expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
                expect(typeof arg.view).toEqual('object') // "
                expect(arg.allDay).toEqual(false)
                expect(arg.date).toEqualDate('2014-05-28T09:00:00Z')
                expect(arg.dateStr).toEqual('2014-05-28T09:00:00Z')
                done()
              }

              initCalendar(options)

              // the middle is 2014-05-28T09:00:00 (regardless of dir)
              var slotRow = $('.fc-slats tr:eq(18) td:not(.fc-time)')

              // for simulating the mousedown/mouseup/click (relevant for selectable)
              slotRow.simulate('drag')
            })

            // issue 2217
            it('fires correctly when clicking on a timed slot, with minTime set', function(done) {
              var options = {}
              // make sure the click slot will be in scroll view
              options.contentHeight = 500
              options.scrollTime = '07:00:00'
              options.minTime = '02:00:00'

              options.dateClick = function(arg) {
                expect(arg.date instanceof Date).toEqual(true)
                expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
                expect(typeof arg.view).toEqual('object') // "
                expect(arg.allDay).toEqual(false)
                expect(arg.date).toEqualDate('2014-05-28T11:00:00Z')
                expect(arg.dateStr).toEqual('2014-05-28T11:00:00Z')
                done()
              }

              initCalendar(options)

              // the middle is 2014-05-28T11:00:00 (regardless of dir)
              var slotRow = $('.fc-slats tr:eq(18) td:not(.fc-time)')

              // for simulating the mousedown/mouseup/click (relevant for selectable)
              slotRow.simulate('drag')
            })
          })
        })
      })
    })
  })

  it('will still fire if clicked on background event', function(done) {
    initCalendar({
      defaultView: 'month',
      events: [ {
        start: '2014-05-06',
        rendering: 'background'
      } ],
      dateClick(info) {
        expect(info.dateStr).toBe('2014-05-06')
        done()
      }
    })

    // for simulating the mousedown/mouseup/click (relevant for selectable)
    $('.fc-bgevent').simulate('drag')
  })

  describe('when touch', function() {

    it('fires correctly when simulated short drag on a cell', function(done) {
      var options = {}
      options.dateClick = function(arg) {
        expect(arg.date instanceof Date).toEqual(true)
        expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
        expect(typeof arg.view).toEqual('object') // "
        expect(arg.allDay).toEqual(true)
        expect(arg.date).toEqualDate('2014-05-07')
        expect(arg.dateStr).toEqual('2014-05-07')
        done()
      }

      initCalendar(options)

      var dayCell = $('.fc-day:eq(10)') // 2014-05-07 (regardless of dir)

      // for simulating the mousedown/mouseup/click (relevant for selectable)
      dayCell.simulate('drag', {
        isTouch: true
      })
    })

    it('won\'t fire if touch moves outside of date cell', function(done) {
      var options = {}
      options.dateClick = function(arg) {}
      spyOn(options, 'dateClick').and.callThrough()

      initCalendar(options)

      var startCell = $('.fc-day[data-date="2014-05-07"]')
      var endCell = $('.fc-day[data-date="2014-05-08"]')

      startCell.simulate('drag', {
        // FYI, when debug:true, not a good representation because the minimal  delay is required
        // to recreate bug #3332
        isTouch: true,
        end: endCell,
        callback: function() {
          expect(options.dateClick).not.toHaveBeenCalled()
          done()
        }
      })
    })

    it('fires correctly when simulated click on a cell', function(done) {
      var options = {}
      options.dateClick = function(arg) {
        expect(arg.date instanceof Date).toEqual(true)
        expect(typeof arg.jsEvent).toEqual('object') // TODO: more descrimination
        expect(typeof arg.view).toEqual('object') // "
        expect(arg.allDay).toEqual(true)
        expect(arg.date).toEqualDate('2014-05-07')
        expect(arg.dateStr).toEqual('2014-05-07')
        done()
      }

      initCalendar(options)

      var dayCell = $('.fc-day:eq(10)') // 2014-05-07 (regardless of dir)
      $.simulateTouchClick(dayCell)
    })
  })
})
<|endoftext|>


<commit_before>/*jshint node:true*/
'use strict';

module.exports = function(environment) {
  var ENV = {
    modulePrefix: '<%= modulePrefix %>',
    environment: environment
  }

  return ENV;
};
<commit_msg>Added semicolon at end of ENV variable<commit_after>/*jshint node:true*/
'use strict';

module.exports = function(environment) {
  var ENV = {
    modulePrefix: '<%= modulePrefix %>',
    environment: environment
  };

  return ENV;
};
<|endoftext|>


<commit_before>import { useMemo, useEffect, useRef } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { mountPaths, unmountPaths } from '../kea/mount'

export function useProps(logic) {
  useMountedLogic(logic)

  let response = {}

  for (const key of Object.keys(logic.selectors)) {
    response[key] = useSelector(logic.selectors[key])
  }

  return response
}

export function useActions(logic) {
  useMountedLogic(logic)

  const dispatch = useDispatch()

  return useMemo(() => {
    let response = {}

    for (const key of Object.keys(logic.actions)) {
      response[key] = (...args) => dispatch(logic.actions[key](...args))
    }

    return response
  }, [dispatch])
}

export function useMountedLogic(logic) {
  const firstRender = useRef(true)
  if (firstRender.current) {
    firstRender.current = false
    mountPaths(logic)
  }
  useEffect(() => () => unmountPaths(logic), [])
}

// import { bindActionCreators } from 'redux'
// import { useDispatch } from 'react-redux'
// import { useMemo } from 'react'

// export function useActions(actions, deps) {
//   const dispatch = useDispatch()
//   return useMemo(() => {
//     if (Array.isArray(actions)) {
//       return actions.map(a => bindActionCreators(a, dispatch))
//     }
//     return bindActionCreators(actions, dispatch)
//   }, deps ? [dispatch, ...deps] : deps)
// }<commit_msg>useProps via getters<commit_after>import { useMemo, useEffect, useRef } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { mountPaths, unmountPaths } from '../kea/mount'

export function useProps(logic) {
  useMountedLogic(logic)

  return useMemo(() => {
    let response = {}

    for (const key of Object.keys(logic.selectors)) {
      Object.defineProperty(response, key, {
        get: () => useSelector(logic.selectors[key])
      })
    }

    return response
  }, [])
}

export function useActions(logic) {
  useMountedLogic(logic)

  const dispatch = useDispatch()

  return useMemo(() => {
    let response = {}

    for (const key of Object.keys(logic.actions)) {
      response[key] = (...args) => dispatch(logic.actions[key](...args))
    }

    return response
  }, [dispatch])
}

export function useMountedLogic(logic) {
  const firstRender = useRef(true)
  if (firstRender.current) {
    firstRender.current = false
    mountPaths(logic)
  }
  useEffect(() => () => unmountPaths(logic), [])
}
<|endoftext|>


<commit_before>"use strict";
var express = require("express");
var bodyParser = require('body-parser');
var morgan = require("morgan");
var util = require("util");
var EventEmitter = require("events").EventEmitter;

/**
 * Construct a new application service.
 * @constructor
 * @param {Object} config Configuration for this service.
 * @param {String} config.homeserverToken The incoming HS token to expect. Must
 * be set prior to calling listen(port).
 * @param {Number} config.httpMaxSizeBytes The max number of bytes allowed on an
 * incoming HTTP request. Default: 5000000.
 * @throws If a homeserver token is not supplied.
 */
function AppService(config) {
    var self = this;
    this.config = config || {};
    this.app = express();
    this.app.use(morgan("combined", {
        stream: {
            write: function(str) {
                /**
                 * An HTTP log line.
                 * @event AppService#http-log
                 * @type {String}
                 * @example
                 * appService.on("http-log", function(logLine) {
                 *   console.log(logLine);
                 * });
                 */
                self.emit("http-log", str);
            }
        }
    }));

    this.app.use(bodyParser.json({
        limit: this.config.httpMaxSizeBytes || 5000000  // 5MB
    }));

    function isInvalidToken(req, res) {
        var hsToken = req.query.access_token;
        if (hsToken !== self.config.homeserverToken) {
            res.send({
                errcode: "M_FORBIDDEN",
                error: "Bad token supplied,"
            });
            return true;
        }
        return false;
    }

    this.app.get("/users/:userId", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }
        var possiblePromise = self.onUserQuery(req.params.userId, function() {
            res.send({});
        });
        if (possiblePromise) {
            possiblePromise.then(function() {
                res.send({});
            });
        }
    });

    this.app.get("/rooms/:alias", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }
        var possiblePromise = self.onAliasQuery(req.params.alias, function() {
            res.send({});
        });
        if (possiblePromise) {
            possiblePromise.then(function() {
                res.send({});
            });
        }
    });

    var lastProcessedTxnId;
    this.app.put("/transactions/:txnId", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }

        var txnId = req.params.txnId;
        if (!txnId) {
            res.send("Missing transaction ID.");
            return;
        }
        if (!req.body || !req.body.events) {
            res.send("Missing events body.");
            return;
        }
        var events = req.body.events;

        if (lastProcessedTxnId === txnId) {
            res.send({}); // duplicate
            return;
        }
        for (var i = 0; i < events.length; i++) {
            /**
             * An incoming Matrix JSON event.
             * @event AppService#event
             * @type {Object}
             * @example
             * appService.on("event", function(ev) {
             *   console.log("ID: %s", ev.event_id);
             * });
             */
            self.emit("event", events[i]);
            if (events[i].type) {
                /**
                 * An incoming Matrix JSON event, filtered by event.type
                 * @event AppService#type:event
                 * @type {Object}
                 * @example
                 * appService.on("type:m.room.message", function(ev) {
                 *   console.log("Body: %s", ev.content.body);
                 * });
                 */
                self.emit("type:" + events[i].type, events[i]);
            }
        }
        lastProcessedTxnId = txnId;
        res.send({});
    });
}
util.inherits(AppService, EventEmitter);

/**
 * Override this method to handle alias queries.
 * @param {string} alias The queried room alias
 * @param {Function} callback The callback to invoke when complete.
 * @return {Promise} A promise to resolve when complete (if callback isn't supplied)
 */
AppService.prototype.onAliasQuery = function(alias, callback) {
    callback(); // stub impl
    return null;
};

/**
 * Override this method to handle user queries.
 * @param {string} userId The queried user ID.
 * @param {Function} callback The callback to invoke when complete.
 * @return {Promise} A promise to resolve when complete (if callback isn't supplied)
 */
AppService.prototype.onUserQuery = function(userId, callback) {
    callback(); // stub impl
    return null;
};

/**
 * Set the token that should be used to verify incoming events.
 * @param {string} hsToken The home server token
 */
AppService.prototype.setHomeserverToken = function(hsToken) {
    this.config.homeserverToken = hsToken;
};

/**
 * Begin listening on the specified port.
 * @param {Number} port The port to listen on.
 */
AppService.prototype.listen = function(port) {
    this.app.listen(port);
};

/** The application service class */
module.exports = AppService;
<commit_msg>Terminate the promise chain onXQuery<commit_after>"use strict";
var express = require("express");
var bodyParser = require('body-parser');
var morgan = require("morgan");
var util = require("util");
var EventEmitter = require("events").EventEmitter;

/**
 * Construct a new application service.
 * @constructor
 * @param {Object} config Configuration for this service.
 * @param {String} config.homeserverToken The incoming HS token to expect. Must
 * be set prior to calling listen(port).
 * @param {Number} config.httpMaxSizeBytes The max number of bytes allowed on an
 * incoming HTTP request. Default: 5000000.
 * @throws If a homeserver token is not supplied.
 */
function AppService(config) {
    var self = this;
    this.config = config || {};
    this.app = express();
    this.app.use(morgan("combined", {
        stream: {
            write: function(str) {
                /**
                 * An HTTP log line.
                 * @event AppService#http-log
                 * @type {String}
                 * @example
                 * appService.on("http-log", function(logLine) {
                 *   console.log(logLine);
                 * });
                 */
                self.emit("http-log", str);
            }
        }
    }));

    this.app.use(bodyParser.json({
        limit: this.config.httpMaxSizeBytes || 5000000  // 5MB
    }));

    function isInvalidToken(req, res) {
        var hsToken = req.query.access_token;
        if (hsToken !== self.config.homeserverToken) {
            res.send({
                errcode: "M_FORBIDDEN",
                error: "Bad token supplied,"
            });
            return true;
        }
        return false;
    }

    this.app.get("/users/:userId", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }
        var possiblePromise = self.onUserQuery(req.params.userId, function() {
            res.send({});
        });
        if (possiblePromise) {
            possiblePromise.done(function() {
                res.send({});
            }, function(e) {
                res.send({
                    errcode: "M_UNKNOWN",
                    error: e ? e.message : ""
                });
            });
        }
    });

    this.app.get("/rooms/:alias", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }
        var possiblePromise = self.onAliasQuery(req.params.alias, function() {
            res.send({});
        });
        if (possiblePromise) {
            possiblePromise.done(function() {
                res.send({});
            }, function(e) {
                res.send({
                    errcode: "M_UNKNOWN",
                    error: e ? e.message : ""
                });
            });
        }
    });

    var lastProcessedTxnId;
    this.app.put("/transactions/:txnId", function(req, res) {
        if (isInvalidToken(req, res)) {
            return;
        }

        var txnId = req.params.txnId;
        if (!txnId) {
            res.send("Missing transaction ID.");
            return;
        }
        if (!req.body || !req.body.events) {
            res.send("Missing events body.");
            return;
        }
        var events = req.body.events;

        if (lastProcessedTxnId === txnId) {
            res.send({}); // duplicate
            return;
        }
        for (var i = 0; i < events.length; i++) {
            /**
             * An incoming Matrix JSON event.
             * @event AppService#event
             * @type {Object}
             * @example
             * appService.on("event", function(ev) {
             *   console.log("ID: %s", ev.event_id);
             * });
             */
            self.emit("event", events[i]);
            if (events[i].type) {
                /**
                 * An incoming Matrix JSON event, filtered by event.type
                 * @event AppService#type:event
                 * @type {Object}
                 * @example
                 * appService.on("type:m.room.message", function(ev) {
                 *   console.log("Body: %s", ev.content.body);
                 * });
                 */
                self.emit("type:" + events[i].type, events[i]);
            }
        }
        lastProcessedTxnId = txnId;
        res.send({});
    });
}
util.inherits(AppService, EventEmitter);

/**
 * Override this method to handle alias queries.
 * @param {string} alias The queried room alias
 * @param {Function} callback The callback to invoke when complete.
 * @return {Promise} A promise to resolve when complete (if callback isn't supplied)
 */
AppService.prototype.onAliasQuery = function(alias, callback) {
    callback(); // stub impl
    return null;
};

/**
 * Override this method to handle user queries.
 * @param {string} userId The queried user ID.
 * @param {Function} callback The callback to invoke when complete.
 * @return {Promise} A promise to resolve when complete (if callback isn't supplied)
 */
AppService.prototype.onUserQuery = function(userId, callback) {
    callback(); // stub impl
    return null;
};

/**
 * Set the token that should be used to verify incoming events.
 * @param {string} hsToken The home server token
 */
AppService.prototype.setHomeserverToken = function(hsToken) {
    this.config.homeserverToken = hsToken;
};

/**
 * Begin listening on the specified port.
 * @param {Number} port The port to listen on.
 */
AppService.prototype.listen = function(port) {
    this.app.listen(port);
};

/** The application service class */
module.exports = AppService;
<|endoftext|>


<commit_before>// We reduce page load times by using pre-built and compressed
// libraries. This shaves around 500kb from the payload
import socketIoClient from '../vendor/socket.io-client';
import _ from '../vendor/lodash';

// Newer browsers support explicit prototype manipulation,
// but slightly older ones need a shim that uses the
// `__proto__` binding
let getPrototypeOf;
let setPrototypeOf;

if (_.isFunction(Object.getPrototypeOf)) {
  getPrototypeOf = Object.getPrototypeOf;
} else {
  getPrototypeOf = function fallbackGetPrototypeOf(obj) {
    return obj.__proto__;
  };
}

if (_.isFunction(Object.setPrototypeOf)) {
  setPrototypeOf = Object.setPrototypeOf;
} else {
  setPrototypeOf = function fallbackSetPrototypeOf(obj, proto) {
    obj.__proto__ = proto;
  };
}

// Older browsers tend to support varying degrees of prototype
// introspection and manipulation. As the hot runtime depends
// on being able to perform both, we run through a quick
// test-case so that we can warn when things are likely to break
(() => {
  const proto1 = {};
  const obj = _.create(proto1);

  if (getPrototypeOf(obj) !== proto1) {
    console.warn('[hot] Prototype introspection is not supported. The hot runtime should be removed');
    return;
  }

  const proto2 = {};
  setPrototypeOf(obj, proto2);
  if (getPrototypeOf(obj) !== proto2) {
    console.warn('[hot] Prototype manipulation is not supported. The hot runtime should be removed');
  }
})();

__modules.hotSwapExportReferences = Object.create(null);

function log() {
  if (global.__QUIET_UNFORT__ !== true) {
    console.log.apply(console, arguments);
  }
}

// Before we start monkey-patching the runtime, we need to preserve some references
const extendModule = __modules.extendModule;
const defineModule = __modules.defineModule;

// Monkey-patch `extendModule` so that we can add the `hot` API
__modules.extendModule = function extendModuleHotWrapper(mod) {
  mod = extendModule(mod);

  // State associated with swapping the module
  if (mod.hot === undefined) {
    mod.hot = {
      // The previous state of the module
      previous: null,
      exportsProxy: {},
      accepted: false,
      acceptedCallback: null,
      onExit: null,
      exitData: undefined,
      onChanges: null
    };
  }

  if (mod.commonjs.hot === undefined) {
    // The `module.hot` API
    mod.commonjs.hot = {
      /**
       * `module.hot.accept`
       *
       * Indicate that a module will accept hot swaps. Accepts on optional
       * callback that will be triggered when a module has been removed.
       *
       * Note: this has some cross-over with the functionality of
       * `module.hot.exit`. This is provided mostly for compatibility with
       * webpack's HMR API.
       *
       * @param {Function} [cb]
       */
      accept(cb) {
        mod.hot.accepted = true;

        if (_.isFunction(cb)) {
          mod.hot.acceptedCallback = cb;
        }
      },
      /**
       * `module.hot.enter`
       *
       * If a module was swapped and the new version is being executed, a
       * callback passed to `module.hot.enter` will be called immediately
       * with one argument, the value returned from the previous version's
       * `module.hot.exit` callback.
       *
       * @param {Function} cb
       * @returns {*} returns the return value from `cb`
       */
      enter(cb) {
        if (!_.isFunction(cb)) {
          throw new Error(`module.hot.enter must be provided with a function. Received: ${cb}`);
        }

        const prevMod = mod.hot.previous;
        if (prevMod) {
          return cb(prevMod.hot.exitData);
        }
      },
      /**
       * `module.hot.exit`
       *
       * Indicates that a module will accept hot swaps and allows you to pass
       * data from one module version to another.
       *
       * Before a module is swapped, a callback passed to `module.hot.exit`
       * will be called and its return value will be stored for the next
       * version of the module.
       *
       * @param {Function} cb
       */
      exit(cb) {
        mod.hot.accepted = true;

        if (!_.isFunction(cb)) {
          throw new Error(`module.hot.exit must be provided with a function. Received: ${cb}`);
        }

        mod.hot.onExit = cb;
      },
      /**
       * `module.hot.changes`
       *
       * Allows you to execute a callback that will occur after all buffered
       * modules have been hot swapped.
       *
       * Note: if the module that specified the callback is one of the swapped
       * modules, the callback will *not* be called
       *
       * @param {Function} cb
       */
      changes(cb) {
        if (!_.isFunction(cb)) {
          throw new Error(`module.hot.changes must be provided with a function. Received: ${cb}`);
        }

        mod.hot.onChanges = cb;
      }
    };
  }

  return mod;
};

// Add the hot API to each module
_.forEach(__modules.modules, __modules.extendModule);

// There's a bit of complexity in the handling of changed assets.
// In particular, js modules that may depend on new dependencies
// introduce the potential for race conditions as we may or may
// not have a module available when we execute its dependent modules.
// To get around this, we buffer all the modules and only start to
// apply them once _.every pending module has been buffered
__modules.pending = {};
__modules.buffered = [];

// Monkey-patch `defineModule` so that we can intercept incoming modules
__modules.defineModule = function defineModuleHotWrapper(mod) {
  mod = __modules.extendModule(mod);

  const {name, hash} = mod;

  // Prevent unexpected modules from being applied
  if (__modules.pending[name] === undefined) {
    return log(
      `[hot] Attempted to add module "${name}", but it is not registered as pending and will be ignored`
    );
  }

  // Prevent an unexpected version from being applied
  if (__modules.pending[name] !== hash) {
    return log(
      `[hot] Unexpected update for module ${name}. Hash ${hash} does not reflect the expected hash ${__modules.pending[name]} and will be ignored`
    );
  }

  __modules.buffered.push(mod);
  __modules.pending[mod.name] = undefined;
  const readyToApply = _.every(__modules.pending, _.isUndefined);

  if (readyToApply) {
    __modules.pending = {};
    const _buffered = __modules.buffered;
    __modules.buffered = [];

    const modulesSwapped = Object.create(null);

    const toSwap = _buffered.map(mod => {
      modulesSwapped[mod.name] = true;
      return [mod, __modules.modules[mod.name]];
    });

    toSwap.forEach(([mod, prevMod]) => {
      const {name, hash} = mod;

      if (prevMod) {
        log(`[hot] Hot swapping ${name} from hash ${prevMod.hash} to hash ${hash}`);
      } else {
        log(`[hot] Initializing ${name} at hash ${hash}`);
      }

      if (prevMod) {
        // Trigger any callbacks passed to `module.hot.exit` and
        // store its return value for the next version
        if (prevMod.hot.onExit) {
          prevMod.hot.exitData = prevMod.hot.onExit();
        }

        // Prevent memory leaks by removing any references to past
        // versions of the module that is about to be swapped
        if (prevMod.hot.previous) {
          prevMod.hot.previous = null;
        }

        // We pass the exports proxy between module states so that dependent modules
        // have their references updated when we swap
        mod.hot.exportsProxy = prevMod.hot.exportsProxy;

        // Store the previous version of the module so that the next version
        // can introspect it
        mod.hot.previous = prevMod;
      }

      // Update the runtime's module registry
      defineModule(mod);
    });

    toSwap.forEach(([mod]) => {
      // If we're applying multiple modules, it's possible that new
      // modules may execute other new modules, so we need to iterate
      // through and selectively execute modules that have not been
      // called yet.
      if (!mod.executed) {
        // Note: if a module throws an error during execution, the entire
        // hot swap will fail with it. This is by design, as it prevents
        // unexpected behaviour
        __modules.executeModule(mod.name);
      }
    });

    // Trigger any callbacks passed to `module.hot.accept`
    toSwap.forEach(([_, prevMod]) => {
      if (prevMod && prevMod.hot.acceptedCallback) {
        prevMod.hot.acceptedCallback();
      }
    });

    _.forOwn(__modules.modules, mod => {
      // Handle modules which have been removed
      if (mod === undefined) {
        return;
      }

      // If a module was not swapped and it has specified a callback to
      // `module.hot.changes`, then we call it
      if (!modulesSwapped[mod.name] && mod.hot.onChanges) {
        mod.hot.onChanges();
      }
    });
  }
};

const executeModule = __modules.executeModule;
__modules.executeModule = function executeModuleHotWrapper(name) {
  executeModule(name);

  const mod = __modules.modules[name];
  const exports = mod.commonjs.exports;
  if (
    _.isObject(exports) &&
    !_.isFunction(exports) &&
    exports.__esModule
  ) {
    const exportsProxy = mod.hot.exportsProxy;

    if (getPrototypeOf(exportsProxy) !== exports) {
      setPrototypeOf(exportsProxy, exports);
      mod.commonjs.exports = exportsProxy;
    }
  }

  return mod.commonjs.exports;
};

const io = socketIoClient();

io.on('connect', () => {
  log('[hot] Connected');
});

io.on('unfort:build-started', () => {
  log('[hot] Build started');
});

io.on('unfort:build-error', err => {
  console.error(`[hot] Build error: ${err}`);
});

io.on('unfort:build-complete', ({records, removed}) => {
  // With the complete signal, we can start updating our assets
  // and begin the process of hot swapping code.

  const accepted = [];
  const unaccepted = [];

  _.forEach(records, (record, name) => {
    const mod = __modules.modules[name];

    // If it's a new module, we accept it
    if (!mod) {
      accepted.push(name);
      return;
    }

    // If the module is outdated, we check if we can update it
    if (mod.hash !== record.hash) {
      if (_.endsWith(name, '.css') || !record.isTextFile) {
        // As css and binary files are stateless, we can
        // blindly accept them
        accepted.push(name);
      } else if (mod.hot.accepted) {
        accepted.push(name);
      } else {
        unaccepted.push(name);
      }
    }
  });

  // If there were any unaccepted modules, we refuse to apply any changes
  if (unaccepted.length) {
    let message = `[hot] Cannot accept any changes as the following modules have not accepted hot swaps:\n${unaccepted.join('\n')}`;
    if (accepted.length) {
      message += `\n\nUpdates to the following modules have been blocked:\n${accepted.join('\n')}`;
    }
    return console.warn(message);
  }

  // We try to avoid issues from concurrent-ish updates by resetting
  // the pending state so that any calls to `defineModule` will be ignored
  __modules.pending = {};

  // If a module has already been buffered for execution, we can ignore
  // updates for it
  __modules.buffered = __modules.buffered.filter(({name, hash}) => {
    if (_.includes(accepted, name) && records[name].hash === hash) {
      _.pull(accepted, name);
      return true;
    }
    return false;
  });

  const modulesToRemove = _.keys(removed);
  if (modulesToRemove.length) {
    _.forEach(removed, (record, name) => {
      // We need to clear the state for any modules that have been removed
      // so that if they are re-added, they are executed again. This could
      // cause some issues for crazily stateful js, but it's needed to ensure
      // that css changes are always applied
      __modules.modules[name] = undefined;

      // Ensure that the asset is removed from the document
      removeRecordAssetFromDocument(record);
    });
    log(`[hot] Removed modules:\n${modulesToRemove.join('\n')}`);
  }

  if (!accepted.length) {
    return log('[hot] No updates to apply');
  }

  accepted.forEach(name => {
    const record = records[name];

    // Asynchronously fetch the asset
    updateRecordAssetInDocument(record);

    // Ensure that the runtime knows that we are waiting for this specific
    // versions of the module. We need to keep this synced so that we can
    // clear the buffered modules only when it's appropriate to
    __modules.pending[record.name] = record.hash;
  });

  // Note: this iteration *must* occur after `__modules.pending` has been
  // configured. Otherwise, `__modules.defineModule` will give strange
  // responses if we are trying to update multiple assets that include
  // css files
  accepted.forEach(name => {
    const record = records[name];

    // As css assets wont trigger a call to `defineModule`, we need to manually
    // call it to ensure that our module buffer is inevitably cleared and
    // the runtime's module registry is updated. This prevents an issue where
    // reverting a css asset to a previous version may have no effect as the
    // registry assumes it's already been applied
    if (
      _.endsWith(record.url, '.css') ||
      !record.isTextFile
    ) {
      __modules.defineModule({
        name: record.name,
        hash: record.hash,
        deps: {},
        // Note: the factory is defined outside of this closure to prevent the
        // signal payload from sitting in memory
        factory: createRecordUrlModule(record.url)
      });
    }
  });
});

function createRecordUrlModule(url) {
  return function recordUrlModule(module, exports) {
    exports.default = url;
    exports.__esModule = true;
    if (module.hot) {
      module.hot.accept();
    }
  };
}

function removeRecordAssetFromDocument(record) {
  const {name, url} = record;

  if (!record.isTextFile) {
    // Nothing to do here
    return;
  }

  if (_.endsWith(url, '.css')) {
    return removeStylesheet(record);
  }

  if (_.endsWith(url, '.js') || _.endsWith(url, '.json')) {
    return removeScript(record);
  }

  console.warn(`[hot] Unknown file type for module ${name}, cannot remove`);
}

function updateRecordAssetInDocument(record) {
  const {name, url} = record;

  if (!record.isTextFile) {
    // Nothing to do here
    return;
  }

  if (_.endsWith(url, '.css')) {
    return replaceStylesheet(record);
  }

  if (
    _.endsWith(url, '.js') ||
    _.endsWith(url, '.json')
  ) {
    return replaceScript(record);
  }

  console.warn(`[hot] Unknown file type for module ${name}, cannot update`);
}

function replaceStylesheet(record) {
  const {name, url} = record;

  const links = document.getElementsByTagName('link');

  let replaced = false;

  // Update any matching <link> element
  _.forEach(links, link => {
    const attributeName = link.getAttribute('data-unfort-name');
    if (attributeName === name) {
      link.href = url;
      replaced = true;
      return false;
    }
  });

  if (replaced) {
    return;
  }

  // It's a new file, so we need to add a new <link> element
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = url;
  link.setAttribute('data-unfort-name', name);
  // We insert new stylesheets at the top of the head as this
  // optimises for the common case where a stylesheet import is
  // at the top of a file and its dependents may override the
  // selectors. If we don't do this, the cascade can get messed
  // up when you add new styles
  document.head.insertBefore(link, document.head.firstChild);
}

function removeStylesheet(record) {
  const {name} = record;

  const links = document.getElementsByTagName('link');

  _.forEach(links, link => {
    const attributeName = link.getAttribute('data-unfort-name');
    if (attributeName === name) {
      link.parentNode.removeChild(link);
      return false;
    }
  });
}

function replaceScript(record) {
  const {name, url} = record;

  // Clean up any pre-existing scripts
  removeScript(name);

  // Add a new <script> element
  const script = document.createElement('script');
  script.src = url;
  script.setAttribute('data-unfort-name', name);
  document.body.appendChild(script);
}

function removeScript(record) {
  const {name} = record;

  const scripts = document.getElementsByTagName('script');

  _.forEach(scripts, script => {
    const attributeName = script.getAttribute('data-unfort-name');
    if (attributeName === name) {
      script.parentNode.removeChild(script);
      return false;
    }
  });
}<commit_msg>Dead code removal<commit_after>// We reduce page load times by using pre-built and compressed
// libraries. This shaves around 500kb from the payload
import socketIoClient from '../vendor/socket.io-client';
import _ from '../vendor/lodash';

// Newer browsers support explicit prototype manipulation,
// but slightly older ones need a shim that uses the
// `__proto__` binding
let getPrototypeOf;
let setPrototypeOf;

if (_.isFunction(Object.getPrototypeOf)) {
  getPrototypeOf = Object.getPrototypeOf;
} else {
  getPrototypeOf = function fallbackGetPrototypeOf(obj) {
    return obj.__proto__;
  };
}

if (_.isFunction(Object.setPrototypeOf)) {
  setPrototypeOf = Object.setPrototypeOf;
} else {
  setPrototypeOf = function fallbackSetPrototypeOf(obj, proto) {
    obj.__proto__ = proto;
  };
}

// Older browsers tend to support varying degrees of prototype
// introspection and manipulation. As the hot runtime depends
// on being able to perform both, we run through a quick
// test-case so that we can warn when things are likely to break
(() => {
  const proto1 = {};
  const obj = _.create(proto1);

  if (getPrototypeOf(obj) !== proto1) {
    console.warn('[hot] Prototype introspection is not supported. The hot runtime should be removed');
    return;
  }

  const proto2 = {};
  setPrototypeOf(obj, proto2);
  if (getPrototypeOf(obj) !== proto2) {
    console.warn('[hot] Prototype manipulation is not supported. The hot runtime should be removed');
  }
})();

function log() {
  if (global.__QUIET_UNFORT__ !== true) {
    console.log.apply(console, arguments);
  }
}

// Before we start monkey-patching the runtime, we need to preserve some references
const extendModule = __modules.extendModule;
const defineModule = __modules.defineModule;

// Monkey-patch `extendModule` so that we can add the `hot` API
__modules.extendModule = function extendModuleHotWrapper(mod) {
  mod = extendModule(mod);

  // State associated with swapping the module
  if (mod.hot === undefined) {
    mod.hot = {
      // The previous state of the module
      previous: null,
      exportsProxy: {},
      accepted: false,
      acceptedCallback: null,
      onExit: null,
      exitData: undefined,
      onChanges: null
    };
  }

  if (mod.commonjs.hot === undefined) {
    // The `module.hot` API
    mod.commonjs.hot = {
      /**
       * `module.hot.accept`
       *
       * Indicate that a module will accept hot swaps. Accepts on optional
       * callback that will be triggered when a module has been removed.
       *
       * Note: this has some cross-over with the functionality of
       * `module.hot.exit`. This is provided mostly for compatibility with
       * webpack's HMR API.
       *
       * @param {Function} [cb]
       */
      accept(cb) {
        mod.hot.accepted = true;

        if (_.isFunction(cb)) {
          mod.hot.acceptedCallback = cb;
        }
      },
      /**
       * `module.hot.enter`
       *
       * If a module was swapped and the new version is being executed, a
       * callback passed to `module.hot.enter` will be called immediately
       * with one argument, the value returned from the previous version's
       * `module.hot.exit` callback.
       *
       * @param {Function} cb
       * @returns {*} returns the return value from `cb`
       */
      enter(cb) {
        if (!_.isFunction(cb)) {
          throw new Error(`module.hot.enter must be provided with a function. Received: ${cb}`);
        }

        const prevMod = mod.hot.previous;
        if (prevMod) {
          return cb(prevMod.hot.exitData);
        }
      },
      /**
       * `module.hot.exit`
       *
       * Indicates that a module will accept hot swaps and allows you to pass
       * data from one module version to another.
       *
       * Before a module is swapped, a callback passed to `module.hot.exit`
       * will be called and its return value will be stored for the next
       * version of the module.
       *
       * @param {Function} cb
       */
      exit(cb) {
        mod.hot.accepted = true;

        if (!_.isFunction(cb)) {
          throw new Error(`module.hot.exit must be provided with a function. Received: ${cb}`);
        }

        mod.hot.onExit = cb;
      },
      /**
       * `module.hot.changes`
       *
       * Allows you to execute a callback that will occur after all buffered
       * modules have been hot swapped.
       *
       * Note: if the module that specified the callback is one of the swapped
       * modules, the callback will *not* be called
       *
       * @param {Function} cb
       */
      changes(cb) {
        if (!_.isFunction(cb)) {
          throw new Error(`module.hot.changes must be provided with a function. Received: ${cb}`);
        }

        mod.hot.onChanges = cb;
      }
    };
  }

  return mod;
};

// Add the hot API to each module
_.forEach(__modules.modules, __modules.extendModule);

// There's a bit of complexity in the handling of changed assets.
// In particular, js modules that may depend on new dependencies
// introduce the potential for race conditions as we may or may
// not have a module available when we execute its dependent modules.
// To get around this, we buffer all the modules and only start to
// apply them once _.every pending module has been buffered
__modules.pending = {};
__modules.buffered = [];

// Monkey-patch `defineModule` so that we can intercept incoming modules
__modules.defineModule = function defineModuleHotWrapper(mod) {
  mod = __modules.extendModule(mod);

  const {name, hash} = mod;

  // Prevent unexpected modules from being applied
  if (__modules.pending[name] === undefined) {
    return log(
      `[hot] Attempted to add module "${name}", but it is not registered as pending and will be ignored`
    );
  }

  // Prevent an unexpected version from being applied
  if (__modules.pending[name] !== hash) {
    return log(
      `[hot] Unexpected update for module ${name}. Hash ${hash} does not reflect the expected hash ${__modules.pending[name]} and will be ignored`
    );
  }

  __modules.buffered.push(mod);
  __modules.pending[mod.name] = undefined;
  const readyToApply = _.every(__modules.pending, _.isUndefined);

  if (readyToApply) {
    __modules.pending = {};
    const _buffered = __modules.buffered;
    __modules.buffered = [];

    const modulesSwapped = Object.create(null);

    const toSwap = _buffered.map(mod => {
      modulesSwapped[mod.name] = true;
      return [mod, __modules.modules[mod.name]];
    });

    toSwap.forEach(([mod, prevMod]) => {
      const {name, hash} = mod;

      if (prevMod) {
        log(`[hot] Hot swapping ${name} from hash ${prevMod.hash} to hash ${hash}`);
      } else {
        log(`[hot] Initializing ${name} at hash ${hash}`);
      }

      if (prevMod) {
        // Trigger any callbacks passed to `module.hot.exit` and
        // store its return value for the next version
        if (prevMod.hot.onExit) {
          prevMod.hot.exitData = prevMod.hot.onExit();
        }

        // Prevent memory leaks by removing any references to past
        // versions of the module that is about to be swapped
        if (prevMod.hot.previous) {
          prevMod.hot.previous = null;
        }

        // We pass the exports proxy between module states so that dependent modules
        // have their references updated when we swap
        mod.hot.exportsProxy = prevMod.hot.exportsProxy;

        // Store the previous version of the module so that the next version
        // can introspect it
        mod.hot.previous = prevMod;
      }

      // Update the runtime's module registry
      defineModule(mod);
    });

    toSwap.forEach(([mod]) => {
      // If we're applying multiple modules, it's possible that new
      // modules may execute other new modules, so we need to iterate
      // through and selectively execute modules that have not been
      // called yet.
      if (!mod.executed) {
        // Note: if a module throws an error during execution, the entire
        // hot swap will fail with it. This is by design, as it prevents
        // unexpected behaviour
        __modules.executeModule(mod.name);
      }
    });

    // Trigger any callbacks passed to `module.hot.accept`
    toSwap.forEach(([_, prevMod]) => {
      if (prevMod && prevMod.hot.acceptedCallback) {
        prevMod.hot.acceptedCallback();
      }
    });

    _.forOwn(__modules.modules, mod => {
      // Handle modules which have been removed
      if (mod === undefined) {
        return;
      }

      // If a module was not swapped and it has specified a callback to
      // `module.hot.changes`, then we call it
      if (!modulesSwapped[mod.name] && mod.hot.onChanges) {
        mod.hot.onChanges();
      }
    });
  }
};

const executeModule = __modules.executeModule;
__modules.executeModule = function executeModuleHotWrapper(name) {
  executeModule(name);

  const mod = __modules.modules[name];
  const exports = mod.commonjs.exports;
  if (
    _.isObject(exports) &&
    !_.isFunction(exports) &&
    exports.__esModule
  ) {
    const exportsProxy = mod.hot.exportsProxy;

    if (getPrototypeOf(exportsProxy) !== exports) {
      setPrototypeOf(exportsProxy, exports);
      mod.commonjs.exports = exportsProxy;
    }
  }

  return mod.commonjs.exports;
};

const io = socketIoClient();

io.on('connect', () => {
  log('[hot] Connected');
});

io.on('unfort:build-started', () => {
  log('[hot] Build started');
});

io.on('unfort:build-error', err => {
  console.error(`[hot] Build error: ${err}`);
});

io.on('unfort:build-complete', ({records, removed}) => {
  // With the complete signal, we can start updating our assets
  // and begin the process of hot swapping code.

  const accepted = [];
  const unaccepted = [];

  _.forEach(records, (record, name) => {
    const mod = __modules.modules[name];

    // If it's a new module, we accept it
    if (!mod) {
      accepted.push(name);
      return;
    }

    // If the module is outdated, we check if we can update it
    if (mod.hash !== record.hash) {
      if (_.endsWith(name, '.css') || !record.isTextFile) {
        // As css and binary files are stateless, we can
        // blindly accept them
        accepted.push(name);
      } else if (mod.hot.accepted) {
        accepted.push(name);
      } else {
        unaccepted.push(name);
      }
    }
  });

  // If there were any unaccepted modules, we refuse to apply any changes
  if (unaccepted.length) {
    let message = `[hot] Cannot accept any changes as the following modules have not accepted hot swaps:\n${unaccepted.join('\n')}`;
    if (accepted.length) {
      message += `\n\nUpdates to the following modules have been blocked:\n${accepted.join('\n')}`;
    }
    return console.warn(message);
  }

  // We try to avoid issues from concurrent-ish updates by resetting
  // the pending state so that any calls to `defineModule` will be ignored
  __modules.pending = {};

  // If a module has already been buffered for execution, we can ignore
  // updates for it
  __modules.buffered = __modules.buffered.filter(({name, hash}) => {
    if (_.includes(accepted, name) && records[name].hash === hash) {
      _.pull(accepted, name);
      return true;
    }
    return false;
  });

  const modulesToRemove = _.keys(removed);
  if (modulesToRemove.length) {
    _.forEach(removed, (record, name) => {
      // We need to clear the state for any modules that have been removed
      // so that if they are re-added, they are executed again. This could
      // cause some issues for crazily stateful js, but it's needed to ensure
      // that css changes are always applied
      __modules.modules[name] = undefined;

      // Ensure that the asset is removed from the document
      removeRecordAssetFromDocument(record);
    });
    log(`[hot] Removed modules:\n${modulesToRemove.join('\n')}`);
  }

  if (!accepted.length) {
    return log('[hot] No updates to apply');
  }

  accepted.forEach(name => {
    const record = records[name];

    // Asynchronously fetch the asset
    updateRecordAssetInDocument(record);

    // Ensure that the runtime knows that we are waiting for this specific
    // versions of the module. We need to keep this synced so that we can
    // clear the buffered modules only when it's appropriate to
    __modules.pending[record.name] = record.hash;
  });

  // Note: this iteration *must* occur after `__modules.pending` has been
  // configured. Otherwise, `__modules.defineModule` will give strange
  // responses if we are trying to update multiple assets that include
  // css files
  accepted.forEach(name => {
    const record = records[name];

    // As css assets wont trigger a call to `defineModule`, we need to manually
    // call it to ensure that our module buffer is inevitably cleared and
    // the runtime's module registry is updated. This prevents an issue where
    // reverting a css asset to a previous version may have no effect as the
    // registry assumes it's already been applied
    if (
      _.endsWith(record.url, '.css') ||
      !record.isTextFile
    ) {
      __modules.defineModule({
        name: record.name,
        hash: record.hash,
        deps: {},
        // Note: the factory is defined outside of this closure to prevent the
        // signal payload from sitting in memory
        factory: createRecordUrlModule(record.url)
      });
    }
  });
});

function createRecordUrlModule(url) {
  return function recordUrlModule(module, exports) {
    exports.default = url;
    exports.__esModule = true;
    if (module.hot) {
      module.hot.accept();
    }
  };
}

function removeRecordAssetFromDocument(record) {
  const {name, url} = record;

  if (!record.isTextFile) {
    // Nothing to do here
    return;
  }

  if (_.endsWith(url, '.css')) {
    return removeStylesheet(record);
  }

  if (_.endsWith(url, '.js') || _.endsWith(url, '.json')) {
    return removeScript(record);
  }

  console.warn(`[hot] Unknown file type for module ${name}, cannot remove`);
}

function updateRecordAssetInDocument(record) {
  const {name, url} = record;

  if (!record.isTextFile) {
    // Nothing to do here
    return;
  }

  if (_.endsWith(url, '.css')) {
    return replaceStylesheet(record);
  }

  if (
    _.endsWith(url, '.js') ||
    _.endsWith(url, '.json')
  ) {
    return replaceScript(record);
  }

  console.warn(`[hot] Unknown file type for module ${name}, cannot update`);
}

function replaceStylesheet(record) {
  const {name, url} = record;

  const links = document.getElementsByTagName('link');

  let replaced = false;

  // Update any matching <link> element
  _.forEach(links, link => {
    const attributeName = link.getAttribute('data-unfort-name');
    if (attributeName === name) {
      link.href = url;
      replaced = true;
      return false;
    }
  });

  if (replaced) {
    return;
  }

  // It's a new file, so we need to add a new <link> element
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  link.href = url;
  link.setAttribute('data-unfort-name', name);
  // We insert new stylesheets at the top of the head as this
  // optimises for the common case where a stylesheet import is
  // at the top of a file and its dependents may override the
  // selectors. If we don't do this, the cascade can get messed
  // up when you add new styles
  document.head.insertBefore(link, document.head.firstChild);
}

function removeStylesheet(record) {
  const {name} = record;

  const links = document.getElementsByTagName('link');

  _.forEach(links, link => {
    const attributeName = link.getAttribute('data-unfort-name');
    if (attributeName === name) {
      link.parentNode.removeChild(link);
      return false;
    }
  });
}

function replaceScript(record) {
  const {name, url} = record;

  // Clean up any pre-existing scripts
  removeScript(name);

  // Add a new <script> element
  const script = document.createElement('script');
  script.src = url;
  script.setAttribute('data-unfort-name', name);
  document.body.appendChild(script);
}

function removeScript(record) {
  const {name} = record;

  const scripts = document.getElementsByTagName('script');

  _.forEach(scripts, script => {
    const attributeName = script.getAttribute('data-unfort-name');
    if (attributeName === name) {
      script.parentNode.removeChild(script);
      return false;
    }
  });
}<|endoftext|>


<commit_before>"use strict";

var debug = require('debug')('sira:app');
var assert = require('assert');
var util = require('util');
var Emitter = require('events').EventEmitter;
var bootable = require('bootable');
var middist = require('middist');
var Remotes = require('strong-remoting');
var _ = require('lodash');
var delegate = require('delegates');

var Registry = require('./registry');
var context = require('./context');
var Dispatcher = require('./dispatcher');

module.exports = Application;

function Application() {
    if (!(this instanceof Application)) return new Application();

    Emitter.call(this);

    this.__initializer = new bootable.Initializer();
    this.__middist = middist();
    this.__postprocessors = new Processors();

    this.remotes = undefined;

    var registry = this.registry = new Registry(this);
    registry.use(require('./model/model'));
    registry.use(require('./model/hook'));
    registry.use(require('./model/exposable'));

    this.context = Object.create(context);

}

util.inherits(Application, Emitter);

Application.prototype.__defineGetter__('dispatcher', function () {
    if (!this.__dispatcher) {
        this.__dispatcher = new Dispatcher(this.remotes);
    }
    return this.__dispatcher.middleware();
});

Application.prototype.init = function (options) {
    this.options = options = options || {};

    this.remotes = new Remotes(options['remoting']);
};

Application.prototype.postprocess = function () {
    this.__postprocessors.process(this);

    var models = this.models;

    for (var name in models) {
        var Model = models[name];
        var isPublic = true;
        if (Model.settings.hasOwnProperty('public')) {
            isPublic = Model.settings.public;
        }
        if (isPublic && Model.sharedClass) {
            this.remotes.addClass(Model.sharedClass);
            if (Model.settings.trackChanges && Model.Change) {
                this.remotes.addClass(Model.Change.sharedClass);
            }
        }
    }

    var dispatcher = this.dispatcher;
    var found = _.find(this.__middist.stack, function (layer) {
        return layer.handle === dispatcher;
    });
    if (!found) this.use(dispatcher);
};

/**
 * Register a boot phase.
 *
 * When an application boots, it proceeds through a series of phases, ultimately
 * resulting in a listening server ready to handle requests.
 *
 * A phase can be either synchronous or asynchronous.  Synchronous phases have
 * following function signature
 *
 *     function myPhase() {
 *       // perform initialization
 *     }
 *
 * Asynchronous phases have the following function signature.
 *
 *     function myAsyncPhase(done) {
 *       // perform initialization
 *       done();  // or done(err);
 *     }
 *
 * @param {Function|Function[]} fns
 * @api public
 */
Application.prototype.phase = function (fns) {
    if (!Array.isArray(fns)) fns = [fns];
    for (var i = 0; i < fns.length; i++) {
        this.__initializer.phase(fns[i]);
    }
    return this;
};

Application.prototype.model = function (model, caseSensitive) {
    if (typeof model === 'function') {
        throw new Error('`model` must not be string');
    }
    if (!caseSensitive) {
        model = model.toLowerCase();
    }
    var foundModel;
    for (var i in this.models) {
        if (model === i || !caseSensitive && model === i.toLowerCase()) {
            foundModel = this.models[i];
        }
    }
    return foundModel;
};

/**
 * Use the given middleware `fn`.
 *
 * @param {Function} fn
 * @return {Application} self
 * @api public
 */

Application.prototype.use = function (fn) {
    assert(typeof fn === 'function', 'app.use() requires a function');
    debug('use %s', fn._name || fn.name || '-');
    this.__middist.use(fn);
    return this;
};

Application.prototype.handle = function (req, cb) {
    var ctx = this.createContext(req, context);
    this.__middist.handle(ctx, cb);
};

/**
 * Initialize a new context.
 *
 * @api private
 */

Application.prototype.createContext = function (req, ctx) {
    var context = Object.create(this.context);
    if (!ctx) _.assign(context, ctx);
    var request = context.request = context.req = Object.create(req);
    context.app = request.app = this;
    request.ctx = context;
    for (var m in this.models) context[m] = this.models[m];
    return context;
};

/**
 * Boot `Sira` application.
 *
 * Locomotive builds on Express, providing a set of conventions for how to
 * organize code and resources on the file system as well as an MVC architecture
 * for structuring code.
 *
 * When booting a Locomotive application, the file system conventions are used
 * to initialize modules, configure the environment, register controllers, and
 * draw routes.  When complete, `callback` is invoked with an initialized
 * `express` instance that can listen for requests or be mounted in a larger
 * application.
 *
 * @param {Object|Function} [options]
 * @param {Function} [cb]
 * @api public
 */
Application.prototype.boot = function (options, cb) {
    if (typeof options === "function") {
        cb = options;
        options = null;
    }
    options = options || {};
    cb = cb || function () {
    };

    var self = this;
    this.init(options);
    this.__initializer.run(function (err) {
        if (err) return cb(err);
        self.postprocess();
        self.emit('ready', self);
        cb();
    }, this);
};

delegate(Application.prototype, 'registry')
    .getter('models')
    .getter('schemas');

function Processors() {
    if (!(this instanceof Processors)) return new Processors();
    this.processors = [];
}

Processors.prototype.add = function (fn) {
    this.processors.push(fn);
};

Processors.prototype.process = function (thisArg) {
    for (var i = 0; i < this.processors.length; i++) {
        this.processors[i].call(thisArg);
    }
    this.processors = [];
};<commit_msg>throw error if no callback provided in boot<commit_after>"use strict";

var debug = require('debug')('sira:app');
var assert = require('assert');
var util = require('util');
var Emitter = require('events').EventEmitter;
var bootable = require('bootable');
var middist = require('middist');
var Remotes = require('strong-remoting');
var _ = require('lodash');
var delegate = require('delegates');

var Registry = require('./registry');
var context = require('./context');
var Dispatcher = require('./dispatcher');

module.exports = Application;

function Application() {
    if (!(this instanceof Application)) return new Application();

    Emitter.call(this);

    this.__initializer = new bootable.Initializer();
    this.__middist = middist();
    this.__postprocessors = new Processors();

    this.remotes = undefined;

    var registry = this.registry = new Registry(this);
    registry.use(require('./model/model'));
    registry.use(require('./model/hook'));
    registry.use(require('./model/exposable'));

    this.context = Object.create(context);

}

util.inherits(Application, Emitter);

Application.prototype.__defineGetter__('dispatcher', function () {
    if (!this.__dispatcher) {
        this.__dispatcher = new Dispatcher(this.remotes);
    }
    return this.__dispatcher.middleware();
});

Application.prototype.init = function (options) {
    this.options = options = options || {};

    this.remotes = new Remotes(options['remoting']);
};

Application.prototype.postprocess = function () {
    this.__postprocessors.process(this);

    var models = this.models;

    for (var name in models) {
        var Model = models[name];
        var isPublic = true;
        if (Model.settings.hasOwnProperty('public')) {
            isPublic = Model.settings.public;
        }
        if (isPublic && Model.sharedClass) {
            this.remotes.addClass(Model.sharedClass);
            if (Model.settings.trackChanges && Model.Change) {
                this.remotes.addClass(Model.Change.sharedClass);
            }
        }
    }

    var dispatcher = this.dispatcher;
    var found = _.find(this.__middist.stack, function (layer) {
        return layer.handle === dispatcher;
    });
    if (!found) this.use(dispatcher);
};

/**
 * Register a boot phase.
 *
 * When an application boots, it proceeds through a series of phases, ultimately
 * resulting in a listening server ready to handle requests.
 *
 * A phase can be either synchronous or asynchronous.  Synchronous phases have
 * following function signature
 *
 *     function myPhase() {
 *       // perform initialization
 *     }
 *
 * Asynchronous phases have the following function signature.
 *
 *     function myAsyncPhase(done) {
 *       // perform initialization
 *       done();  // or done(err);
 *     }
 *
 * @param {Function|Function[]} fns
 * @api public
 */
Application.prototype.phase = function (fns) {
    if (!Array.isArray(fns)) fns = [fns];
    for (var i = 0; i < fns.length; i++) {
        this.__initializer.phase(fns[i]);
    }
    return this;
};

Application.prototype.model = function (model, caseSensitive) {
    if (typeof model === 'function') {
        throw new Error('`model` must not be string');
    }
    if (!caseSensitive) {
        model = model.toLowerCase();
    }
    var foundModel;
    for (var i in this.models) {
        if (model === i || !caseSensitive && model === i.toLowerCase()) {
            foundModel = this.models[i];
        }
    }
    return foundModel;
};

/**
 * Use the given middleware `fn`.
 *
 * @param {Function} fn
 * @return {Application} self
 * @api public
 */

Application.prototype.use = function (fn) {
    assert(typeof fn === 'function', 'app.use() requires a function');
    debug('use %s', fn._name || fn.name || '-');
    this.__middist.use(fn);
    return this;
};

Application.prototype.handle = function (req, cb) {
    var ctx = this.createContext(req, context);
    this.__middist.handle(ctx, cb);
};

/**
 * Initialize a new context.
 *
 * @api private
 */

Application.prototype.createContext = function (req, ctx) {
    var context = Object.create(this.context);
    if (!ctx) _.assign(context, ctx);
    var request = context.request = context.req = Object.create(req);
    context.app = request.app = this;
    request.ctx = context;
    for (var m in this.models) context[m] = this.models[m];
    return context;
};

/**
 * Boot `Sira` application.
 *
 * Locomotive builds on Express, providing a set of conventions for how to
 * organize code and resources on the file system as well as an MVC architecture
 * for structuring code.
 *
 * When booting a Locomotive application, the file system conventions are used
 * to initialize modules, configure the environment, register controllers, and
 * draw routes.  When complete, `callback` is invoked with an initialized
 * `express` instance that can listen for requests or be mounted in a larger
 * application.
 *
 * @param {Object|Function} [options]
 * @param {Function} [cb]
 * @api public
 */
Application.prototype.boot = function (options, cb) {
    if (typeof options === "function") {
        cb = options;
        options = null;
    }
    options = options || {};

    var self = this;
    this.init(options);
    this.__initializer.run(function (err) {
        if (err) {
            if (!cb) throw err;
            return cb(err);
        }
        self.postprocess();
        self.emit('ready', self);
        cb && cb();
    }, this);
};

delegate(Application.prototype, 'registry')
    .getter('models')
    .getter('schemas');

function Processors() {
    if (!(this instanceof Processors)) return new Processors();
    this.processors = [];
}

Processors.prototype.add = function (fn) {
    this.processors.push(fn);
};

Processors.prototype.process = function (thisArg) {
    for (var i = 0; i < this.processors.length; i++) {
        this.processors[i].call(thisArg);
    }
    this.processors = [];
};<|endoftext|>