<commit_before>(function(window) {

  function now() {
    if(window.performance) {
      return window.performance.now();
    }
    return (new Date()).getTime();
  }

  function Renderer() {
    this.content = document.createElement("div");
    this.content.className = "__root";
    this.elementCache = {"__root": this.content};
    this.prevTree = {};
    this.tree = {};
    this.postRenders = [];
    this.lastDiff = {};
    var self = this;
    this.handleEvent = function handleEvent(e) {
      var id = e.currentTarget._id;
      var elem = self.tree[id];
      if(!elem) return;
      var handler = elem[e.type];
      if(handler) { handler(e, elem); }
    };
  }

  Renderer.prototype = {
    reset: function() {
      this.prevTree = this.tree;
      this.tree = {};
    },

    domify: function domify() {
      var fakePrev = {}; //create an empty object once instead of every instance of the loop
      var elements = this.tree;
      var prevElements = this.prevTree;
      var diff = this.lastDiff;
      var elemKeys = Object.keys(diff);
      var elementCache = this.elementCache;
      for(var i = 0, len = elemKeys.length; i < len; i++) {
        var id = elemKeys[i];
        var cur = elements[id];
        var prev = prevElements[id] || fakePrev;
        var type = diff[id];
        var div;
        if(type === "replaced") {
          var me = elementCache[id];
          if(me.parentNode) me.parentNode.removeChild(me);
          div = document.createElement(cur.t || "div");
          div._id = id;
          elementCache[id] = div;
        } else if(type === "added") {
          div = document.createElement(cur.t || "div");
          div._id = id;
          elementCache[id] = div;
        } else if(type === "updated") {
          div = elementCache[id];
        } else {
          //NOTE: Batching the removes such that you only remove the parent
          //didn't actually make this faster surprisingly. Given that this
          //strategy is much simpler and there's no noticable perf difference
          //we'll just do the dumb thing and remove all the children one by one.
          var me = elementCache[id]
          if(me.parentNode) me.parentNode.removeChild(me);
          elementCache[id] = null;
          continue;
        }

        style = div.style;

        if(cur.c !== prev.c) div.className = cur.c;
        if(cur.draggable !== prev.draggable) div.draggable = cur.draggable;
        if(cur.contentEditable !== prev.contentEditable) div.contentEditable = cur.contentEditable || "inherit";
        if(cur.colspan !== prev.colspan) div.colSpan = cur.colspan;

        if(cur.left !== prev.left)  style.left = cur.left;
        if(cur.top !== prev.top) style.top = cur.top;
        if(cur.height !== prev.height) style.height = cur.height;
        if(cur.width !== prev.width)  style.width = cur.width;
        if(cur.zIndex !== prev.zIndex) style.zIndex = cur.zIndex;

        if(cur.backgroundColor !== prev.backgroundColor) style.backgroundColor = cur.backgroundColor;
        if(cur.backgroundImage !== prev.backgroundImage) {
          style.backgroundImage = "url('" + cur.backgroundImage + "')";
        }
        if(cur.border !== prev.border) style.border = cur.border;
        if(cur.borderRadius !== prev.borderRadius) style.borderRadius = cur.borderRadius;
        if(cur.fontSize !== prev.fontSize) style.fontSize = cur.fontSize;
        if(cur.textAlign !== prev.textAlign) style.justifyContent = cur.textAlign;
        if(cur.verticalAlign !== prev.verticalAlign) style.alignItems = cur.verticalAlign;
        if(cur.color !== prev.color) style.color = cur.color;
        if(cur.fontFamily !== prev.fontFamily) style.fontFamily = cur.fontFamily;
        if(cur.value !== prev.value) div.value = cur.value;
        if(cur.t === "input" && cur.type !== prev.type) div.type = cur.type;
        if(cur.text !== prev.text) div.textContent = cur.text;

        //events
        if(cur.click !== prev.click) div.onclick = cur.click !== undefined ? this.handleEvent : undefined;
        if(cur.contextmenu !== prev.contextmenu) div.oncontextmenu = cur.contextmenu !== undefined ? this.handleEvent : undefined;
        if(cur.mousedown !== prev.mousedown) div.onmousedown = cur.mousedown !== undefined ? this.handleEvent : undefined;
        if(cur.mousewheel !== prev.mousewheel) div.onmouseheel = cur.mousewheel !== undefined ? this.handleEvent : undefined;
        if(cur.dragover !== prev.dragover) div.ondragover = cur.dragover !== undefined ? this.handleEvent : undefined;
        if(cur.dragstart !== prev.dragstart) div.ondragstart = cur.dragstart !== undefined ? this.handleEvent : undefined;
        if(cur.dragend !== prev.dragend) div.ondragend = cur.dragend !== undefined ? this.handleEvent : undefined;
        if(cur.drag !== prev.drag) div.ondrag = cur.drag !== undefined ? this.handleEvent : undefined;
        if(cur.drop !== prev.drop) div.ondrop = cur.drop !== undefined ? this.handleEvent : undefined;
        if(cur.scroll !== prev.scroll) div.onscroll = cur.scroll !== undefined ? this.handleEvent : undefined;
        if(cur.focus !== prev.focus) div.onfocus = cur.focus !== undefined ? this.handleEvent : undefined;
        if(cur.blur !== prev.blur) div.onblur = cur.blur !== undefined ? this.handleEvent : undefined;
        if(cur.input !== prev.input) div.oninput = cur.input !== undefined ? this.handleEvent : undefined;
        if(cur.keydown !== prev.keydown) div.onkeydown = cur.keydown !== undefined ? this.handleEvent : undefined;

        if(type === "added" || type === "replaced") {
          //TODO: we aren't inserting in order.
          elementCache[cur.parent].appendChild(div);
        }
      }
    },

    diff: function diff() {
      var a = this.prevTree;
      var b = this.tree;
      var as = Object.keys(a);
      var bs = Object.keys(b);
      var updated = {};
      for(var i = 0, len = as.length; i < len; i++) {
        var curA = a[as[i]];
        var curB = b[as[i]];
        if(curB === undefined) {
          updated[as[i]] = "removed";
          continue;
        }
        if(curA.t !== curB.t) {
          updated[as[i]] = "replaced";
          continue;
        }
        if(curA.c === curB.c
           && curA.value === curB.value
           && curA.text === curB.text
           && curA.top === curB.top
           && curA.left === curB.left
           && curA.width === curB.width
           && curA.height === curB.height
           && curA.zIndex === curB.zIndex
           && curA.backgroundColor === curB.backgroundColor
           && curA.backgroundImage === curB.backgroundImage
           && curA.color === curB.color
           && curA.border === curB.border
           && curA.borderRadius === curB.borderRadius
           && curA.fontFamily === curB.fontFamily
           && curA.fontSize === curB.fontSize
           && curA.textAlign === curB.textAlign
           && curA.verticalAlign === curB.verticalAlign) {
          continue;
        }
        updated[as[i]] = "updated";
      }
      for(var i = 0, len = bs.length; i < len; i++) {
        var curA = a[bs[i]];
        var curB = b[bs[i]];
        if(curA === undefined) {
          updated[bs[i]] = "added";
          continue;
        }
        if(curA.t !== curB.t) {
          updated[bs[i]] = "replaced";
          continue;
        }
        if(curA.c === curB.c
           && curA.value === curB.value
           && curA.text === curB.text
           && curA.top === curB.top
           && curA.left === curB.left
           && curA.width === curB.width
           && curA.height === curB.height
           && curA.zIndex === curB.zIndex
           && curA.backgroundColor === curB.backgroundColor
           && curA.backgroundImage === curB.backgroundImage
           && curA.color === curB.color
           && curA.border === curB.border
           && curA.borderRadius === curB.borderRadius
           && curA.fontFamily === curB.fontFamily
           && curA.fontSize === curB.fontSize
           && curA.textAlign === curB.textAlign
           && curA.verticalAlign === curB.verticalAlign) {
          continue;
        }
        updated[bs[i]] = "updated";
      }
      this.lastDiff = updated;
      return updated;
    },

    prepare: function prepare(elem) {
      var tree = this.tree;
      if(!elem.parent) elem.parent = "__root";
      tree[elem.id] = elem;
      if(elem.postRender) {
        this.postRenders.push(elem);
      }
      var children = elem.children;
      if(children) {
        for(var childIx = 0, len = children.length; childIx < len; childIx++) {
          var child = children[childIx];
          if(child === undefined) continue;
          if(!child.id) { child.id = elem.id + "__" + childIx; }
          child.parent = elem.id;
          child.ix = childIx;
          this.prepare(child);
        }
      }
      return tree;
    },

    postDomify: function postRender() {
      var postRenders = this.postRenders;
      var diff = this.lastDiff;
      var elementCache = this.elementCache;
      for(var i = 0, len = postRenders.length; i < len; i++) {
        var elem = postRenders[i];
        var id = elem.id;
        if(diff[id] === "updated" || diff[id] === "added") {
          elem.postRender(elementCache[elem.id], elem);
        }
      }
    },

    render: function(elem) {
      var start = now();
      this.reset();
      var post = this.prepare(elem);
      var prepare = now();
      var d = this.diff();
      var diff = now();
      this.domify();
      var domify = now();
      this.postDomify();
      var postDomify = now();
      var time = now() - start;
      if(time > 5) {
        console.log("slow render (> 5ms): ", time, {prepare: prepare - start,
                                                    diff: diff - prepare,
                                                    domify: domify - diff,
                                                    postDomify: postDomify - domify});
      }
    }

  };

  function test() {
    console.time("build");
    var root = {children: [], id: "testRoot"};
    for(var i = 0; i < 100; i++) {
      var me = {id: "blah" + i};
      var children = [];
      for(var x = 0; x < 100; x++) {
        children[x] = {text: "foo" + x};
      }
      me.children = children;
      root.children[i] = me;
    }
    console.log(root);
    var tree = {};
    prepare(tree, root);
    console.timeEnd("build");
    console.time("diff scratch");
    var d = diff({}, tree);
    console.timeEnd("diff scratch");
    console.time("draw scratch");
    render(tree, d);
    console.timeEnd("draw scratch");
    console.time("diff equal");
    var d2 = diff(tree, tree);
    console.timeEnd("diff equal");
    console.time("draw equal");
    render(tree, d2);
    console.timeEnd("draw equal");
    return [d,d2];
  }

  window.microReact = {Renderer: Renderer};

})(window);
<commit_msg>Fixes child insertion ordering<commit_after>(function(window) {

  function now() {
    if(window.performance) {
      return window.performance.now();
    }
    return (new Date()).getTime();
  }

  function Renderer() {
    this.content = document.createElement("div");
    this.content.className = "__root";
    this.elementCache = {"__root": this.content};
    this.prevTree = {};
    this.tree = {};
    this.postRenders = [];
    this.lastDiff = {};
    var self = this;
    this.handleEvent = function handleEvent(e) {
      var id = e.currentTarget._id;
      var elem = self.tree[id];
      if(!elem) return;
      var handler = elem[e.type];
      if(handler) { handler(e, elem); }
    };
  }

  Renderer.prototype = {
    reset: function() {
      this.prevTree = this.tree;
      this.tree = {};
    },

    domify: function domify() {
      var fakePrev = {}; //create an empty object once instead of every instance of the loop
      var elements = this.tree;
      var prevElements = this.prevTree;
      var diff = this.lastDiff;
      var elemKeys = Object.keys(diff);
      var elementCache = this.elementCache;
      for(var i = 0, len = elemKeys.length; i < len; i++) {
        var id = elemKeys[i];
        var cur = elements[id];
        var prev = prevElements[id] || fakePrev;
        var type = diff[id];
        var div;
        if(type === "replaced") {
          var me = elementCache[id];
          if(me.parentNode) me.parentNode.removeChild(me);
          div = document.createElement(cur.t || "div");
          div._id = id;
          elementCache[id] = div;
        } else if(type === "added") {
          div = document.createElement(cur.t || "div");
          div._id = id;
          elementCache[id] = div;
        } else if(type === "updated") {
          div = elementCache[id];
        } else {
          //NOTE: Batching the removes such that you only remove the parent
          //didn't actually make this faster surprisingly. Given that this
          //strategy is much simpler and there's no noticable perf difference
          //we'll just do the dumb thing and remove all the children one by one.
          var me = elementCache[id]
          if(me.parentNode) me.parentNode.removeChild(me);
          elementCache[id] = null;
          continue;
        }

        style = div.style;

        if(cur.c !== prev.c) div.className = cur.c;
        if(cur.draggable !== prev.draggable) div.draggable = cur.draggable;
        if(cur.contentEditable !== prev.contentEditable) div.contentEditable = cur.contentEditable || "inherit";
        if(cur.colspan !== prev.colspan) div.colSpan = cur.colspan;

        if(cur.left !== prev.left)  style.left = cur.left;
        if(cur.top !== prev.top) style.top = cur.top;
        if(cur.height !== prev.height) style.height = cur.height;
        if(cur.width !== prev.width)  style.width = cur.width;
        if(cur.zIndex !== prev.zIndex) style.zIndex = cur.zIndex;

        if(cur.backgroundColor !== prev.backgroundColor) style.backgroundColor = cur.backgroundColor;
        if(cur.backgroundImage !== prev.backgroundImage) {
          style.backgroundImage = "url('" + cur.backgroundImage + "')";
        }
        if(cur.border !== prev.border) style.border = cur.border;
        if(cur.borderRadius !== prev.borderRadius) style.borderRadius = cur.borderRadius;
        if(cur.fontSize !== prev.fontSize) style.fontSize = cur.fontSize;
        if(cur.textAlign !== prev.textAlign) style.justifyContent = cur.textAlign;
        if(cur.verticalAlign !== prev.verticalAlign) style.alignItems = cur.verticalAlign;
        if(cur.color !== prev.color) style.color = cur.color;
        if(cur.fontFamily !== prev.fontFamily) style.fontFamily = cur.fontFamily;
        if(cur.value !== prev.value) div.value = cur.value;
        if(cur.t === "input" && cur.type !== prev.type) div.type = cur.type;
        if(cur.text !== prev.text) div.textContent = cur.text;

        //events
        if(cur.click !== prev.click) div.onclick = cur.click !== undefined ? this.handleEvent : undefined;
        if(cur.contextmenu !== prev.contextmenu) div.oncontextmenu = cur.contextmenu !== undefined ? this.handleEvent : undefined;
        if(cur.mousedown !== prev.mousedown) div.onmousedown = cur.mousedown !== undefined ? this.handleEvent : undefined;
        if(cur.mousewheel !== prev.mousewheel) div.onmouseheel = cur.mousewheel !== undefined ? this.handleEvent : undefined;
        if(cur.dragover !== prev.dragover) div.ondragover = cur.dragover !== undefined ? this.handleEvent : undefined;
        if(cur.dragstart !== prev.dragstart) div.ondragstart = cur.dragstart !== undefined ? this.handleEvent : undefined;
        if(cur.dragend !== prev.dragend) div.ondragend = cur.dragend !== undefined ? this.handleEvent : undefined;
        if(cur.drag !== prev.drag) div.ondrag = cur.drag !== undefined ? this.handleEvent : undefined;
        if(cur.drop !== prev.drop) div.ondrop = cur.drop !== undefined ? this.handleEvent : undefined;
        if(cur.scroll !== prev.scroll) div.onscroll = cur.scroll !== undefined ? this.handleEvent : undefined;
        if(cur.focus !== prev.focus) div.onfocus = cur.focus !== undefined ? this.handleEvent : undefined;
        if(cur.blur !== prev.blur) div.onblur = cur.blur !== undefined ? this.handleEvent : undefined;
        if(cur.input !== prev.input) div.oninput = cur.input !== undefined ? this.handleEvent : undefined;
        if(cur.keydown !== prev.keydown) div.onkeydown = cur.keydown !== undefined ? this.handleEvent : undefined;

        if(type === "added" || type === "replaced") {
          var parentEl = elementCache[cur.parent];
          if(cur.ix >= parentEl.children.length) {
            parentEl.appendChild(div);
          } else {
            parentEl.insertBefore(div, parentEl.children[cur.ix]);
          }
        }
      }
    },

    diff: function diff() {
      var a = this.prevTree;
      var b = this.tree;
      var as = Object.keys(a);
      var bs = Object.keys(b);
      var updated = {};
      for(var i = 0, len = as.length; i < len; i++) {
        var curA = a[as[i]];
        var curB = b[as[i]];
        if(curB === undefined) {
          updated[as[i]] = "removed";
          continue;
        }
        if(curA.t !== curB.t) {
          updated[as[i]] = "replaced";
          continue;
        }
        if(curA.c === curB.c
           && curA.value === curB.value
           && curA.text === curB.text
           && curA.top === curB.top
           && curA.left === curB.left
           && curA.width === curB.width
           && curA.height === curB.height
           && curA.zIndex === curB.zIndex
           && curA.backgroundColor === curB.backgroundColor
           && curA.backgroundImage === curB.backgroundImage
           && curA.color === curB.color
           && curA.border === curB.border
           && curA.borderRadius === curB.borderRadius
           && curA.fontFamily === curB.fontFamily
           && curA.fontSize === curB.fontSize
           && curA.textAlign === curB.textAlign
           && curA.verticalAlign === curB.verticalAlign) {
          continue;
        }
        updated[as[i]] = "updated";
      }
      for(var i = 0, len = bs.length; i < len; i++) {
        var curA = a[bs[i]];
        var curB = b[bs[i]];
        if(curA === undefined) {
          updated[bs[i]] = "added";
          continue;
        }
        if(curA.t !== curB.t) {
          updated[bs[i]] = "replaced";
          continue;
        }
        if(curA.c === curB.c
           && curA.value === curB.value
           && curA.text === curB.text
           && curA.top === curB.top
           && curA.left === curB.left
           && curA.width === curB.width
           && curA.height === curB.height
           && curA.zIndex === curB.zIndex
           && curA.backgroundColor === curB.backgroundColor
           && curA.backgroundImage === curB.backgroundImage
           && curA.color === curB.color
           && curA.border === curB.border
           && curA.borderRadius === curB.borderRadius
           && curA.fontFamily === curB.fontFamily
           && curA.fontSize === curB.fontSize
           && curA.textAlign === curB.textAlign
           && curA.verticalAlign === curB.verticalAlign) {
          continue;
        }
        updated[bs[i]] = "updated";
      }
      this.lastDiff = updated;
      return updated;
    },

    prepare: function prepare(elem) {
      var tree = this.tree;
      if(!elem.parent) elem.parent = "__root";
      tree[elem.id] = elem;
      if(elem.postRender) {
        this.postRenders.push(elem);
      }
      var children = elem.children;
      if(children) {
        for(var childIx = 0, len = children.length; childIx < len; childIx++) {
          var child = children[childIx];
          if(child === undefined) continue;
          if(!child.id) { child.id = elem.id + "__" + childIx; }
          child.parent = elem.id;
          child.ix = childIx;
          this.prepare(child);
        }
      }
      return tree;
    },

    postDomify: function postRender() {
      var postRenders = this.postRenders;
      var diff = this.lastDiff;
      var elementCache = this.elementCache;
      for(var i = 0, len = postRenders.length; i < len; i++) {
        var elem = postRenders[i];
        var id = elem.id;
        if(diff[id] === "updated" || diff[id] === "added") {
          elem.postRender(elementCache[elem.id], elem);
        }
      }
    },

    render: function(elem) {
      var start = now();
      this.reset();
      var post = this.prepare(elem);
      var prepare = now();
      var d = this.diff();
      var diff = now();
      this.domify();
      var domify = now();
      this.postDomify();
      var postDomify = now();
      var time = now() - start;
      if(time > 5) {
        console.log("slow render (> 5ms): ", time, {prepare: prepare - start,
                                                    diff: diff - prepare,
                                                    domify: domify - diff,
                                                    postDomify: postDomify - domify});
      }
    }

  };

  function test() {
    console.time("build");
    var root = {children: [], id: "testRoot"};
    for(var i = 0; i < 100; i++) {
      var me = {id: "blah" + i};
      var children = [];
      for(var x = 0; x < 100; x++) {
        children[x] = {text: "foo" + x};
      }
      me.children = children;
      root.children[i] = me;
    }
    console.log(root);
    var tree = {};
    prepare(tree, root);
    console.timeEnd("build");
    console.time("diff scratch");
    var d = diff({}, tree);
    console.timeEnd("diff scratch");
    console.time("draw scratch");
    render(tree, d);
    console.timeEnd("draw scratch");
    console.time("diff equal");
    var d2 = diff(tree, tree);
    console.timeEnd("diff equal");
    console.time("draw equal");
    render(tree, d2);
    console.timeEnd("draw equal");
    return [d,d2];
  }

  window.microReact = {Renderer: Renderer};

})(window);
<|endoftext|>


<commit_before>const path = require("path");
const webpack = require("webpack");

const FlowtypePlugin = require('flowtype-loader/plugin');

const config = {
  devtool: "#source-maps",
  entry: "./src/App.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "App.js"
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        loader: 'flowtype-loader',
        enforce: 'pre',
        exclude: /node_modules/
      },
      {
        test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        options: {
          presets: ["es2015", "react"],
          plugins: ["transform-flow-strip-types", "transform-class-properties"]
        },
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      },
      {
        test: /\.(eot|woff|woff2|ttf|svg|png|jpg)$/,
        use: ["url-loader"]
      }
    ]
  },
  plugins: [
    new webpack.ProvidePlugin({
      'jQuery': 'jquery'
    }),
    new FlowtypePlugin()
  ],
  devServer: {
    contentBase: path.join(__dirname, "dist")
  },
}

module.exports = config;
<commit_msg>Fix react router hot reload of routes in dev mode<commit_after>const path = require("path");
const webpack = require("webpack");

const FlowtypePlugin = require('flowtype-loader/plugin');

const config = {
  devtool: "#source-maps",
  entry: "./src/App.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "App.js"
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        loader: 'flowtype-loader',
        enforce: 'pre',
        exclude: /node_modules/
      },
      {
        test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        options: {
          presets: ["es2015", "react"],
          plugins: ["transform-flow-strip-types", "transform-class-properties"]
        },
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      },
      {
        test: /\.(eot|woff|woff2|ttf|svg|png|jpg)$/,
        use: ["url-loader"]
      }
    ]
  },
  plugins: [
    new webpack.ProvidePlugin({
      'jQuery': 'jquery'
    }),
    new FlowtypePlugin()
  ],
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    historyApiFallback: true
  },
}

module.exports = config;
<|endoftext|>


<commit_before>/* global Event Movement */
'use strict';

var DateRange = require('../lib/DateRange');
var moment = require('moment');

const fullRange = (visibilityRange, rangeFactory) =>
  DateRange.widen.apply(null, [visibilityRange, rangeFactory(visibilityRange.from), rangeFactory(visibilityRange.to)]);

let populate = (model, centreId, range) =>
  model.find({
    where: {
      centre: centreId,
      timestamp: { '>=': range.from, '<=': range.to }
    },
    sort: 'timestamp'
  });

const populateEvents = (centre, range) =>
  populate(Event, centre.id, range)
    .populate('detainee')
    .then(events => {
      events.forEach((event) => {
        if (event.operation === Event.OPERATION_REINSTATEMENT) {
          centre.unreconciledReinstatements.push(event);
        } else {
          centre.unreconciledEvents.push(event);
        }
      });
    });

const populateMovements = (centre, range) =>
  populate(Movement, centre.id, range)
    .then(movements => centre.unreconciledMovements = movements);

const resolveEventOperationWithMovementDirection = (operation) => {
  switch (operation) {
  case 'check in':
    return 'in';
  case 'check out':
    return 'out';
  default:
    return 'unknown';
  }
};

const getReconciliationTester = (movementsRangeFactory, eventsRangeFactory) => (event, movement) => {
  const cidMatches = movement.cid_id === event.detainee.cid_id;
  const directionMatches = resolveEventOperationWithMovementDirection(event.operation) === movement.direction;
  const timestampMatches = movementsRangeFactory(event.timestamp).contains(movement.timestamp) || eventsRangeFactory(movement.timestamp).contains(event.timestamp);

  return cidMatches && directionMatches && timestampMatches && { event, movement };
};

const filterUnreconciled = (centre, range) => {
  centre.unreconciledEvents = centre.unreconciledEvents.filter(event => range.contains(event.timestamp));
  centre.unreconciledMovements = centre.unreconciledMovements.filter(movement => range.contains(movement.timestamp));
};

const getReinstatementTester = (checkoutEventsRangeFactory) => (reinstatement, event) => {
  const operationMatches = event.operation === Event.OPERATION_CHECK_OUT;
  const personIdMatches = reinstatement.person_id === event.person_id;
  const timestampMatches = checkoutEventsRangeFactory(reinstatement.timestamp).contains(event.timestamp);

  return operationMatches && personIdMatches && timestampMatches && { reinstatement, event };
};

const untersect = (left, right, mapper) => {
  const matches = [];
  const rightExclusive = right.slice(0);

  const leftExclusive = left.filter((leftThing) =>
    !rightExclusive.some((rightThing, rightKey) => {
      const result = mapper(leftThing, rightThing);
      if (result) {
        matches.push(result);
        rightExclusive.splice(rightKey, 1);
        return true;
      }
      return false;
    })
  );
  return [leftExclusive, matches, rightExclusive];
};

const reconcileEvents = (centre, tester) => {
  const result = untersect(centre.unreconciledEvents, centre.unreconciledMovements, tester);
  centre.unreconciledEvents = result[0];
  centre.reconciled = result[1];
  centre.unreconciledMovements = result[2];
  return centre;
};

const handleReinstatements = (centre, tester) => {
  const result = untersect(centre.unreconciledReinstatements, centre.unreconciledEvents, tester);
  centre.unreconciledReinstatements = result[0];
  centre.reinstatements = result[1];
  centre.unreconciledEvents = result[2];
  return centre;
};

module.exports = {
  performReconciliation: (centre, visibilityRange, eventSearchDateRangeFactory, movementSearchDateRangeFactory, checkOutSearchDateRangeFactory) => {
    const rangeOfEvents = fullRange(visibilityRange, eventSearchDateRangeFactory);
    const rangeOfMovements = fullRange(visibilityRange, movementSearchDateRangeFactory);
    const reconciler = getReconciliationTester(movementSearchDateRangeFactory, eventSearchDateRangeFactory);
    const reinstatementReconciler = getReinstatementTester(checkOutSearchDateRangeFactory);

    centre.unreconciledEvents = [];
    centre.unreconciledMovements = [];
    centre.unreconciledReinstatements = [];
    centre.reconciled = [];
    centre.reinstatements = [];

    return populateEvents(centre, rangeOfEvents)
      .then(() => populateMovements(centre, rangeOfMovements))
      .then(() => handleReinstatements(centre, reinstatementReconciler))
      .then(() => reconcileEvents(centre, reconciler))
      .then(() => filterUnreconciled(centre, visibilityRange))
      .return(centre);
  },
  performConfiguredReconciliation: function (centre) {
    const visibilityRange = new DateRange(moment().subtract(2, 'days').toDate(), moment().toDate());
    const eventsFromMovementDateRangeFactory = (date) => new DateRange(
      moment(date).startOf('day').toDate(),
      moment(date).add(2, 'days').endOf('day').toDate()
    );
    const movementsFromEventDateRangeFactory = (date) => new DateRange(
      moment(date).subtract(2, 'days').startOf('day').toDate(),
      moment(date).endOf('day').toDate()
    );
    const checkOutFromReinstatementDateRangeFactory = (date) => new DateRange(
      moment(date).subtract(1, 'days').toDate(),
      moment(date).toDate()
    );
    return this.performReconciliation(
      centre,
      visibilityRange,
      eventsFromMovementDateRangeFactory,
      movementsFromEventDateRangeFactory,
      checkOutFromReinstatementDateRangeFactory
    );
  }
};
<commit_msg>IBM-116 Updated Bed Count Config<commit_after>/* global Event Movement */
'use strict';

var DateRange = require('../lib/DateRange');
var moment = require('moment');

const fullRange = (visibilityRange, rangeFactory) =>
  DateRange.widen.apply(null, [visibilityRange, rangeFactory(visibilityRange.from), rangeFactory(visibilityRange.to)]);

let populate = (model, centreId, range) =>
  model.find({
    where: {
      centre: centreId,
      timestamp: { '>=': range.from, '<=': range.to }
    },
    sort: 'timestamp'
  });

const populateEvents = (centre, range) =>
  populate(Event, centre.id, range)
    .populate('detainee')
    .then(events => {
      events.forEach((event) => {
        if (event.operation === Event.OPERATION_REINSTATEMENT) {
          centre.unreconciledReinstatements.push(event);
        } else {
          centre.unreconciledEvents.push(event);
        }
      });
    });

const populateMovements = (centre, range) =>
  populate(Movement, centre.id, range)
    .then(movements => centre.unreconciledMovements = movements);

const resolveEventOperationWithMovementDirection = (operation) => {
  switch (operation) {
  case 'check in':
    return 'in';
  case 'check out':
    return 'out';
  default:
    return 'unknown';
  }
};

const getReconciliationTester = (movementsRangeFactory, eventsRangeFactory) => (event, movement) => {
  const cidMatches = movement.cid_id === event.detainee.cid_id;
  const directionMatches = resolveEventOperationWithMovementDirection(event.operation) === movement.direction;
  const timestampMatches = movementsRangeFactory(event.timestamp).contains(movement.timestamp) || eventsRangeFactory(movement.timestamp).contains(event.timestamp);

  return cidMatches && directionMatches && timestampMatches && { event, movement };
};

const filterUnreconciled = (centre, range) => {
  centre.unreconciledEvents = centre.unreconciledEvents.filter(event => range.contains(event.timestamp));
  centre.unreconciledMovements = centre.unreconciledMovements.filter(movement => range.contains(movement.timestamp));
};

const getReinstatementTester = (checkoutEventsRangeFactory) => (reinstatement, event) => {
  const operationMatches = event.operation === Event.OPERATION_CHECK_OUT;
  const personIdMatches = reinstatement.person_id === event.person_id;
  const timestampMatches = checkoutEventsRangeFactory(reinstatement.timestamp).contains(event.timestamp);

  return operationMatches && personIdMatches && timestampMatches && { reinstatement, event };
};

const untersect = (left, right, mapper) => {
  const matches = [];
  const rightExclusive = right.slice(0);

  const leftExclusive = left.filter((leftThing) =>
    !rightExclusive.some((rightThing, rightKey) => {
      const result = mapper(leftThing, rightThing);
      if (result) {
        matches.push(result);
        rightExclusive.splice(rightKey, 1);
        return true;
      }
      return false;
    })
  );
  return [leftExclusive, matches, rightExclusive];
};

const reconcileEvents = (centre, tester) => {
  const result = untersect(centre.unreconciledEvents, centre.unreconciledMovements, tester);
  centre.unreconciledEvents = result[0];
  centre.reconciled = result[1];
  centre.unreconciledMovements = result[2];
  return centre;
};

const handleReinstatements = (centre, tester) => {
  const result = untersect(centre.unreconciledReinstatements, centre.unreconciledEvents, tester);
  centre.unreconciledReinstatements = result[0];
  centre.reinstatements = result[1];
  centre.unreconciledEvents = result[2];
  return centre;
};

module.exports = {
  performReconciliation: (centre, visibilityRange, eventSearchDateRangeFactory, movementSearchDateRangeFactory, checkOutSearchDateRangeFactory) => {
    const rangeOfEvents = fullRange(visibilityRange, eventSearchDateRangeFactory);
    const rangeOfMovements = fullRange(visibilityRange, movementSearchDateRangeFactory);
    const reconciler = getReconciliationTester(movementSearchDateRangeFactory, eventSearchDateRangeFactory);
    const reinstatementReconciler = getReinstatementTester(checkOutSearchDateRangeFactory);

    centre.unreconciledEvents = [];
    centre.unreconciledMovements = [];
    centre.unreconciledReinstatements = [];
    centre.reconciled = [];
    centre.reinstatements = [];

    return populateEvents(centre, rangeOfEvents)
      .then(() => populateMovements(centre, rangeOfMovements))
      .then(() => handleReinstatements(centre, reinstatementReconciler))
      .then(() => reconcileEvents(centre, reconciler))
      .then(() => filterUnreconciled(centre, visibilityRange))
      .return(centre);
  },
  performConfiguredReconciliation: function (centre) {
    const visibilityRange = new DateRange(
      moment().subtract(2, 'days').startOf('day').toDate(),
      moment().endOf('day').toDate()
    );
    const eventsFromMovementDateRangeFactory = (date) => new DateRange(
      moment(date).subtract(2, 'days').startOf('day').toDate(),
      moment(date).add(2, 'days').endOf('day').toDate()
    );
    const movementsFromEventDateRangeFactory = (date) => new DateRange(
      moment(date).subtract(2, 'days').startOf('day').toDate(),
      moment(date).add(2, 'days').endOf('day').toDate()
    );
    const checkOutFromReinstatementDateRangeFactory = (date) => new DateRange(
      moment(date).subtract(1, 'days').toDate(),
      moment(date).toDate()
    );
    return this.performReconciliation(
      centre,
      visibilityRange,
      eventsFromMovementDateRangeFactory,
      movementsFromEventDateRangeFactory,
      checkOutFromReinstatementDateRangeFactory
    );
  }
};
<|endoftext|>


<commit_before>var request = require('request');
var admin = require("firebase-admin");

module.exports = {

    /**
     * Send new a notification for a new chat message
     *
     * @param {Message} message: Chat message object
     * @param {User} sender
     * @param {Team} senderTeam
     * @return {Promise}
     */
    notifyChatMessage(message, sender, senderTeam) {
        if(!sails.config.firebase.database) {
            return;
        }
        // Create receiver list
        let receivers = [];

        // Find potential receivers
        Session.find({firebaseToken: {'!': null}})
        .populate('user')
        .exec((error, sessions) => {
            if(error) sails.log.error('Error while sending alert notification to android', error);

            Team.find({id: _.pluck(sessions, 'user.team')})
            .exec((error, teams) => {
                if(error) sails.log.error('Error while sending alert notification to android', error);
                teams = _.groupBy(teams, 'id');

                // Only team which can read the channel will receive the notification
                for (let session of sessions) {
                    let team = teams[session.user.team][0];
                    if(session.user && session.user.team && teams[session.user.team] && sender.id != session.user.id) {
                        // Receive #group:[groupname] and #[teamname] but can send only in #[teamname]
                        if(Team.can(team, 'message/oneChannel') &&
                            (message.channel == 'public:'+Message.toChannel(team.name) ||
                            message.channel == 'group:'+Message.toChannel(team.group))) {
                                receivers.push(session.firebaseToken);
                        }
                        else if(Team.can(team, 'message/admin')) {
                            receivers.push(session.firebaseToken);
                        }
                        else if(Team.can(team, 'message/public')) {
                            if(message.channel.split(':')[0] == 'public') {
                                receivers.push(session.firebaseToken);
                            }
                            else if(message.channel == 'group:'+Message.toChannel(team.group)) {
                                receivers.push(session.firebaseToken);
                            }
                            else if(Team.can(team, 'message/group') && message.channel.split(':')[0] == 'group') {
                                receivers.push(session.firebaseToken);
                            }
                            else if(Team.can(team, 'message/private') && message.channel == 'private:'+Message.toChannel(team.group)) {
                                receivers.push(session.firebaseToken);
                            }
                        }
                    }
                }

                // Send message
                if(receivers.length > 0) {
                    return admin.messaging().sendToDevice(receivers,
                    {
                        data: {
                            type: 'message',
                            sender: message.sender || '',
                            senderName: sender.name || '',
                            senderTeamName: senderTeam.name || '',
                            text: message.text || '',
                            channel: message.channel || '',
                        }
                    })
                    .catch(function(error) {
                        sails.log.error('Error while sending message notification to android', error);
                    });
                }
            });
        });
    },

    /**
     * Send new a notification for a new alert
     *
     * @param {Alert} alert: Alert object
     * @return {Promise}
     */
    notifyAlert(alert) {
        if(!sails.config.firebase.database) {
            return;
        }

        // Create receiver list
        let receivers = [];

        // Find potential receivers
        Session.find({firebaseToken: {'!': null}})
        .populate('user')
        .exec((error, sessions) => {
            if(error) sails.log.error('Error while sending alert notification to android', error);

            Team.find({id: _.pluck(sessions, 'user.team')})
            .exec((error, teams) => {
                if(error) sails.log.error('Error while sending alert notification to android', error);
                teams = _.groupBy(teams, 'id');

                // Only admin or specified receiver will receive the notification
                for (let session of sessions) {
                    if(session.user && session.user.team && teams[session.user.team]) {
                        let team = teams[session.user.team][0];
                        if(Team.can(team, 'alert/read') || Team.can(team, 'alert/admin') ||
                        (Team.can(team, 'alert/restrictedReceiver') && alert.receiver == team.id) ||
                        (Team.can(team, 'ui/receiveDefaultAlerts') && !alert.receiver)) {
                            receivers.push(session.firebaseToken);
                        }
                    }
                }

                // Send to selected phones
                if(receivers.length > 0) {
                    return Team.findOneById(alert.sender || '').exec((error, team) => {
                        // Send message
                        return admin.messaging().sendToDevice(receivers,
                        {
                            data: {
                                type: 'alert',
                                id: alert.id || '',
                                sender: alert.sender || '',
                                receiver: alert.receiver || null,
                                senderName: (team && team.name) || 'Équipe supprimée',
                                senderLocation: (team && team.location) || '',
                                severity: alert.severity || 'done',
                                title: alert.title || '',
                                message: alert.message || '',
                            }
                        })
                        .catch(function(error) {
                            sails.log.error('Error while sending alert notification to android', error);
                        });
                    });
                }
            });
        });
    }
};
<commit_msg>Fix firebase service<commit_after>var request = require('request');
var admin = require("firebase-admin");

module.exports = {

    /**
     * Send new a notification for a new chat message
     *
     * @param {Message} message: Chat message object
     * @param {User} sender
     * @param {Team} senderTeam
     * @return {Promise}
     */
    notifyChatMessage(message, sender, senderTeam) {
        if(!sails.config.firebase.database) {
            return;
        }
        // Create receiver list
        let receivers = [];

        // Find potential receivers
        Session.find({firebaseToken: {'!': null}})
        .populate('user')
        .exec((error, sessions) => {
            if(error) sails.log.error('Error while sending alert notification to android', error);

            Team.find({id: _.pluck(sessions, 'user.team')})
            .exec((error, teams) => {
                if(error) sails.log.error('Error while sending alert notification to android', error);
                teams = _.groupBy(teams, 'id');

                // Only team which can read the channel will receive the notification
                for (let session of sessions) {
                    let team = teams[session.user.team][0];
                    if(session.user && session.user.team && teams[session.user.team] && sender.id != session.user.id) {
                        // Receive #group:[groupname] and #[teamname] but can send only in #[teamname]
                        if(Team.can(team, 'message/oneChannel') &&
                            (message.channel == 'public:'+Message.toChannel(team.name) ||
                            message.channel == 'group:'+Message.toChannel(team.group))) {
                                receivers.push(session.firebaseToken);
                        }
                        else if(Team.can(team, 'message/admin')) {
                            receivers.push(session.firebaseToken);
                        }
                        else if(Team.can(team, 'message/public')) {
                            if(message.channel.split(':')[0] == 'public') {
                                receivers.push(session.firebaseToken);
                            }
                            else if(message.channel == 'group:'+Message.toChannel(team.group)) {
                                receivers.push(session.firebaseToken);
                            }
                            else if(Team.can(team, 'message/group') && message.channel.split(':')[0] == 'group') {
                                receivers.push(session.firebaseToken);
                            }
                            else if(Team.can(team, 'message/private') && message.channel == 'private:'+Message.toChannel(team.group)) {
                                receivers.push(session.firebaseToken);
                            }
                        }
                    }
                }

                // Send message
                if(receivers.length > 0) {
                    return admin.messaging().sendToDevice(receivers,
                    {
                        data: {
                            type: 'message',
                            sender: message.sender || '',
                            senderName: sender.name || '',
                            senderTeamName: senderTeam.name || '',
                            text: message.text || '',
                            channel: message.channel || '',
                        }
                    })
                    .catch(function(error) {
                        sails.log.error('Error while sending message notification to android', error);
                    });
                }
            });
        });
    },

    /**
     * Send new a notification for a new alert
     *
     * @param {Alert} alert: Alert object
     * @return {Promise}
     */
    notifyAlert(alert) {
        if(!sails.config.firebase.database) {
            return;
        }

        // Create receiver list
        let receivers = [];

        // Find potential receivers
        Session.find({firebaseToken: {'!': null}})
        .populate('user')
        .exec((error, sessions) => {
            if(error) sails.log.error('Error while sending alert notification to android', error);

            Team.find({id: _.pluck(sessions, 'user.team')})
            .exec((error, teams) => {
                if(error) sails.log.error('Error while sending alert notification to android', error);
                teams = _.groupBy(teams, 'id');

                // Only admin or specified receiver will receive the notification
                for (let session of sessions) {
                    if(session.user && session.user.team && teams[session.user.team]) {
                        let team = teams[session.user.team][0];
                        if(Team.can(team, 'alert/read') || Team.can(team, 'alert/admin') ||
                        (Team.can(team, 'alert/restrictedReceiver') && alert.receiver == team.id) ||
                        (Team.can(team, 'ui/receiveDefaultAlerts') && !alert.receiver)) {
                            receivers.push(session.firebaseToken);
                        }
                    }
                }

                // Send to selected phones
                if(receivers.length > 0) {
                    return Team.findOneById(alert.sender || '').exec((error, team) => {
                        // Send message
                        return admin.messaging().sendToDevice(receivers,
                        {
                            data: {
                                type: 'alert',
                                id: alert.id || '',
                                sender: alert.sender || '',
                                receiver: alert.receiver || '',
                                senderName: (team && team.name) || 'Équipe supprimée',
                                senderLocation: (team && team.location) || '',
                                severity: alert.severity || 'done',
                                title: alert.title || '',
                                message: alert.message || '',
                            }
                        })
                        .catch(function(error) {
                            sails.log.error('Error while sending alert notification to android', error);
                        });
                    });
                }
            });
        });
    }
};
<|endoftext|>


<commit_before>const m = require('mithril');

const getData = () =>
    m.request({method: 'GET', url: 'js/modules/mockdata/streams.json'});

const Streams = module.exports = {
  controller: function () {
    let table = {
      columns: ['title', 'desc', 'stats', 'date', 'user'],
      names: {
        title: 'Title',
        desc: 'Description',
        stats: 'Statistics',
        date: 'Start Date',
        user: 'User'
      },
      data: getData()
    };
    let ctrl = this;

    ctrl.table = m.prop(table);
    ctrl.columns = m.prop(table.columns);
    ctrl.names = m.prop(table.names);
    ctrl.currentPage = m.prop(m.route.param('page') || 1);

    let formatStats = (viewers, stickers) => [
      m('span', viewers + 'V'),
      m('br'),
      m('span', stickers + 'S')
    ];

    let parse = (streams) => streams.map(
        function (stream) {
          return {
            title: stream.title,
            desc: stream.description,
            stats: formatStats(stream.totalViewers, stream.totalStickers),
            date: stream.startDateTime,
            user: stream.user.alias
          };
        }
    );

    ctrl.rawData = table.data.then(parse);

    ctrl.data = m.prop([]);

    ctrl.update = function () {
      ctrl.data = ctrl.rawData;
    };

    ctrl.update();
  },
  view: function (ctrl) {
    let getPagination = function () {
      let getPageRange = function (curPage, totalLength) {
        // TODO: Implement proper page range generation
        //   Pass in possible items count, divide by items per page
        //   Insert "..." if more than total length
        let range = [];
        let startPage = Math.max(curPage - Math.floor(totalLength / 2), 1);
        for (let i = 0; i < totalLength; i++) {
          range.push(startPage + i);
        }
        return range;
      };

      let getPageLink = (page) =>
          m('a', {href: '?page=' + page, config: m.route}, page);

      return [
        m('li', [m('a', '<')]),
        getPageRange(ctrl.currentPage(), 5).
            map((page) => ctrl.currentPage() == page ?
                m('li', {class: 'active'}, getPageLink(page)) :
                m('li', getPageLink(page))),
        m('li', [m('a', '>')])
      ];
    };

    return [m('h1', 'Streams'),
      m('div', {className: 'row right-align'}, [
        m('div', {className: 'col s12'}, [
        ])
      ]),
      m('table', {className: 'bordered responsive-table'}, [
        m('thead', [
          m('tr', [
            ctrl.columns().map((col) => m('td', ctrl.names()[col]))
          ])
        ]),
        m('tbody', [
          ctrl.data().map(function (stream) {
            return m('tr', [
              ctrl.columns().map((col) => m('td', stream[col]))
            ]);
          })
        ])
      ])
    ];
  }
};<commit_msg>Implement pagination<commit_after>const m = require('mithril');

const getData = () =>
    m.request({method: 'GET', url: 'js/modules/mockdata/streams.json'});

const Streams = module.exports = {
  controller: function () {
    let table = {
      columns: ['title', 'desc', 'stats', 'date', 'user'],
      names: {
        title: 'Title',
        desc: 'Description',
        stats: 'Statistics',
        date: 'Start Date',
        user: 'User'
      },
      data: getData()
    };
    let ctrl = this;

    ctrl.table = m.prop(table);
    ctrl.columns = m.prop(table.columns);
    ctrl.names = m.prop(table.names);
    ctrl.currentPage = m.prop(m.route.param('page') || 1);
    ctrl.itemsPerPage = m.prop(10);

    let formatStats = (viewers, stickers) => [
      m('span', viewers + 'V'),
      m('br'),
      m('span', stickers + 'S')
    ];

    let parse = (streams) => streams.map(
        function (stream) {
          return {
            title: stream.title,
            desc: stream.description,
            stats: formatStats(stream.totalViewers, stream.totalStickers),
            date: stream.startDateTime,
            user: stream.user.alias
          };
        }
    );

    ctrl.rawData = table.data.then(parse);

    let paginate = function (streams) {
      let items = parseInt(ctrl.itemsPerPage());
      let from = (parseInt(ctrl.currentPage()) - 1) * items;
      let to = from + items;
      return streams.slice(from, to);
    };

    ctrl.data = m.prop([]);

    ctrl.update = function () {
      ctrl.data = ctrl.rawData.then(paginate);
    };

    ctrl.update();
  },
  view: function (ctrl) {
    let getPagination = function () {
      let getPageRange = function (curPage, totalLength) {
        // TODO: Implement proper page range generation
        //   Pass in possible items count, divide by items per page
        //   Insert "..." if more than total length
        let range = [];
        let startPage = Math.max(curPage - Math.floor(totalLength / 2), 1);
        for (let i = 0; i < totalLength; i++) {
          range.push(startPage + i);
        }
        return range;
      };

      let getPageLink = (page) =>
          m('a', {href: '?page=' + page, config: m.route}, page);

      return [
        m('li', [m('a', '<')]),
        getPageRange(ctrl.currentPage(), 5).
            map((page) => ctrl.currentPage() == page ?
                m('li', {class: 'active'}, getPageLink(page)) :
                m('li', getPageLink(page))),
        m('li', [m('a', '>')])
      ];
    };

    return [m('h1', 'Streams'),
      m('div', {className: 'row right-align'}, [
        m('div', {className: 'col s12'},
          m('ul', {className: 'pagination'}, getPagination())
        )
      ]),
      m('table', {className: 'bordered responsive-table'}, [
        m('thead', [
          m('tr', [
            ctrl.columns().map((col) => m('td', ctrl.names()[col]))
          ])
        ]),
        m('tbody', [
          ctrl.data().map(function (stream) {
            return m('tr', [
              ctrl.columns().map((col) => m('td', stream[col]))
            ]);
          })
        ])
      ])
    ];
  }
};<|endoftext|>


<commit_before>//= require history_jquery

(function(window, undefined) {
  // get some globals
  var History = window.History,
      $ = window.jQuery,
      document = window.document,
      ajaxr;

  // container for event handlers
  var handlers = {
    beforeSend: [],
    success:    [],
    complete:   [],
    error:      []
  };

  // scroll cache to suppress browser scroll history
  var scroll;

  $.ajaxr = ajaxr = {
    // current absolute url
    currentUrl: $(window.location).attr('href'),

    lastUrl: undefined,

    // returns the json URL for a href. may be overridden
    jsonUrl: function(url) {
      // TODO check trailing slash
      return url + ".json";
    },

    // find the first handler matching the urls and calls it
    trigger: function(url, eventType, data) {
      var i = handlers[eventType].length;
      while(i--) {
        var item = handlers[eventType][i];
        var srcPattern    = item[0],
            targetPattern = item[1],
            handler       = item[2];
        if (ajaxr.currentUrl.match(srcPattern) && url.match(targetPattern)) {
          data.push(url);
          return handler.apply(this, data);
        }
      }
      return true;
    },

    // possible additional AJAX options for an URL
    optionsForUrl: function(url) {
      return {};
    },

    //registerEvent: function(eventType, urlPattern, handler) {
    registerEvent: function(eventType) {
      // TODO error checking, parameter types...
      var srcPattern, targetPattern, handler;
      if (arguments.length == 3) {
        srcPattern    = /.*/;
        targetPattern = arguments[1];
        handler       = arguments[2];
      } else if (arguments.length == 4) {
        srcPattern    = arguments[1];
        targetPattern = arguments[2];
        handler       = arguments[3];
      }
      handlers[eventType].push([srcPattern, targetPattern, handler]);
    },

    // handles the AJAX request
    handleRequest: function(url) {
      var options = $.extend(ajaxr.optionsForUrl(url), {
        url: ajaxr.jsonUrl(url),
        beforeSend: function(xhr, settings) {
          return ajaxr.trigger(url, 'beforeSend', [xhr, settings]);
        },
        success: function(data, status, xhr) {
          ajaxr.trigger(url, 'success', [data, status, xhr]);
          //$.ajaxr.currentUrl = url;
        },
        complete: function(xhr, status) {
          ajaxr.trigger(url, 'complete', [xhr, status]);
        },
        error: function(xhr, status, error) {
          ajaxr.trigger('error', [xhr, status, error]);
        }
      });

      return $.ajax(options);
    }
  };

  // register default success handler
  ajaxr.registerEvent('success', /.*/, function(data, status, xhr, url) {
    $.each(data, function(key, value) {
      $("#" + key).html(value);
    });
  });

  // keep track of scrolling (for browser scoll history suppression)
  $(window).scroll(function() { scroll = $(window).scrollTop(); });

  // bind history handler
  History.Adapter.bind(window,'statechange',function() {
    // restore the scroll position
    if (scroll > 0) $(window).scrollTo(scroll);
    scroll = -1;

    var State = History.getState();
    ajaxr.handleRequest(State.url);
    $.ajaxr.lastUrl    = $.ajaxr.currentUrl;
    $.ajaxr.currentUrl = State.url;
  });

  // register links with remote and history enabled
  $(document).delegate("a[data-remote][data-history]", "ajax:before", function(event) {
    // reset scroll to suppress scroll history of browser
    scroll = $(window).scrollTop();

    var url = $.rails.href($(this));
    $(window).scrollTo(0);
    History.pushState(null, null, url);

    return false;
  });

})(window);
<commit_msg>fixed version<commit_after>//= require history_jquery

(function(window, undefined) {
  // get some globals
  var History = window.History,
      $ = window.jQuery,
      document = window.document,
      ajaxr;

  // container for event handlers
  var handlers = {
    beforeSend: [],
    success:    [],
    complete:   [],
    error:      []
  };

  // scroll cache to suppress browser scroll history
  var scroll;

  $.ajaxr = ajaxr = {
    // current absolute url
    currentUrl: $(window.location).attr('href'),

    lastUrl: undefined,

    // returns the json URL for a href. may be overridden
    jsonUrl: function(url) {
      // TODO check trailing slash
      return url + ".json";
    },

    // find the first handler matching the urls and calls it
    trigger: function(url, eventType, data) {
      var i = handlers[eventType].length;
      while(i--) {
        var item = handlers[eventType][i];
        var srcPattern    = item[0],
            targetPattern = item[1],
            handler       = item[2];
        if (ajaxr.currentUrl.match(srcPattern) && url.match(targetPattern)) {
          data.push(url);
          return handler.apply(this, data);
        }
      }
      return true;
    },

    // possible additional AJAX options for an URL
    optionsForUrl: function(url) {
      return {};
    },

    //registerEvent: function(eventType, urlPattern, handler) {
    registerEvent: function(eventType) {
      // TODO error checking, parameter types...
      var srcPattern, targetPattern, handler;
      if (arguments.length == 3) {
        srcPattern    = /.*/;
        targetPattern = arguments[1];
        handler       = arguments[2];
      } else if (arguments.length == 4) {
        srcPattern    = arguments[1];
        targetPattern = arguments[2];
        handler       = arguments[3];
      }
      handlers[eventType].push([srcPattern, targetPattern, handler]);
    },

    // handles the AJAX request
    handleRequest: function(url) {
      var options = $.extend(ajaxr.optionsForUrl(url), {
        url: ajaxr.jsonUrl(url),
        beforeSend: function(xhr, settings) {
          return ajaxr.trigger(url, 'beforeSend', [xhr, settings]);
        },
        success: function(data, status, xhr) {
          ajaxr.trigger(url, 'success', [data, status, xhr]);
          //$.ajaxr.currentUrl = url;
        },
        complete: function(xhr, status) {
          ajaxr.trigger(url, 'complete', [xhr, status]);
        },
        error: function(xhr, status, error) {
          ajaxr.trigger('error', [xhr, status, error]);
        }
      });

      return $.ajax(options);
    }
  };

  // register default success handler
  ajaxr.registerEvent('success', /.*/, function(data, status, xhr, url) {
    $.each(data, function(key, value) {
      $("#" + key).html(value);
    });
  });

  // keep track of scrolling (for browser scoll history suppression)
  $(window).scroll(function() { scroll = $(window).scrollTop(); });

  // bind history handler
  History.Adapter.bind(window,'statechange',function() {
    // restore the scroll position
    $(window).scrollTo(scroll);

    var State = History.getState();
    ajaxr.handleRequest(State.url);
    $.ajaxr.lastUrl    = $.ajaxr.currentUrl;
    $.ajaxr.currentUrl = State.url;
  });

  // register links with remote and history enabled
  $(document).delegate("a[data-remote][data-history]", "ajax:before", function(event) {
    var url = $.rails.href($(this));
    History.pushState(null, null, url);

    return false;
  });

})(window);
<|endoftext|>


<commit_before>/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, semi, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top, padded-blocks */
/* global Breakpoints */
/* global Turbolinks */

(function() {
  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  var AUTO_SCROLL_OFFSET = 75;

  this.Build = (function() {
    Build.interval = null;

    Build.state = null;

    function Build(options) {
      options = options || $('.js-build-options').data();
      this.pageUrl = options.pageUrl;
      this.buildUrl = options.buildUrl;
      this.buildStatus = options.buildStatus;
      this.state = options.logState;
      this.buildStage = options.buildStage;
      this.updateDropdown = bind(this.updateDropdown, this);
      this.$document = $(document);
      this.$body = $('body');
      this.$buildTrace = $('#build-trace');
      this.$autoScrollContainer = $('.autoscroll-container');
      this.$autoScrollStatus = $('#autoscroll-status');
      this.$autoScrollStatusText = this.$autoScrollStatus.find('.status-text');
      this.$upBuildTrace = $('#up-build-trace');
      this.$downBuildTrace = $('#down-build-trace');
      this.$scrollTopBtn = $('#scroll-top');
      this.$scrollBottomBtn = $('#scroll-bottom');
      this.$buildRefreshAnimation = $('.js-build-refresh');

      clearInterval(Build.interval);
      // Init breakpoint checker
      this.bp = Breakpoints.get();

      this.initSidebar();
      this.$buildScroll = $('#js-build-scroll');

      this.populateJobs(this.buildStage);
      this.updateStageDropdownText(this.buildStage);
      this.sidebarOnResize();

      this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));
      this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
      this.$document.on('scroll', this.initScrollMonitor.bind(this));
      $(window).off('resize.build').on('resize.build', this.sidebarOnResize.bind(this));
      $('a', this.$buildScroll).off('click.stepTrace').on('click.stepTrace', this.stepTrace);
      this.updateArtifactRemoveDate();
      if ($('#build-trace').length) {
        this.getInitialBuildTrace();
        this.initScrollButtonAffix();
      }
      if (this.buildStatus === "running" || this.buildStatus === "pending") {
        Build.interval = setInterval((function(_this) {
          // Check for new build output if user still watching build page
          // Only valid for runnig build when output changes during time
          return function() {
            if (_this.location() === _this.pageUrl) {
              return _this.getBuildTrace();
            }
          };
        })(this), 4000);
      }
    }

    Build.prototype.initSidebar = function() {
      this.$sidebar = $('.js-build-sidebar');
      this.sidebarTranslationLimits = {
        min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
      }
      this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight();
      this.$sidebar.css({
        top: this.sidebarTranslationLimits.max
      });
      this.$sidebar.niceScroll();
      this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
      this.$document.off('scroll.translateSidebar').on('scroll.translateSidebar', this.translateSidebar.bind(this));
    };

    Build.prototype.location = function() {
      return window.location.href.split("#")[0];
    };

    Build.prototype.getInitialBuildTrace = function() {
      var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped']

      return $.ajax({
        url: this.buildUrl,
        dataType: 'json',
        success: function(buildData) {
          $('.js-build-output').html(buildData.trace_html);
          if (removeRefreshStatuses.indexOf(buildData.status) >= 0) {
            this.$buildRefreshAnimation.remove();
            return this.initScrollMonitor();
          }
        }.bind(this)
      });
    };

    Build.prototype.getBuildTrace = function() {
      return $.ajax({
        url: this.pageUrl + "/trace.json?state=" + (encodeURIComponent(this.state)),
        dataType: "json",
        success: (function(_this) {
          return function(log) {
            if (log.state) {
              _this.state = log.state;
            }
            if (log.status === "running") {
              if (log.append) {
                $('.js-build-output').append(log.html);
              } else {
                $('.js-build-output').html(log.html);
              }
              return _this.checkAutoscroll();
            } else if (log.status !== _this.buildStatus) {
              return Turbolinks.visit(_this.pageUrl);
            }
          };
        })(this)
      });
    };

    Build.prototype.checkAutoscroll = function() {
      if (this.$autoScrollStatus.data("state") === "enabled") {
        return $("html,body").scrollTop(this.$buildTrace.height());
      }

      // Handle a situation where user started new build
      // but never scrolled a page
      if (!this.$scrollTopBtn.is(':visible') &&
          !this.$scrollBottomBtn.is(':visible') &&
          !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
        this.$scrollBottomBtn.show();
      }
    };

    Build.prototype.initScrollButtonAffix = function() {
      // Hide everything initially
      this.$scrollTopBtn.hide();
      this.$scrollBottomBtn.hide();
      this.$autoScrollContainer.hide();
    }

    // Page scroll listener to detect if user has scrolling page
    // and handle following cases
    // 1) User is at Top of Build Log;
    //      - Hide Top Arrow button
    //      - Show Bottom Arrow button
    //      - Disable Autoscroll and hide indicator (when build is running)
    // 2) User is at Bottom of Build Log;
    //      - Show Top Arrow button
    //      - Hide Bottom Arrow button
    //      - Enable Autoscroll and show indicator (when build is running)
    // 3) User is somewhere in middle of Build Log;
    //      - Show Top Arrow button
    //      - Show Bottom Arrow button
    //      - Disable Autoscroll and hide indicator (when build is running)
    Build.prototype.initScrollMonitor = function() {
      if (!gl.utils.isInViewport(this.$upBuildTrace.get(0)) && !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
        // User is somewhere in middle of Build Log

        this.$scrollTopBtn.show();

        if (this.buildStatus === 'success' || this.buildStatus === 'failed') { // Check if Build is completed
          this.$scrollBottomBtn.show();
        } else if (this.$buildRefreshAnimation.is(':visible') && !gl.utils.isInViewport(this.$buildRefreshAnimation.get(0))) {
          this.$scrollBottomBtn.show();
        } else {
          this.$scrollBottomBtn.hide();
        }

        // Hide Autoscroll Status Indicator
        if (this.$scrollBottomBtn.is(':visible')) {
          this.$autoScrollContainer.hide();
          this.$autoScrollStatusText.removeClass('animate');
        } else {
          this.$autoScrollContainer.css({ top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET }).show();
          this.$autoScrollStatusText.addClass('animate');
        }
      } else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) && !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
        // User is at Top of Build Log

        this.$scrollTopBtn.hide();
        this.$scrollBottomBtn.show();

        this.$autoScrollContainer.hide();
        this.$autoScrollStatusText.removeClass('animate');
      } else if ((!gl.utils.isInViewport(this.$upBuildTrace.get(0)) && gl.utils.isInViewport(this.$downBuildTrace.get(0))) ||
                 (this.$buildRefreshAnimation.is(':visible') && gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)))) {
        // User is at Bottom of Build Log

        this.$scrollTopBtn.show();
        this.$scrollBottomBtn.hide();

        // Show and Reposition Autoscroll Status Indicator
        this.$autoScrollContainer.css({ top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET }).show();
        this.$autoScrollStatusText.addClass('animate');
      } else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) && gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
        // Build Log height is small

        this.$scrollTopBtn.hide();
        this.$scrollBottomBtn.hide();

        // Hide Autoscroll Status Indicator
        this.$autoScrollContainer.hide();
        this.$autoScrollStatusText.removeClass('animate');
      }

      if (this.buildStatus === "running" || this.buildStatus === "pending") {
        // Check if Refresh Animation is in Viewport and enable Autoscroll, disable otherwise.
        this.$autoScrollStatus.data("state", gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)) ? 'enabled' : 'disabled');
      }
    };

    Build.prototype.shouldHideSidebarForViewport = function() {
      var bootstrapBreakpoint;
      bootstrapBreakpoint = this.bp.getBreakpointSize();
      return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
    };

    Build.prototype.translateSidebar = function(e) {
      var newPosition = this.sidebarTranslationLimits.max - (document.body.scrollTop || document.documentElement.scrollTop);
      if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min;
      this.$sidebar.css({
        top: newPosition
      });
    };

    Build.prototype.toggleSidebar = function(shouldHide) {
      var shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
      this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
        .toggleClass('sidebar-collapsed', shouldHide);
      this.$sidebar.toggleClass('right-sidebar-expanded', shouldShow)
        .toggleClass('right-sidebar-collapsed', shouldHide);
    };

    Build.prototype.sidebarOnResize = function() {
      this.toggleSidebar(this.shouldHideSidebarForViewport());
    };

    Build.prototype.sidebarOnClick = function() {
      if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
    };

    Build.prototype.updateArtifactRemoveDate = function() {
      var $date, date;
      $date = $('.js-artifacts-remove');
      if ($date.length) {
        date = $date.text();
        return $date.text(gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
      }
    };

    Build.prototype.populateJobs = function(stage) {
      $('.build-job').hide();
      $('.build-job[data-stage="' + stage + '"]').show();
    };

    Build.prototype.updateStageDropdownText = function(stage) {
      $('.stage-selection').text(stage);
    };

    Build.prototype.updateDropdown = function(e) {
      e.preventDefault();
      var stage = e.currentTarget.text;
      this.updateStageDropdownText(stage);
      this.populateJobs(stage);
    };

    Build.prototype.stepTrace = function(e) {
      var $currentTarget;
      e.preventDefault();
      $currentTarget = $(e.currentTarget);
      $.scrollTo($currentTarget.attr('href'), {
        offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
      });
    };

    return Build;

  })();

}).call(this);
<commit_msg>Scroll to bottom on build completion if autoscroll was active<commit_after>/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, semi, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top, padded-blocks */
/* global Breakpoints */
/* global Turbolinks */

(function() {
  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
  var AUTO_SCROLL_OFFSET = 75;

  this.Build = (function() {
    Build.interval = null;

    Build.state = null;

    function Build(options) {
      options = options || $('.js-build-options').data();
      this.pageUrl = options.pageUrl;
      this.buildUrl = options.buildUrl;
      this.buildStatus = options.buildStatus;
      this.state = options.logState;
      this.buildStage = options.buildStage;
      this.updateDropdown = bind(this.updateDropdown, this);
      this.$document = $(document);
      this.$body = $('body');
      this.$buildTrace = $('#build-trace');
      this.$autoScrollContainer = $('.autoscroll-container');
      this.$autoScrollStatus = $('#autoscroll-status');
      this.$autoScrollStatusText = this.$autoScrollStatus.find('.status-text');
      this.$upBuildTrace = $('#up-build-trace');
      this.$downBuildTrace = $('#down-build-trace');
      this.$scrollTopBtn = $('#scroll-top');
      this.$scrollBottomBtn = $('#scroll-bottom');
      this.$buildRefreshAnimation = $('.js-build-refresh');

      clearInterval(Build.interval);
      // Init breakpoint checker
      this.bp = Breakpoints.get();

      this.initSidebar();
      this.$buildScroll = $('#js-build-scroll');

      this.populateJobs(this.buildStage);
      this.updateStageDropdownText(this.buildStage);
      this.sidebarOnResize();

      this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));
      this.$document.off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
      this.$document.on('scroll', this.initScrollMonitor.bind(this));
      $(window).off('resize.build').on('resize.build', this.sidebarOnResize.bind(this));
      $('a', this.$buildScroll).off('click.stepTrace').on('click.stepTrace', this.stepTrace);
      this.updateArtifactRemoveDate();
      if ($('#build-trace').length) {
        this.getInitialBuildTrace();
        this.initScrollButtonAffix();
      }
      if (this.buildStatus === "running" || this.buildStatus === "pending") {
        Build.interval = setInterval((function(_this) {
          // Check for new build output if user still watching build page
          // Only valid for runnig build when output changes during time
          return function() {
            if (_this.location() === _this.pageUrl) {
              return _this.getBuildTrace();
            }
          };
        })(this), 4000);
      }
    }

    Build.prototype.initSidebar = function() {
      this.$sidebar = $('.js-build-sidebar');
      this.sidebarTranslationLimits = {
        min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
      }
      this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight();
      this.$sidebar.css({
        top: this.sidebarTranslationLimits.max
      });
      this.$sidebar.niceScroll();
      this.$document.off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
      this.$document.off('scroll.translateSidebar').on('scroll.translateSidebar', this.translateSidebar.bind(this));
    };

    Build.prototype.location = function() {
      return window.location.href.split("#")[0];
    };

    Build.prototype.locationHash = function() {
      return window.location.href.split("#")[1];
    };

    Build.prototype.getInitialBuildTrace = function() {
      var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped']

      return $.ajax({
        url: this.buildUrl,
        dataType: 'json',
        success: function(buildData) {
          $('.js-build-output').html(buildData.trace_html);
          if (this.locationHash() === 'down-build-trace') {
            $("html,body").scrollTop(this.$buildTrace.height());
          }
          if (removeRefreshStatuses.indexOf(buildData.status) >= 0) {
            this.$buildRefreshAnimation.remove();
            return this.initScrollMonitor();
          }
        }.bind(this)
      });
    };

    Build.prototype.getBuildTrace = function() {
      return $.ajax({
        url: this.pageUrl + "/trace.json?state=" + (encodeURIComponent(this.state)),
        dataType: "json",
        success: (function(_this) {
          return function(log) {
            var pageUrl;

            if (log.state) {
              _this.state = log.state;
            }
            if (log.status === "running") {
              if (log.append) {
                $('.js-build-output').append(log.html);
              } else {
                $('.js-build-output').html(log.html);
              }
              return _this.checkAutoscroll();
            } else if (log.status !== _this.buildStatus) {
              pageUrl = _this.pageUrl;
              if (_this.$autoScrollStatus.data('state') === 'enabled') {
                pageUrl += '#down-build-trace';
              }

              return Turbolinks.visit(pageUrl);
            }
          };
        })(this)
      });
    };

    Build.prototype.checkAutoscroll = function() {
      if (this.$autoScrollStatus.data("state") === "enabled") {
        return $("html,body").scrollTop(this.$buildTrace.height());
      }

      // Handle a situation where user started new build
      // but never scrolled a page
      if (!this.$scrollTopBtn.is(':visible') &&
          !this.$scrollBottomBtn.is(':visible') &&
          !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
        this.$scrollBottomBtn.show();
      }
    };

    Build.prototype.initScrollButtonAffix = function() {
      // Hide everything initially
      this.$scrollTopBtn.hide();
      this.$scrollBottomBtn.hide();
      this.$autoScrollContainer.hide();
    }

    // Page scroll listener to detect if user has scrolling page
    // and handle following cases
    // 1) User is at Top of Build Log;
    //      - Hide Top Arrow button
    //      - Show Bottom Arrow button
    //      - Disable Autoscroll and hide indicator (when build is running)
    // 2) User is at Bottom of Build Log;
    //      - Show Top Arrow button
    //      - Hide Bottom Arrow button
    //      - Enable Autoscroll and show indicator (when build is running)
    // 3) User is somewhere in middle of Build Log;
    //      - Show Top Arrow button
    //      - Show Bottom Arrow button
    //      - Disable Autoscroll and hide indicator (when build is running)
    Build.prototype.initScrollMonitor = function() {
      if (!gl.utils.isInViewport(this.$upBuildTrace.get(0)) && !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
        // User is somewhere in middle of Build Log

        this.$scrollTopBtn.show();

        if (this.buildStatus === 'success' || this.buildStatus === 'failed') { // Check if Build is completed
          this.$scrollBottomBtn.show();
        } else if (this.$buildRefreshAnimation.is(':visible') && !gl.utils.isInViewport(this.$buildRefreshAnimation.get(0))) {
          this.$scrollBottomBtn.show();
        } else {
          this.$scrollBottomBtn.hide();
        }

        // Hide Autoscroll Status Indicator
        if (this.$scrollBottomBtn.is(':visible')) {
          this.$autoScrollContainer.hide();
          this.$autoScrollStatusText.removeClass('animate');
        } else {
          this.$autoScrollContainer.css({ top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET }).show();
          this.$autoScrollStatusText.addClass('animate');
        }
      } else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) && !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
        // User is at Top of Build Log

        this.$scrollTopBtn.hide();
        this.$scrollBottomBtn.show();

        this.$autoScrollContainer.hide();
        this.$autoScrollStatusText.removeClass('animate');
      } else if ((!gl.utils.isInViewport(this.$upBuildTrace.get(0)) && gl.utils.isInViewport(this.$downBuildTrace.get(0))) ||
                 (this.$buildRefreshAnimation.is(':visible') && gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)))) {
        // User is at Bottom of Build Log

        this.$scrollTopBtn.show();
        this.$scrollBottomBtn.hide();

        // Show and Reposition Autoscroll Status Indicator
        this.$autoScrollContainer.css({ top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET }).show();
        this.$autoScrollStatusText.addClass('animate');
      } else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) && gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
        // Build Log height is small

        this.$scrollTopBtn.hide();
        this.$scrollBottomBtn.hide();

        // Hide Autoscroll Status Indicator
        this.$autoScrollContainer.hide();
        this.$autoScrollStatusText.removeClass('animate');
      }

      if (this.buildStatus === "running" || this.buildStatus === "pending") {
        // Check if Refresh Animation is in Viewport and enable Autoscroll, disable otherwise.
        this.$autoScrollStatus.data("state", gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)) ? 'enabled' : 'disabled');
      }
    };

    Build.prototype.shouldHideSidebarForViewport = function() {
      var bootstrapBreakpoint;
      bootstrapBreakpoint = this.bp.getBreakpointSize();
      return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
    };

    Build.prototype.translateSidebar = function(e) {
      var newPosition = this.sidebarTranslationLimits.max - (document.body.scrollTop || document.documentElement.scrollTop);
      if (newPosition < this.sidebarTranslationLimits.min) newPosition = this.sidebarTranslationLimits.min;
      this.$sidebar.css({
        top: newPosition
      });
    };

    Build.prototype.toggleSidebar = function(shouldHide) {
      var shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
      this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
        .toggleClass('sidebar-collapsed', shouldHide);
      this.$sidebar.toggleClass('right-sidebar-expanded', shouldShow)
        .toggleClass('right-sidebar-collapsed', shouldHide);
    };

    Build.prototype.sidebarOnResize = function() {
      this.toggleSidebar(this.shouldHideSidebarForViewport());
    };

    Build.prototype.sidebarOnClick = function() {
      if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
    };

    Build.prototype.updateArtifactRemoveDate = function() {
      var $date, date;
      $date = $('.js-artifacts-remove');
      if ($date.length) {
        date = $date.text();
        return $date.text(gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '));
      }
    };

    Build.prototype.populateJobs = function(stage) {
      $('.build-job').hide();
      $('.build-job[data-stage="' + stage + '"]').show();
    };

    Build.prototype.updateStageDropdownText = function(stage) {
      $('.stage-selection').text(stage);
    };

    Build.prototype.updateDropdown = function(e) {
      e.preventDefault();
      var stage = e.currentTarget.text;
      this.updateStageDropdownText(stage);
      this.populateJobs(stage);
    };

    Build.prototype.stepTrace = function(e) {
      var $currentTarget;
      e.preventDefault();
      $currentTarget = $(e.currentTarget);
      $.scrollTo($currentTarget.attr('href'), {
        offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight())
      });
    };

    return Build;

  })();

}).call(this);
<|endoftext|>