<commit_before>define(function(require) {

	'use strict';

	//var _       = require('underscore');
	var PIXI    = require('pixi');
	var Vector2 = require('common/math/vector2');

	var ElementView = require('views/element');
	var Block       = require('models/element/block');
	//var Assets      = require('assets');

	var Constants = require('constants');

	/**
	 * A view that represents a block model
	 */
	var BlockView = ElementView.extend({

		/**
		 *
		 */
		initialize: function(options) {
			ElementView.prototype.initialize.apply(this, arguments);

			this.listenTo(this.model, 'change:position', this.updatePosition);

			this.initGraphics();

			this.updatePosition(this.model, this.model.get('position'));
		},

		initGraphics: function() {

			this.outlineBack  = new PIXI.DisplayObjectContainer();
			this.faces        = new PIXI.DisplayObjectContainer();
			this.outlineFront = new PIXI.DisplayObjectContainer();

			this.displayObject.addChild(this.outlineBack);
			this.displayObject.addChild(this.faces);
			this.displayObject.addChild(this.outlineFront);

			var rect = this.mvt.modelToViewScale(Block.getRawShape());
			var perspectiveEdgeSize = this.mvt.modelToViewDeltaX(this.model.getRect().w * Constants.PERSPECTIVE_EDGE_PROPORTION);

			var blockFaceOffset  = (new Vector2(-perspectiveEdgeSize / 2, 0)).rotate(-Constants.PERSPECTIVE_ANGLE);
			var backCornerOffset = (new Vector2(perspectiveEdgeSize,      0)).rotate(-Constants.PERSPECTIVE_ANGLE);

			// For calculating the dragging bounds
			this.blockFaceYOffset = Math.ceil(blockFaceOffset.y);

			// Front face
			var lowerLeftFrontCorner  = (new Vector2(rect.left(),   rect.bottom())).add(blockFaceOffset);
			var lowerRightFrontCorner = (new Vector2(rect.right(),  rect.bottom())).add(blockFaceOffset);
			var upperRightFrontCorner = (new Vector2(rect.right(), -rect.top()   )).add(blockFaceOffset);
			var upperLeftFrontCorner  = (new Vector2(rect.left(),  -rect.top()   )).add(blockFaceOffset);

			var frontFacePoints = [
				lowerLeftFrontCorner,
				lowerRightFrontCorner,
				upperRightFrontCorner,
				upperLeftFrontCorner,
				lowerLeftFrontCorner
			];
			this.faces.addChild(this.createFrontFace(frontFacePoints));

			// Top face
			var upperLeftBackCorner  = upperLeftFrontCorner.clone().add(backCornerOffset);
			var upperRightBackCorner = upperRightFrontCorner.clone().add(backCornerOffset);

			var topFacePoints = [
				upperLeftFrontCorner,
				upperRightFrontCorner,
				upperRightBackCorner,
				upperLeftBackCorner,
				upperLeftFrontCorner
			];
			this.faces.addChild(this.createTopFace(topFacePoints));

			// Side face
			var lowerRightBackCorner = lowerRightFrontCorner.clone().add(backCornerOffset);

			var rightFacePoints = [
				upperRightFrontCorner,
				lowerRightFrontCorner,
				lowerRightBackCorner,
				upperRightBackCorner,
				upperRightFrontCorner
			];
			this.faces.addChild(this.createRightFace(rightFacePoints));

			// Front outline
			var lineStyle = {
				lineWidth: 3,
				strokeStyle: '#444',
				lineJoin: 'bevel'
			};
			var pointArrays = [frontFacePoints, topFacePoints, rightFacePoints];
			this.outlineFront.addChild(this.createOutlineFromPointArrays(pointArrays, lineStyle));

			// Back outline
			var lowerLeftBackCorner = lowerLeftFrontCorner.clone().add(backCornerOffset);

			pointArrays = [[
				lowerLeftBackCorner,
				lowerRightBackCorner
			],[
				lowerLeftBackCorner,
				lowerLeftFrontCorner
			],[
				lowerLeftBackCorner,
				upperLeftBackCorner
			]];
			this.outlineBack.addChild(this.createOutlineFromPointArrays(pointArrays, lineStyle));

			this.outlineBack.visible = false;

			// Label
			this.label = new PIXI.Text(this.labelText, {
				font: this.textFont,
				fill: this.textColor
			});
			this.label.anchor.x = this.label.anchor.y = 0.5;
			this.label.x = blockFaceOffset.x;
			this.label.y = -(rect.h / 2) + blockFaceOffset.y;
			this.displayObject.addChild(this.label);

			// Just for debugging
			this.renderTopCenterPoint();
			//this.renderBottomCenterPoint();
		},

		createFrontFace: function(points) {
			return this.createColoredPolygonFromPoints(points, this.fillColor, this.fillAlpha);
		},

		createTopFace: function(points) {
			return this.createColoredPolygonFromPoints(points, this.fillColor, this.fillAlpha);
		},

		createRightFace: function(points) {
			return this.createColoredPolygonFromPoints(points, this.fillColor, this.fillAlpha);
		},

		/**
		 * For debugging purposes
		 */
		renderTopCenterPoint: function() {
			var top = new PIXI.Graphics();
			top.beginFill(0x0000FF, 1);
			top.drawCircle(0, this.mvt.modelToViewDeltaY(this.model.topSurface.yPos - this.model.get('position').y), 3);
			top.endFill();
			this.displayObject.addChild(top);
		},

		/**
		 * For debugging purposes
		 */
		renderBottomCenterPoint: function() {
			var origin = new PIXI.Graphics();
			origin.beginFill(0xFF0000, 1);
			origin.drawCircle(0, 0, 3);
			origin.endFill();
			this.displayObject.addChild(origin);
		},

		calculateDragBounds: function(dx, dy) {
			var bounds = ElementView.prototype.calculateDragBounds.apply(this, arguments);
			bounds.h -= this.blockFaceYOffset + (this.lineWidth / 2);
			return bounds;
		},

		updatePosition: function(model, position) {
			var viewPoint = this.mvt.modelToView(position);
			this.displayObject.x = viewPoint.x;
			this.displayObject.y = viewPoint.y;
		},

		showEnergyChunks: function() {
			this.outlineBack.visible = true;
			this.faces.alpha = 0.5;
			this.label.alpha = 0.5;
		},

		hideEnergyChunks: function() {
			this.outlineBack.visible = false;
			this.faces.alpha = 1;
			this.label.alpha = 1;
		},

		update: function(time, deltaTime) {}

	});

	return BlockView;
});<commit_msg>The masked textures on the brick were messing up the bounds calculation of the DisplayObject, so I made the bounds calculation independent of the DisplayObject.<commit_after>define(function(require) {

	'use strict';

	//var _       = require('underscore');
	var PIXI    = require('pixi');
	var Vector2 = require('common/math/vector2');
	var PiecewiseCurve = require('common/math/piecewise-curve');

	var ElementView = require('views/element');
	var Block       = require('models/element/block');
	//var Assets      = require('assets');

	var Constants = require('constants');

	/**
	 * A view that represents a block model
	 */
	var BlockView = ElementView.extend({

		/**
		 *
		 */
		initialize: function(options) {
			ElementView.prototype.initialize.apply(this, arguments);

			this.listenTo(this.model, 'change:position', this.updatePosition);

			this.initGraphics();

			this.updatePosition(this.model, this.model.get('position'));
		},

		initGraphics: function() {

			this.outlineBack  = new PIXI.DisplayObjectContainer();
			this.faces        = new PIXI.DisplayObjectContainer();
			this.outlineFront = new PIXI.DisplayObjectContainer();

			this.displayObject.addChild(this.outlineBack);
			this.displayObject.addChild(this.faces);
			this.displayObject.addChild(this.outlineFront);

			var rect = this.mvt.modelToViewScale(Block.getRawShape());
			var perspectiveEdgeSize = this.mvt.modelToViewDeltaX(this.model.getRect().w * Constants.PERSPECTIVE_EDGE_PROPORTION);

			var blockFaceOffset  = (new Vector2(-perspectiveEdgeSize / 2, 0)).rotate(-Constants.PERSPECTIVE_ANGLE);
			var backCornerOffset = (new Vector2(perspectiveEdgeSize,      0)).rotate(-Constants.PERSPECTIVE_ANGLE);

			// Front face
			var lowerLeftFrontCorner  = (new Vector2(rect.left(),   rect.bottom())).add(blockFaceOffset);
			var lowerRightFrontCorner = (new Vector2(rect.right(),  rect.bottom())).add(blockFaceOffset);
			var upperRightFrontCorner = (new Vector2(rect.right(), -rect.top()   )).add(blockFaceOffset);
			var upperLeftFrontCorner  = (new Vector2(rect.left(),  -rect.top()   )).add(blockFaceOffset);

			var frontFacePoints = [
				lowerLeftFrontCorner,
				lowerRightFrontCorner,
				upperRightFrontCorner,
				upperLeftFrontCorner,
				lowerLeftFrontCorner
			];
			this.faces.addChild(this.createFrontFace(frontFacePoints));

			// Top face
			var upperLeftBackCorner  = upperLeftFrontCorner.clone().add(backCornerOffset);
			var upperRightBackCorner = upperRightFrontCorner.clone().add(backCornerOffset);

			var topFacePoints = [
				upperLeftFrontCorner,
				upperRightFrontCorner,
				upperRightBackCorner,
				upperLeftBackCorner,
				upperLeftFrontCorner
			];
			this.faces.addChild(this.createTopFace(topFacePoints));

			// Side face
			var lowerRightBackCorner = lowerRightFrontCorner.clone().add(backCornerOffset);

			var rightFacePoints = [
				upperRightFrontCorner,
				lowerRightFrontCorner,
				lowerRightBackCorner,
				upperRightBackCorner,
				upperRightFrontCorner
			];
			this.faces.addChild(this.createRightFace(rightFacePoints));

			// Front outline
			var lineStyle = {
				lineWidth: 3,
				strokeStyle: '#444',
				lineJoin: 'bevel'
			};
			var pointArrays = [frontFacePoints, topFacePoints, rightFacePoints];
			this.outlineFront.addChild(this.createOutlineFromPointArrays(pointArrays, lineStyle));

			// Back outline
			var lowerLeftBackCorner = lowerLeftFrontCorner.clone().add(backCornerOffset);

			pointArrays = [[
				lowerLeftBackCorner,
				lowerRightBackCorner
			],[
				lowerLeftBackCorner,
				lowerLeftFrontCorner
			],[
				lowerLeftBackCorner,
				upperLeftBackCorner
			]];
			this.outlineBack.addChild(this.createOutlineFromPointArrays(pointArrays, lineStyle));

			this.outlineBack.visible = false;

			// Label
			this.label = new PIXI.Text(this.labelText, {
				font: this.textFont,
				fill: this.textColor
			});
			this.label.anchor.x = this.label.anchor.y = 0.5;
			this.label.x = blockFaceOffset.x;
			this.label.y = -(rect.h / 2) + blockFaceOffset.y;
			this.displayObject.addChild(this.label);

			// Calculate the bounding box for the dragging bounds
			var outline = PiecewiseCurve.fromPointArrays(pointArrays);
			this.boundingBox = outline.getBounds().clone();
			this.boundingBox.x -= (this.lineWidth / 2);
			this.boundingBox.y -= (this.lineWidth / 2);
			this.boundingBox.w += this.lineWidth;
			this.boundingBox.h += (this.lineWidth / 2) - blockFaceOffset.y;

			// Just for debugging
			this.renderTopCenterPoint();
			//this.renderBottomCenterPoint();
		},

		createFrontFace: function(points) {
			return this.createColoredPolygonFromPoints(points, this.fillColor, this.fillAlpha);
		},

		createTopFace: function(points) {
			return this.createColoredPolygonFromPoints(points, this.fillColor, this.fillAlpha);
		},

		createRightFace: function(points) {
			return this.createColoredPolygonFromPoints(points, this.fillColor, this.fillAlpha);
		},

		/**
		 * For debugging purposes
		 */
		renderTopCenterPoint: function() {
			var top = new PIXI.Graphics();
			top.beginFill(0x0000FF, 1);
			top.drawCircle(0, this.mvt.modelToViewDeltaY(this.model.topSurface.yPos - this.model.get('position').y), 3);
			top.endFill();
			this.displayObject.addChild(top);
		},

		/**
		 * For debugging purposes
		 */
		renderBottomCenterPoint: function() {
			var origin = new PIXI.Graphics();
			origin.beginFill(0xFF0000, 1);
			origin.drawCircle(0, 0, 3);
			origin.endFill();
			this.displayObject.addChild(origin);
		},

		calculateDragBounds: function(dx, dy) {
			return this._dragBounds
				.set(this.boundingBox)
				.translate(this.displayObject.x + dx, this.displayObject.y + dy);
		},

		updatePosition: function(model, position) {
			var viewPoint = this.mvt.modelToView(position);
			this.displayObject.x = viewPoint.x;
			this.displayObject.y = viewPoint.y;
		},

		showEnergyChunks: function() {
			this.outlineBack.visible = true;
			this.faces.alpha = 0.5;
			this.label.alpha = 0.5;
		},

		hideEnergyChunks: function() {
			this.outlineBack.visible = false;
			this.faces.alpha = 1;
			this.label.alpha = 1;
		},

		update: function(time, deltaTime) {}

	});

	return BlockView;
});<|endoftext|>


<commit_before>$(document).ready(function () {
    var header = $('header#primary h1 a');
    var headerText = header.text();
    console.debug(headerText);
    var headerTextArray = headerText.split(' ');
    var headerTextArrayLength = headerTextArray.length;
    var lastWord = headerTextArray[headerTextArrayLength - 1];
    console.debug(lastWord);
    headerTextArray.pop();
    var firstWords = headerTextArray.join(' ');

    header.text(firstWords);
    header.append(' ' + lastWord + '');
});
<commit_msg>Apparently IE doesn't like console.debug. <sigh><commit_after>$(document).ready(function () {
    var header = $('header#primary h1 a');
    var headerText = header.text();
    console.log(headerText);
    var headerTextArray = headerText.split(' ');
    var headerTextArrayLength = headerTextArray.length;
    var lastWord = headerTextArray[headerTextArrayLength - 1];
    console.log(lastWord);
    headerTextArray.pop();
    var firstWords = headerTextArray.join(' ');

    header.text(firstWords);
    header.append(' ' + lastWord + '');
});
<|endoftext|>


<commit_before>/** @jsx React.DOM */

define(
  [
    'react',
    'components/common/PageHeader.react',
    './FeedbackForm.react'
  ],
  function (React, PageHeader, FeedbackForm) {

    var resources = [
      {
        title: "User Manual",
        href: "https://pods.iplantcollaborative.org/wiki/x/Iaxm",
        description: "Complete documentation for using Atmosphere"
      },
      {
        title: "User Forums",
        href: "http://ask.iplantcollaborative.org",
        description: "Get answers from iPlant users and staff"
      },
      {
        title: "FAQs",
        href: "https://pods.iplantcollaborative.org/wiki/x/Iaxm",
        description: "Atmosphere's most frequently asked questions"
      },
      {
        title: "VNC Viewer Tutorial",
        href: "https://pods.iplantcollaborative.org/wiki/x/Iaxm",
        description: "Instructions for downloading and using VNC Viewer"
      }
    ];

    return React.createClass({
      render: function () {

        var resourceElements = _.map(resources, function (resource) {
          return (

              {resource.title}
              {" " + resource.description}

          );
        });

        return (

            <PageHeader title="Help"/>

External resources

              {resourceElements}

Atmosphere staff support

              {
                "Are you experiencing a problem with Atmosphere to which you " +
                "can't find a solution? Do you have a feature request or bug " +
                "report? Let us know!"
              }

            <FeedbackForm/>

        );
      }
    });

  });
<commit_msg>Fix links on Help page<commit_after>/** @jsx React.DOM */

define(
  [
    'react',
    'components/common/PageHeader.react',
    './FeedbackForm.react'
  ],
  function (React, PageHeader, FeedbackForm) {

    var resources = [
      {
        title: "User Manual",
        href: "https://pods.iplantcollaborative.org/wiki/x/Iaxm",
        description: "Complete documentation for using Atmosphere"
      },
      {
        title: "User Forums",
        href: "http://ask.iplantcollaborative.org",
        description: "Get answers from iPlant users and staff"
      },
      {
        title: "FAQs",
        href: "https://pods.iplantcollaborative.org/wiki/x/Iaxm",
        description: "Atmosphere's most frequently asked questions"
      },
      {
        title: "VNC Viewer Tutorial",
        href: "https://pods.iplantcollaborative.org/wiki/x/Iaxm",
        description: "Instructions for downloading and using VNC Viewer"
      }
    ];

    return React.createClass({
      render: function () {

        var resourceElements = _.map(resources, function (resource) {
          return (

              {resource.title}
              {" " + resource.description}

          );
        });

        return (

            <PageHeader title="Help"/>

External resources

              {resourceElements}

Atmosphere staff support

              {
                "Are you experiencing a problem with Atmosphere to which you " +
                "can't find a solution? Do you have a feature request or bug " +
                "report? Let us know!"
              }

            <FeedbackForm/>

        );
      }
    });

  });
<|endoftext|>


<commit_before>import React from 'react';

export const fillTemplate = (template, replacers) => {
    const res       = [];
    let str         = template;
    let open_tag_id = null;

    while (str.length) {
        const match = str.match(/\[_(\d+)\]/);

        if (!match) {
            res.push(str);
            break;
        }

        const {
            0: tag,
            1: tag_id,
            index,
        } = match;

        const before = str.slice(0, index);
        str = str.slice(index + tag.length);

        if (open_tag_id) {
            const pair_code = `${open_tag_id}_${tag_id}`;
            const wrapper   = replacers[pair_code];

            if (!React.isValidElement(wrapper)) throw new Error(`Localize: pair tag must be replaced with a react element.`);
            if (!wrapper) throw new Error(`Localize: no ${open_tag_id} or ${pair_code} replacer for "${template}" template.`);

            res.push(React.cloneElement(wrapper, { key: index, children: before }));
            open_tag_id = null;
        }
        else {
            if (before.length) res.push(before);

            if (tag_id in replacers) {
                res.push(replacers[tag_id]);
            }
            else {
                open_tag_id = tag_id;
            }
        }
    }
    if (open_tag_id) throw new Error(`Localize: no ${open_tag_id} replacer for "${template}" template.`);

    // concat adjacent strings in result array
    return res.reduce((arr, el) => {
        const last = arr[arr.length - 1];
        if (typeof last === 'string' && typeof el === 'string') {
            arr[arr.length - 1] = last + el;
        }
        else {
            arr.push(el);
        }
        return arr;
    }, []);
};
<commit_msg>fix eslint<commit_after>import React from 'react';

export const fillTemplate = (template, replacers) => {
    const res       = [];
    let str         = template;
    let open_tag_id = null;

    while (str.length) {
        const match = str.match(/\[_(\d+)\]/);

        if (!match) {
            res.push(str);
            break;
        }

        const {
            0: tag,
            1: tag_id,
            index,
        } = match;

        const before = str.slice(0, index);
        str = str.slice(index + tag.length);

        if (open_tag_id) {
            const pair_code = `${open_tag_id}_${tag_id}`;
            const wrapper   = replacers[pair_code];

            if (!React.isValidElement(wrapper)) throw new Error(`Localize: pair tag ${pair_code} must be replaced with a react element.`);
            if (!wrapper) throw new Error(`Localize: no ${open_tag_id} or ${pair_code} replacer for "${template}" template.`);

            res.push(React.cloneElement(wrapper, { key: index, children: before }));
            open_tag_id = null;
        }
        else {
            if (before.length) res.push(before);

            if (tag_id in replacers) {
                res.push(replacers[tag_id]);
            }
            else {
                open_tag_id = tag_id;
            }
        }
    }
    if (open_tag_id) throw new Error(`Localize: no ${open_tag_id} replacer for "${template}" template.`);

    // concat adjacent strings in result array
    return res.reduce((arr, el) => {
        const last = arr[arr.length - 1];
        if (typeof last === 'string' && typeof el === 'string') {
            arr[arr.length - 1] = last + el;
        }
        else {
            arr.push(el);
        }
        return arr;
    }, []);
};
<|endoftext|>


<commit_before>// Ultima Weapon Ultimate
[{
  zoneRegex: /^(The Weapon's Refrain \(Ultimate\)|Unknown Zone \(309\))$/,
  timelineFile: 'ultima_weapon_ultimate.txt',
  triggers: [
    {
      id: 'UWU Garuda Slipstream',
      regex: /14:2B53:Garuda starts using (?:Unknown_2B53|Slipstream)/,
      condition: function(data) { return data.role == 'tank'; },
      alertText: {
        en: 'Slipstream',
      },
      tts: {
        en: 'Slipstream',
      },
    },
    {
      id: 'UWU Garuda Mistral Song Marker',
      regex: / 1B:........:(\y{Name}):....:....:0010:0000:0000:0000:/,
      suppressSeconds: 5,
      infoText: {
        en: 'Mistral Song',
      },
      tts: {
        en: 'Mistral Song',
      },
    },
    {
      id: 'UWU Garuda Spiny Plume',
      regex: / 03:Added new combatant Spiny Plume/,
      condition: function(data, matches) { return data.role == 'tank'; },
      infoText: {
        en: 'Spiny Plume Add',
      },
      tts: {
        en: 'Spiny Plume Add',
      },
    },
    {
      id: 'UWU Ifrit Fetters',
      regex: /1A:(\y{Name}) gains the effect of Infernal Fetters from/,
      condition: function(data, matches) { return data.me == matches[1]; },
      suppressSeconds: 45,
      alertText: {
        en: 'Fetters on YOU',
      },
      tts: {
        en: 'Fetters',
      },
    },
    {
      id: 'UWU Searing Wind',
      regex: /1A:(\y{Name}) gains the effect of (?:Unknown_62A|Searing Wind) from/,
      condition: function(data, matches) { return data.me == matches[1]; },
      alarmText: {
        en: 'Searing Wind on YOU',
      },
      tts: {
        en: 'Searing Wind',
      },
    },
    // TODO: what's the 0075 marker
  ],
}]
<commit_msg>uwu: feather rain warning<commit_after>// Ultima Weapon Ultimate
[{
  zoneRegex: /^(The Weapon's Refrain \(Ultimate\)|Unknown Zone \(309\))$/,
  timelineFile: 'ultima_weapon_ultimate.txt',
  timeline: [
    'infotext "Feather Rain" before 3 "Move!"',
  ],
  triggers: [
    {
      id: 'UWU Garuda Slipstream',
      regex: /14:2B53:Garuda starts using (?:Unknown_2B53|Slipstream)/,
      condition: function(data) { return data.role == 'tank'; },
      alertText: {
        en: 'Slipstream',
      },
      tts: {
        en: 'Slipstream',
      },
    },
    {
      id: 'UWU Garuda Mistral Song Marker',
      regex: / 1B:........:(\y{Name}):....:....:0010:0000:0000:0000:/,
      suppressSeconds: 5,
      infoText: {
        en: 'Mistral Song',
      },
      tts: {
        en: 'Mistral Song',
      },
    },
    {
      id: 'UWU Garuda Spiny Plume',
      regex: / 03:Added new combatant Spiny Plume/,
      condition: function(data, matches) { return data.role == 'tank'; },
      infoText: {
        en: 'Spiny Plume Add',
      },
      tts: {
        en: 'Spiny Plume Add',
      },
    },
    {
      id: 'UWU Ifrit Fetters',
      regex: /1A:(\y{Name}) gains the effect of Infernal Fetters from/,
      condition: function(data, matches) { return data.me == matches[1]; },
      suppressSeconds: 45,
      alertText: {
        en: 'Fetters on YOU',
      },
      tts: {
        en: 'Fetters',
      },
    },
    {
      id: 'UWU Searing Wind',
      regex: /1A:(\y{Name}) gains the effect of (?:Unknown_62A|Searing Wind) from/,
      condition: function(data, matches) { return data.me == matches[1]; },
      alarmText: {
        en: 'Searing Wind on YOU',
      },
      tts: {
        en: 'Searing Wind',
      },
    },
    // TODO: what's the 0075 marker
  ],
}]
<|endoftext|>


<commit_before>import React from 'react';

import ItemLink from 'common/ItemLink';

import Analyzer from 'Parser/Core/Analyzer';
import SUGGESTION_IMPORTANCE from 'Parser/Core/ISSUE_IMPORTANCE';

// Example logs with missing enchants:
// https://www.warcraftlogs.com/reports/ydxavfGq1mBrM9Vc/#fight=1&source=14

class EnchantChecker extends Analyzer {

  static ENCHANTABLE_SLOTS = {
    1: 'Neck',
    10: 'Ring',
    11: 'Ring',
    14: 'Cloak',
  };
  static MAX_ENCHANT_IDS = [
    5436, // binding of intellect
    5435, // binding of agility
    5434, // binding of strength
    5891, // mark of the ancient priestess
    5437, // mark of the claw
    5438, // mark of the distant army
    5889, // mark of the heavy hide
    5439, // mark of the hidden satyr
    5890, // mark of the trained soldier
    5898, // mark of the deadly
    5897, // mark of the quick
    5895, // mark of the master
    5896, // mark of the verstile
    5427, // binding of critical strike
    5428, // binding of haste
    5429, // binding of mastery
    5430, // binding of versatility

    //some specs use warlords enchants because they're better than Legion ones currently
    //if people are using them, it should be because they know they're better
    5310, //gift of critical strike
    5311, //gift of haste
    5312, //gift of mastery
    5313, //gift of critical strike 2
    5314, //gift of versatility
  ];

  get enchantableGear() {
    return Object.keys(this.constructor.ENCHANTABLE_SLOTS).reduce((obj, slot) => {
      obj[slot] = this.selectedCombatant._getGearItemBySlotId(slot);
      return obj;
    }, {});
  }
  get numEnchantableGear() {
    return Object.keys(this.constructor.ENCHANTABLE_SLOTS).length;
  }
  get slotsMissingEnchant() {
    const gear = this.enchantableGear;
    return Object.keys(gear).filter(slot => !this.hasEnchant(gear[slot]));
  }
  get numSlotsMissingEnchant() {
    return this.slotsMissingEnchant.length;
  }
  get slotsMissingMaxEnchant() {
    const gear = this.enchantableGear;
    return Object.keys(gear).filter(slot => this.hasEnchant(gear[slot]) && !this.hasMaxEnchant(gear[slot]));
  }
  get numSlotsMissingMaxEnchant() {
    return this.slotsMissingMaxEnchant.length;
  }
  hasEnchant(item) {
    return !!item.permanentEnchant;
  }
  hasMaxEnchant(item) {
    return this.constructor.MAX_ENCHANT_IDS.includes(item.permanentEnchant);
  }

  suggestions(when) {
    const gear = this.enchantableGear;
    // iterating with keys instead of value because the values don't store what slot is being looked at
    Object.keys(gear)
      .forEach(slot => {
        const item = gear[slot];
        const slotName = this.constructor.ENCHANTABLE_SLOTS[slot];
        const hasEnchant = this.hasEnchant(item);

        when(hasEnchant).isFalse()
          .addSuggestion((suggest, actual, recommended) => {
            return suggest(
              <React.Fragment>
                Your <ItemLink id={item.id} quality={item.quality} details={item} icon={false}>{slotName}</ItemLink> is missing an enchant. Apply a strong enchant to very easily increase your throughput slightly.
              </React.Fragment>
            )
              .icon(item.icon)
              .staticImportance(SUGGESTION_IMPORTANCE.MAJOR);
          });

        const noMaxEnchant = hasEnchant && !this.hasMaxEnchant(item);
        when(noMaxEnchant).isTrue()
          .addSuggestion((suggest, actual, recommended) => {
            return suggest(
              <React.Fragment>
                Your <ItemLink id={item.id} quality={item.quality} details={item} icon={false}>{slotName}</ItemLink> has a cheap enchant. Apply a strong enchant to very easily increase your throughput slightly.
              </React.Fragment>
            )
              .icon(item.icon)
              .staticImportance(SUGGESTION_IMPORTANCE.MINOR);
          });
      });
  }
}

export default EnchantChecker;
<commit_msg>updating enchant ids<commit_after>import React from 'react';

import ItemLink from 'common/ItemLink';

import Analyzer from 'Parser/Core/Analyzer';
import SUGGESTION_IMPORTANCE from 'Parser/Core/ISSUE_IMPORTANCE';

// Example logs with missing enchants:
// https://www.warcraftlogs.com/reports/ydxavfGq1mBrM9Vc/#fight=1&source=14

class EnchantChecker extends Analyzer {

  static ENCHANTABLE_SLOTS = {
    10: 'Ring',
    11: 'Ring',
    15: 'Weapon',
    16: 'OffHand',
  };
  static MAX_ENCHANT_IDS = [
    //BfA enchants
    5946, // Weapon Enchant - Coastal Surge
    5950, // Weapon Enchant - Gale-Force Striking
    5949, // Weapon Enchant - Torrent of Elements
    5948, // Weapon Enchant - Siphoning
    5965, // Weapon Enchant - Deadly Navigation
    5964, // Weapon Enchant - Masterful Navigation
    5963, // Weapon Enchant - Quick Navigation
    5966, // Weapon Enchant - Stalwart Navigation
    5962, // Weapon Enchant - Versatile Navigation

    5938, // Seal of Critical Strike
    5939, // Seal of Haste
    5940, // Seal of Mastery
    5941, // Seal of Versatility
    5942, // Pact of Critical Strike
    5943, // Pact of Haste
    5944, // Pact of Mastery
    5945, // Pact of Versatility
  ];

  get enchantableGear() {
    return Object.keys(this.constructor.ENCHANTABLE_SLOTS).reduce((obj, slot) => {
      obj[slot] = this.selectedCombatant._getGearItemBySlotId(slot);
      return obj;
    }, {});
  }
  get numEnchantableGear() {
    return Object.keys(this.constructor.ENCHANTABLE_SLOTS).length;
  }
  get slotsMissingEnchant() {
    const gear = this.enchantableGear;
    return Object.keys(gear).filter(slot => !this.hasEnchant(gear[slot]));
  }
  get numSlotsMissingEnchant() {
    return this.slotsMissingEnchant.length;
  }
  get slotsMissingMaxEnchant() {
    const gear = this.enchantableGear;
    return Object.keys(gear).filter(slot => this.hasEnchant(gear[slot]) && !this.hasMaxEnchant(gear[slot]));
  }
  get numSlotsMissingMaxEnchant() {
    return this.slotsMissingMaxEnchant.length;
  }
  hasEnchant(item) {
    return !!item.permanentEnchant;
  }
  hasMaxEnchant(item) {
    return this.constructor.MAX_ENCHANT_IDS.includes(item.permanentEnchant);
  }

  suggestions(when) {
    const gear = this.enchantableGear;
    // iterating with keys instead of value because the values don't store what slot is being looked at
    Object.keys(gear)
      .forEach(slot => {
        const item = gear[slot];
        const slotName = this.constructor.ENCHANTABLE_SLOTS[slot];
        const hasEnchant = this.hasEnchant(item);

        when(hasEnchant).isFalse()
          .addSuggestion((suggest, actual, recommended) => {
            return suggest(
              <React.Fragment>
                Your <ItemLink id={item.id} quality={item.quality} details={item} icon={false}>{slotName}</ItemLink> is missing an enchant. Apply a strong enchant to very easily increase your throughput slightly.
              </React.Fragment>
            )
              .icon(item.icon)
              .staticImportance(SUGGESTION_IMPORTANCE.MAJOR);
          });

        const noMaxEnchant = hasEnchant && !this.hasMaxEnchant(item);
        when(noMaxEnchant).isTrue()
          .addSuggestion((suggest, actual, recommended) => {
            return suggest(
              <React.Fragment>
                Your <ItemLink id={item.id} quality={item.quality} details={item} icon={false}>{slotName}</ItemLink> has a cheap enchant. Apply a strong enchant to very easily increase your throughput slightly.
              </React.Fragment>
            )
              .icon(item.icon)
              .staticImportance(SUGGESTION_IMPORTANCE.MINOR);
          });
      });
  }
}

export default EnchantChecker;
<|endoftext|>


<commit_before>import React from 'react'
import { assoc, reduce, unapply, converge, mergeAll, map } from 'ramda'
const { check_tests, date_max, exists } = require('app/validate')
const standardise_date = require('app/standardise_date')

import SubDueForm from './sub_due_form.js'
import { fields } from '../form_fields/sub_due_form.js'

export default ({ fetch_sub_due, component, checker, ...props }) => {
  const send_request = (data) => {
    fetch_sub_due(map(standardise_date, data))
  }
  console.log("in sub due correspondence");
  console.log(checker);

  return (

      {checker
        ? component(props)
        : <SubDueForm fields={fields} onSubmit={send_request} validate={validate} button_text='Show Members'/>
      }

  )
}

const validate = values => {
  const date_tests = reduce
    ( (tests, key) =>
      assoc(key, date_max, tests)
    , {}
    , fields
    )

  const required_tests = reduce
    ( (tests, key) =>
      assoc(key, exists, tests)
    , {}
    , fields
  )

  return converge(unapply(mergeAll),
    [ check_tests('invalid date', date_tests)
    , check_tests('required', required_tests)
    ])(values)
}
<commit_msg>#522 - remove logs<commit_after>import React from 'react'
import { assoc, reduce, unapply, converge, mergeAll, map } from 'ramda'
const { check_tests, date_max, exists } = require('app/validate')
const standardise_date = require('app/standardise_date')

import SubDueForm from './sub_due_form.js'
import { fields } from '../form_fields/sub_due_form.js'

export default ({ fetch_sub_due, component, checker, ...props }) => {
  const send_request = (data) => {
    fetch_sub_due(map(standardise_date, data))
  }

  return (

      {checker
        ? component(props)
        : <SubDueForm fields={fields} onSubmit={send_request} validate={validate} button_text='Show Members'/>
      }

  )
}

const validate = values => {
  const date_tests = reduce
    ( (tests, key) =>
      assoc(key, date_max, tests)
    , {}
    , fields
    )

  const required_tests = reduce
    ( (tests, key) =>
      assoc(key, exists, tests)
    , {}
    , fields
  )

  return converge(unapply(mergeAll),
    [ check_tests('invalid date', date_tests)
    , check_tests('required', required_tests)
    ])(values)
}
<|endoftext|>


<commit_before>'use strict';

angular.module('mopify.music.tracklist', [
    'ngRoute',
    'mopify.services.mopidy',
    'mopify.services.util',
    'mopify.services.station',
    'mopify.services.spotifylogin',
    'mopify.services.servicemanager',
    'spotify',
    'ngSanitize',
    'llNotifier',
    'mopify.widgets.directive.track',
    'infinite-scroll'
])

/**
 * Every controller start with defining its own routes.
 */
.config(function($routeProvider) {
    $routeProvider.when("/music/tracklist/:uri/:name?", {
        templateUrl: "music/tracklist/tracklist.tmpl.html",
        controller: "TracklistController"
    });
})

/**
 * After defining the routes we create the controller for this module
 */
.controller("TracklistController", function TracklistController($scope, $rootScope, $timeout, $routeParams, mopidyservice, stationservice, util, Spotify, SpotifyLogin, ServiceManager, notifier){
    // Grab params in var
    var uri = $routeParams.uri;

    // Set default coverimage
    $scope.coverImage = "./assets/images/playlists-header.jpg";

    // Check mopidy state and call loadtracks function
    $scope.$on("mopidy:state:online", loadTracks);
    $scope.$on("mopidy:state:online", loadCurrentTrack);

    // Load tracks when connected
    if(mopidyservice.isConnected){
        loadTracks();
        loadCurrentTrack();
    }

    var albumtracks = [];

    // Define the type from the uri parameter
    if(uri.indexOf(":playlist:") > -1)
        $scope.type = "Playlist";

    if(uri.indexOf(":album:") > -1){
        $scope.type = "Album";

        $scope.albumAlreadySaved = false;

        if(ServiceManager.isEnabled("spotify") && SpotifyLogin.connected){
            // First get the album's tracks
            Spotify.getAlbumTracks(uri, {limit: 50}).then(function(response){
                albumtracks = _.map(response.items, function(track){
                    return track.id;
                });

                // Check if the user is already following the tracks
                Spotify.userTracksContains(albumtracks).then(function (following) {
                    $scope.albumAlreadySaved = following[0];
                });
            });

            $scope.showSaveAlbum = true;
        }
        else{
            $scope.showSaveAlbum = false;
        }
    }

    if(uri.indexOf("mopidy:current") > -1){
        $scope.type = "tracklist";
        $scope.coverImage = "./assets/images/tracklist-header.jpg";
    }

    if(uri.indexOf("spotify:library:songs") > -1){
        $scope.type = "My Music - Songs";
        $scope.coverImage = "./assets/images/tracklist-header.jpg";
    }

    // Check if this is a playlist from the loggedin Spotify user
    if($scope.type == "Playlist"){
        $scope.isowner = false;
        var ownerid = uri.split(":")[2];

        Spotify.getCurrentUser().then(function(data){
            if(ownerid == data.id){
                $scope.isowner = true;
            }
        });
    }

    // Check if a name has been defined
    if($routeParams.name !== undefined)
        $scope.name = $routeParams.name;
    else if(uri.indexOf("mopidy:") > -1)
        $scope.name = "Current tracklist";
    else if(uri.indexOf("spotify:library:songs") > -1)
        $scope.name = "Your music: Songs";
    else
        $scope.name = "";

    $scope.tracks = [];
    $scope.currentPlayingTrack = {};

    $scope.loadedTracks = [];

    if($scope.type == "Playlist"){
        loadSpotifyInfo();
    }

    // Load the user's library tracks is the type equals songs
    if($scope.type == "My Music - Songs"){

        $rootScope.$on("mopify:spotify:connected", function(){
            loadSpotifyLibraryTracks();
        });

        loadSpotifyLibraryTracks();
    }

    /**
     * Load the tracks from the mopidy library
     */
    function loadTracks(){
        // Get curren tracklist from Mopidy
        if(uri.indexOf("mopidy:") > -1){
            mopidyservice.getTracklist().then(function(tracks){

                var mappedTracks = tracks.map(function(tltrack){
                    return tltrack.track;
                });

                $scope.tracks = angular.copy(mappedTracks);
            });

            $scope.$on('mopidy:event:tracklistChanged', loadTracks);
        }

        // Lookup the tracks for the given album or playlist
        if(uri.indexOf("spotify:") > -1){
            mopidyservice.lookup(uri).then(function(tracks){
                // Check if the $scope.tracks contains loading tracks
                var loadingTracks = false;

                _.each(tracks, function(track){
                    if(track.name.indexOf("[loading]") > -1)
                        loadingTracks = true;
                });

                if(loadingTracks){
                    $timeout(loadTracks, 1000);
                }
                else{
                    $scope.loadedTracks = angular.copy(tracks);

                    var random = Math.floor((Math.random() * tracks.length) + 0);

                    if($scope.type == "Album")
                        getCoverImage(tracks[random]);

                    $scope.getMoreTracks();
                }
            });

        }
    }

    /**
     * Load information about the playlist from Spotify
     */
    function loadSpotifyInfo(){
        var splitteduri = uri.split(":");
        var userid = splitteduri[2];
        var playlistid = splitteduri[4];

        if(ServiceManager.isEnabled("spotify") && SpotifyLogin.connected){
            Spotify.getPlaylist(userid, playlistid).then(function(data){
                $scope.name = data.name + " from " + data.owner.id;
            });
        }
        else{
            $scope.name = "Playlist from " + userid;
        }
    }

    /**
     * Load the current playing track
     */
    function loadCurrentTrack(){
        mopidyservice.getCurrentTrack().then(function(track){
            $scope.currentPlayingTrack = track;
        });

        // Update information on a new track
        $scope.$on('mopidy:event:trackPlaybackEnded', function(event, data) {
            if(data.tl_track !== undefined)
                $scope.currentPlayingTrack = data.tl_track.track;
        });
        $scope.$on('mopidy:event:trackPlaybackStarted', function(event, data) {
            if(data.tl_track !== undefined)
                $scope.currentPlayingTrack = data.tl_track.track;
        });
    }

    /**
     * Load the user's Spotify Library tracks
     * @param {int} offset the offset to load the track, will be zero if not defined
     */
    function loadSpotifyLibraryTracks(offset){
        if(ServiceManager.isEnabled("spotify") && SpotifyLogin.connected){

            if(offset === undefined)
                offset = 0;

            Spotify.getSavedUserTracks({
                limit: 50,
                offset: offset
            }).then(function(response){

                // Map all track from the response's items array
                var tracks = _.map(response.items, function(item){
                    return item.track;
                });

                // Concat with previous tracks
                $scope.loadedTracks = $scope.loadedTracks.concat(tracks);

                if(response.next !== null)
                    loadSpotifyLibraryTracks(offset + 50);
                else
                    $scope.getMoreTracks();
            });
        }
        else if(!ServiceManager.isEnabled("spotify")){
            notifier.notify({type: "custom", template: "Please connect with the Spotify service first.", delay: 3000});
        }
    }

    /**
     * Get an album image from Spotify
     * @param  {track} track
     */
    function getCoverImage(track){
        Spotify.getTrack(track.uri).then(function(data) {
            $scope.coverImage = data.album.images[0].url;
        });
    }

    /**
     * Remove the album from the user's library
     */
    $scope.toggleSaveAlbum = function(){
        if($scope.type == "Album"){
            if(ServiceManager.isEnabled("spotify") && SpotifyLogin.connected){

                if($scope.albumAlreadySaved){
                    // Remove
                    Spotify.removeUserTracks(albumtracks).then(function (data) {
                        notifier.notify({type: "custom", template: "Album succesfully removed.", delay: 5000});
                        $scope.albumAlreadySaved = false;
                    }, function(data){
                        notifier.notify({type: "custom", template: "Something wen't wrong, please try again.", delay: 5000});
                    });
                }
                else{
                    // Save
                    Spotify.saveUserTracks(albumtracks).then(function (data) {
                        notifier.notify({type: "custom", template: "Album succesfully saved.", delay: 5000});
                        $scope.albumAlreadySaved = true;
                    }, function(data){
                        notifier.notify({type: "custom", template: "Something wen't wrong, please try again.", delay: 5000});
                    });
                }

            }
            else{
                notifier.notify({type: "custom", template: "Can't add album. Are you connected with Spotify?", delay: 5000});
            }
        }
    };

    /**
     * Add the current tracks to the tracklist, shuffle them and play
     */
    $scope.shuffle = function(){
        if(mopidyservice.isConnected){
            // Clear tracklist
            mopidyservice.clearTracklist().then(function(){
                // Add track to tracklist
                mopidyservice.addToTracklist({ uri: uri }).then(function(tltacks){
                    // Set random to true
                    mopidyservice.setRandom(true).then(function(){
                        // Start with random track
                        mopidyservice.play(tltacks[Math.floor(Math.random() * tltacks.length)]);

                        // Broadcast control change
                        $rootScope.$broadcast("mopify:playercontrols:changed");
                    });
                });

            });

        }
    };

    /**
     * Start a new station based on the tracks in the current view
     */
    $scope.startStation = function(){
        if(uri.indexOf("spotify:") > -1)
            stationservice.startFromSpotifyUri(uri);

        if(uri.indexOf("mopidy:") > -1)
            stationservice.startFromTracks($scope.tracks);
    };

    var tracksPerCall = 20;

    /*
     * Add {trackspercall} tracks to the scope
     * This function is used in combination with infinite scroll
     */
    $scope.getMoreTracks = function(){
        if($scope.loadedTracks.length > 0){
            var current = $scope.tracks;
            var toAdd = $scope.loadedTracks.slice(0, tracksPerCall);

            $scope.tracks = current.concat(toAdd);
        }
    };

});<commit_msg>Increased tracks per call to support bigger screens #76<commit_after>'use strict';

angular.module('mopify.music.tracklist', [
    'ngRoute',
    'mopify.services.mopidy',
    'mopify.services.util',
    'mopify.services.station',
    'mopify.services.spotifylogin',
    'mopify.services.servicemanager',
    'spotify',
    'ngSanitize',
    'llNotifier',
    'mopify.widgets.directive.track',
    'infinite-scroll'
])

/**
 * Every controller start with defining its own routes.
 */
.config(function($routeProvider) {
    $routeProvider.when("/music/tracklist/:uri/:name?", {
        templateUrl: "music/tracklist/tracklist.tmpl.html",
        controller: "TracklistController"
    });
})

/**
 * After defining the routes we create the controller for this module
 */
.controller("TracklistController", function TracklistController($scope, $rootScope, $timeout, $routeParams, mopidyservice, stationservice, util, Spotify, SpotifyLogin, ServiceManager, notifier){
    // Grab params in var
    var uri = $routeParams.uri;

    // Set default coverimage
    $scope.coverImage = "./assets/images/playlists-header.jpg";

    // Check mopidy state and call loadtracks function
    $scope.$on("mopidy:state:online", loadTracks);
    $scope.$on("mopidy:state:online", loadCurrentTrack);

    // Load tracks when connected
    if(mopidyservice.isConnected){
        loadTracks();
        loadCurrentTrack();
    }

    var albumtracks = [];

    // Define the type from the uri parameter
    if(uri.indexOf(":playlist:") > -1)
        $scope.type = "Playlist";

    if(uri.indexOf(":album:") > -1){
        $scope.type = "Album";

        $scope.albumAlreadySaved = false;

        if(ServiceManager.isEnabled("spotify") && SpotifyLogin.connected){
            // First get the album's tracks
            Spotify.getAlbumTracks(uri, {limit: 50}).then(function(response){
                albumtracks = _.map(response.items, function(track){
                    return track.id;
                });

                // Check if the user is already following the tracks
                Spotify.userTracksContains(albumtracks).then(function (following) {
                    $scope.albumAlreadySaved = following[0];
                });
            });

            $scope.showSaveAlbum = true;
        }
        else{
            $scope.showSaveAlbum = false;
        }
    }

    if(uri.indexOf("mopidy:current") > -1){
        $scope.type = "tracklist";
        $scope.coverImage = "./assets/images/tracklist-header.jpg";
    }

    if(uri.indexOf("spotify:library:songs") > -1){
        $scope.type = "My Music - Songs";
        $scope.coverImage = "./assets/images/tracklist-header.jpg";
    }

    // Check if this is a playlist from the loggedin Spotify user
    if($scope.type == "Playlist"){
        $scope.isowner = false;
        var ownerid = uri.split(":")[2];

        Spotify.getCurrentUser().then(function(data){
            if(ownerid == data.id){
                $scope.isowner = true;
            }
        });
    }

    // Check if a name has been defined
    if($routeParams.name !== undefined)
        $scope.name = $routeParams.name;
    else if(uri.indexOf("mopidy:") > -1)
        $scope.name = "Current tracklist";
    else if(uri.indexOf("spotify:library:songs") > -1)
        $scope.name = "Your music: Songs";
    else
        $scope.name = "";

    $scope.tracks = [];
    $scope.currentPlayingTrack = {};

    $scope.loadedTracks = [];

    if($scope.type == "Playlist"){
        loadSpotifyInfo();
    }

    // Load the user's library tracks is the type equals songs
    if($scope.type == "My Music - Songs"){

        $rootScope.$on("mopify:spotify:connected", function(){
            loadSpotifyLibraryTracks();
        });

        loadSpotifyLibraryTracks();
    }

    /**
     * Load the tracks from the mopidy library
     */
    function loadTracks(){
        // Get curren tracklist from Mopidy
        if(uri.indexOf("mopidy:") > -1){
            mopidyservice.getTracklist().then(function(tracks){

                var mappedTracks = tracks.map(function(tltrack){
                    return tltrack.track;
                });

                $scope.tracks = angular.copy(mappedTracks);
            });

            $scope.$on('mopidy:event:tracklistChanged', loadTracks);
        }

        // Lookup the tracks for the given album or playlist
        if(uri.indexOf("spotify:") > -1){
            mopidyservice.lookup(uri).then(function(tracks){
                // Check if the $scope.tracks contains loading tracks
                var loadingTracks = false;

                _.each(tracks, function(track){
                    if(track.name.indexOf("[loading]") > -1)
                        loadingTracks = true;
                });

                if(loadingTracks){
                    $timeout(loadTracks, 1000);
                }
                else{
                    $scope.loadedTracks = angular.copy(tracks);

                    var random = Math.floor((Math.random() * tracks.length) + 0);

                    if($scope.type == "Album")
                        getCoverImage(tracks[random]);

                    $scope.getMoreTracks();
                }
            });

        }
    }

    /**
     * Load information about the playlist from Spotify
     */
    function loadSpotifyInfo(){
        var splitteduri = uri.split(":");
        var userid = splitteduri[2];
        var playlistid = splitteduri[4];

        if(ServiceManager.isEnabled("spotify") && SpotifyLogin.connected){
            Spotify.getPlaylist(userid, playlistid).then(function(data){
                $scope.name = data.name + " from " + data.owner.id;
            });
        }
        else{
            $scope.name = "Playlist from " + userid;
        }
    }

    /**
     * Load the current playing track
     */
    function loadCurrentTrack(){
        mopidyservice.getCurrentTrack().then(function(track){
            $scope.currentPlayingTrack = track;
        });

        // Update information on a new track
        $scope.$on('mopidy:event:trackPlaybackEnded', function(event, data) {
            if(data.tl_track !== undefined)
                $scope.currentPlayingTrack = data.tl_track.track;
        });
        $scope.$on('mopidy:event:trackPlaybackStarted', function(event, data) {
            if(data.tl_track !== undefined)
                $scope.currentPlayingTrack = data.tl_track.track;
        });
    }

    /**
     * Load the user's Spotify Library tracks
     * @param {int} offset the offset to load the track, will be zero if not defined
     */
    function loadSpotifyLibraryTracks(offset){
        if(ServiceManager.isEnabled("spotify") && SpotifyLogin.connected){

            if(offset === undefined)
                offset = 0;

            Spotify.getSavedUserTracks({
                limit: 50,
                offset: offset
            }).then(function(response){

                // Map all track from the response's items array
                var tracks = _.map(response.items, function(item){
                    return item.track;
                });

                // Concat with previous tracks
                $scope.loadedTracks = $scope.loadedTracks.concat(tracks);

                if(response.next !== null)
                    loadSpotifyLibraryTracks(offset + 50);
                else
                    $scope.getMoreTracks();
            });
        }
        else if(!ServiceManager.isEnabled("spotify")){
            notifier.notify({type: "custom", template: "Please connect with the Spotify service first.", delay: 3000});
        }
    }

    /**
     * Get an album image from Spotify
     * @param  {track} track
     */
    function getCoverImage(track){
        Spotify.getTrack(track.uri).then(function(data) {
            $scope.coverImage = data.album.images[0].url;
        });
    }

    /**
     * Remove the album from the user's library
     */
    $scope.toggleSaveAlbum = function(){
        if($scope.type == "Album"){
            if(ServiceManager.isEnabled("spotify") && SpotifyLogin.connected){

                if($scope.albumAlreadySaved){
                    // Remove
                    Spotify.removeUserTracks(albumtracks).then(function (data) {
                        notifier.notify({type: "custom", template: "Album succesfully removed.", delay: 5000});
                        $scope.albumAlreadySaved = false;
                    }, function(data){
                        notifier.notify({type: "custom", template: "Something wen't wrong, please try again.", delay: 5000});
                    });
                }
                else{
                    // Save
                    Spotify.saveUserTracks(albumtracks).then(function (data) {
                        notifier.notify({type: "custom", template: "Album succesfully saved.", delay: 5000});
                        $scope.albumAlreadySaved = true;
                    }, function(data){
                        notifier.notify({type: "custom", template: "Something wen't wrong, please try again.", delay: 5000});
                    });
                }

            }
            else{
                notifier.notify({type: "custom", template: "Can't add album. Are you connected with Spotify?", delay: 5000});
            }
        }
    };

    /**
     * Add the current tracks to the tracklist, shuffle them and play
     */
    $scope.shuffle = function(){
        if(mopidyservice.isConnected){
            // Clear tracklist
            mopidyservice.clearTracklist().then(function(){
                // Add track to tracklist
                mopidyservice.addToTracklist({ uri: uri }).then(function(tltacks){
                    // Set random to true
                    mopidyservice.setRandom(true).then(function(){
                        // Start with random track
                        mopidyservice.play(tltacks[Math.floor(Math.random() * tltacks.length)]);

                        // Broadcast control change
                        $rootScope.$broadcast("mopify:playercontrols:changed");
                    });
                });

            });

        }
    };

    /**
     * Start a new station based on the tracks in the current view
     */
    $scope.startStation = function(){
        if(uri.indexOf("spotify:") > -1)
            stationservice.startFromSpotifyUri(uri);

        if(uri.indexOf("mopidy:") > -1)
            stationservice.startFromTracks($scope.tracks);
    };

    var tracksPerCall = 40;

    /*
     * Add {trackspercall} tracks to the scope
     * This function is used in combination with infinite scroll
     */
    $scope.getMoreTracks = function(){
        if($scope.loadedTracks.length > 0){
            var current = $scope.tracks;
            var toAdd = $scope.loadedTracks.slice(0, tracksPerCall);

            $scope.tracks = current.concat(toAdd);
        }
    };

});<|endoftext|>


<commit_before>define([
  'angular',
  'lodash',
  'config',
  'kbn',
  'moment'
],
function (angular, _, config, kbn, moment) {
  'use strict';

  var module = angular.module('grafana.services');

  module.factory('ElasticDatasource', function($q, $http, templateSrv) {

    function ElasticDatasource(datasource) {
      this.type = 'elastic';
      this.basicAuth = datasource.basicAuth;
      this.url = datasource.url;
      this.name = datasource.name;
      this.index = datasource.index;
      this.grafanaDB = datasource.grafanaDB;
      this.searchMaxResults = config.search.max_results || 20;

      this.saveTemp = _.isUndefined(datasource.save_temp) ? true : datasource.save_temp;
      this.saveTempTTL = _.isUndefined(datasource.save_temp_ttl) ? '30d' : datasource.save_temp_ttl;

      this.annotationEditorSrc = 'app/partials/elasticsearch/annotation_editor.html';
      this.supportAnnotations = true;
      this.supportMetrics = false;
    }

    ElasticDatasource.prototype._request = function(method, url, index, data) {
      var options = {
        url: this.url + "/" + index + url,
        method: method,
        data: data
      };

      if (this.basicAuth) {
        options.headers = {
          "Authorization": "Basic " + this.basicAuth
        };
      }

      return $http(options);
    };

    ElasticDatasource.prototype._get = function(url) {
      return this._request('GET', url, this.index)
        .then(function(results) {
          return results.data;
        });
    };

    ElasticDatasource.prototype._post = function(url, data) {
      return this._request('POST', url, this.index, data)
        .then(function(results) {
          return results.data;
        });
    };

    ElasticDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) {
      var range = {};
      var timeField = annotation.timeField || '@timestamp';
      var queryString = annotation.query || '*';
      var tagsField = annotation.tagsField || 'tags';
      var titleField = annotation.titleField || 'desc';
      var textField = annotation.textField || null;

      range[annotation.timeField]= {
        from: rangeUnparsed.from,
        to: rangeUnparsed.to,
      };

      var queryInterpolated = templateSrv.replace(queryString);
      var filter = { "bool": { "must": [{ "range": range }] } };
      var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } };
      var data = {
        "fields": [timeField, "_source"],
        "query" : { "filtered": { "query" : query, "filter": filter } },
        "size": 100
      };

      return this._request('POST', '/_search', annotation.index, data).then(function(results) {
        var list = [];
        var hits = results.data.hits.hits;

        for (var i = 0; i < hits.length; i++) {
          var source = hits[i]._source;
          var fields = hits[i].fields;
          var time = source[timeField];

          if (_.isString(fields[timeField]) || _.isNumber(fields[timeField])) {
            time = fields[timeField];
          }

          var event = {
            annotation: annotation,
            time: moment.utc(time).valueOf(),
            title: source[titleField],
          };

          if (source[tagsField]) {
            if (_.isArray(source[tagsField])) {
              event.tags = source[tagsField].join(', ');
            }
            else {
              event.tags = source[tagsField];
            }
          }
          if (textField && source[textField]) {
            event.text = source[textField];
          }

          list.push(event);
        }
        return list;
      });
    };

    ElasticDatasource.prototype._getDashboardWithSlug = function(id) {
      return this._get('/dashboard/' + kbn.slugifyForUrl(id))
        .then(function(result) {
          return angular.fromJson(result._source.dashboard);
        }, function() {
          throw "Dashboard not found";
        });
    };

    ElasticDatasource.prototype.getDashboard = function(id, isTemp) {
      var url = '/dashboard/' + id;
      if (isTemp) { url = '/temp/' + id; }

      var self = this;
      return this._get(url)
        .then(function(result) {
          return angular.fromJson(result._source.dashboard);
        }, function(data) {
          if(data.status === 0) {
            throw "Could not contact Elasticsearch. Please ensure that Elasticsearch is reachable from your browser.";
          } else {
            // backward compatible fallback
            return self._getDashboardWithSlug(id);
          }
        });
    };

    ElasticDatasource.prototype.saveDashboard = function(dashboard) {
      var title = dashboard.title;
      var temp = dashboard.temp;
      if (temp) { delete dashboard.temp; }

      var data = {
        user: 'guest',
        group: 'guest',
        title: title,
        tags: dashboard.tags,
        dashboard: angular.toJson(dashboard)
      };

      if (temp) {
        return this._saveTempDashboard(data);
      }
      else {

        var id = encodeURIComponent(kbn.slugifyForUrl(title));
        var self = this;

        return this._request('PUT', '/dashboard/' + id, this.index, data)
          .then(function(results) {
            self._removeUnslugifiedDashboard(results, title);
            return { title: title, url: '/dashboard/db/' + id };
          }, function(err) {
            throw 'Failed to save to elasticsearch ' + err.data;
          });
      }
    };

    ElasticDatasource.prototype._removeUnslugifiedDashboard = function(saveResult, title) {
      if (saveResult.statusText !== 'Created') { return; }

      var self = this;
      this._get('/dashboard/' + title).then(function() {
        self.deleteDashboard(title);
      });
    };

    ElasticDatasource.prototype._saveTempDashboard = function(data) {
      return this._request('POST', '/temp/?ttl=' + this.saveTempTTL, this.index, data)
        .then(function(result) {

          var baseUrl = window.location.href.replace(window.location.hash,'');
          var url = baseUrl + "#dashboard/temp/" + result.data._id;

          return { title: data.title, url: url };

        }, function(err) {
          throw "Failed to save to temp dashboard to elasticsearch " + err.data;
        });
    };

    ElasticDatasource.prototype.deleteDashboard = function(id) {
      return this._request('DELETE', '/dashboard/' + id, this.index)
        .then(function(result) {
          return result.data._id;
        }, function(err) {
          throw err.data;
        });
    };

    ElasticDatasource.prototype.searchDashboards = function(queryString) {
      queryString = queryString.toLowerCase().replace(' and ', ' AND ');

      var tagsOnly = queryString.indexOf('tags!:') === 0;
      if (tagsOnly) {
        var tagsQuery = queryString.substring(6, queryString.length);
        queryString = 'tags:' + tagsQuery + '*';
      }
      else {
        if (queryString.length === 0) {
          queryString = 'title:';
        }

        if (queryString[queryString.length - 1] !== '*') {
          queryString += '*';
        }
      }

      var query = {
        query: { query_string: { query: queryString } },
        facets: { tags: { terms: { field: "tags", order: "term", size: 50 } } },
        size: this.searchMaxResults,
        sort: ["_uid"]
      };

      return this._post('/dashboard/_search', query)
        .then(function(results) {
          if(_.isUndefined(results.hits)) {
            return { dashboards: [], tags: [] };
          }

          var hits = { dashboards: [], tags: results.facets.tags.terms || [] };

          for (var i = 0; i < results.hits.hits.length; i++) {
            hits.dashboards.push({
              id: results.hits.hits[i]._id,
              title: results.hits.hits[i]._source.title,
              tags: results.hits.hits[i]._source.tags
            });
          }

          hits.tagsOnly = tagsOnly;
          return hits;
        });
    };

    return ElasticDatasource;

  });

});
<commit_msg>The elasticsearch datasource will now better handle language specifics when making the search partial (ie for search as you type).  This means the search field will now support significantly more complex searches such as:<commit_after>define([
  'angular',
  'lodash',
  'config',
  'kbn',
  'moment'
],
function (angular, _, config, kbn, moment) {
  'use strict';

  var module = angular.module('grafana.services');

  module.factory('ElasticDatasource', function($q, $http, templateSrv) {

    function ElasticDatasource(datasource) {
      this.type = 'elastic';
      this.basicAuth = datasource.basicAuth;
      this.url = datasource.url;
      this.name = datasource.name;
      this.index = datasource.index;
      this.grafanaDB = datasource.grafanaDB;
      this.searchMaxResults = config.search.max_results || 20;

      this.saveTemp = _.isUndefined(datasource.save_temp) ? true : datasource.save_temp;
      this.saveTempTTL = _.isUndefined(datasource.save_temp_ttl) ? '30d' : datasource.save_temp_ttl;

      this.annotationEditorSrc = 'app/partials/elasticsearch/annotation_editor.html';
      this.supportAnnotations = true;
      this.supportMetrics = false;
    }

    ElasticDatasource.prototype._request = function(method, url, index, data) {
      var options = {
        url: this.url + "/" + index + url,
        method: method,
        data: data
      };

      if (this.basicAuth) {
        options.headers = {
          "Authorization": "Basic " + this.basicAuth
        };
      }

      return $http(options);
    };

    ElasticDatasource.prototype._get = function(url) {
      return this._request('GET', url, this.index)
        .then(function(results) {
          return results.data;
        });
    };

    ElasticDatasource.prototype._post = function(url, data) {
      return this._request('POST', url, this.index, data)
        .then(function(results) {
          return results.data;
        });
    };

    ElasticDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) {
      var range = {};
      var timeField = annotation.timeField || '@timestamp';
      var queryString = annotation.query || '*';
      var tagsField = annotation.tagsField || 'tags';
      var titleField = annotation.titleField || 'desc';
      var textField = annotation.textField || null;

      range[annotation.timeField]= {
        from: rangeUnparsed.from,
        to: rangeUnparsed.to,
      };

      var queryInterpolated = templateSrv.replace(queryString);
      var filter = { "bool": { "must": [{ "range": range }] } };
      var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } };
      var data = {
        "fields": [timeField, "_source"],
        "query" : { "filtered": { "query" : query, "filter": filter } },
        "size": 100
      };

      return this._request('POST', '/_search', annotation.index, data).then(function(results) {
        var list = [];
        var hits = results.data.hits.hits;

        for (var i = 0; i < hits.length; i++) {
          var source = hits[i]._source;
          var fields = hits[i].fields;
          var time = source[timeField];

          if (_.isString(fields[timeField]) || _.isNumber(fields[timeField])) {
            time = fields[timeField];
          }

          var event = {
            annotation: annotation,
            time: moment.utc(time).valueOf(),
            title: source[titleField],
          };

          if (source[tagsField]) {
            if (_.isArray(source[tagsField])) {
              event.tags = source[tagsField].join(', ');
            }
            else {
              event.tags = source[tagsField];
            }
          }
          if (textField && source[textField]) {
            event.text = source[textField];
          }

          list.push(event);
        }
        return list;
      });
    };

    ElasticDatasource.prototype._getDashboardWithSlug = function(id) {
      return this._get('/dashboard/' + kbn.slugifyForUrl(id))
        .then(function(result) {
          return angular.fromJson(result._source.dashboard);
        }, function() {
          throw "Dashboard not found";
        });
    };

    ElasticDatasource.prototype.getDashboard = function(id, isTemp) {
      var url = '/dashboard/' + id;
      if (isTemp) { url = '/temp/' + id; }

      var self = this;
      return this._get(url)
        .then(function(result) {
          return angular.fromJson(result._source.dashboard);
        }, function(data) {
          if(data.status === 0) {
            throw "Could not contact Elasticsearch. Please ensure that Elasticsearch is reachable from your browser.";
          } else {
            // backward compatible fallback
            return self._getDashboardWithSlug(id);
          }
        });
    };

    ElasticDatasource.prototype.saveDashboard = function(dashboard) {
      var title = dashboard.title;
      var temp = dashboard.temp;
      if (temp) { delete dashboard.temp; }

      var data = {
        user: 'guest',
        group: 'guest',
        title: title,
        tags: dashboard.tags,
        dashboard: angular.toJson(dashboard)
      };

      if (temp) {
        return this._saveTempDashboard(data);
      }
      else {

        var id = encodeURIComponent(kbn.slugifyForUrl(title));
        var self = this;

        return this._request('PUT', '/dashboard/' + id, this.index, data)
          .then(function(results) {
            self._removeUnslugifiedDashboard(results, title);
            return { title: title, url: '/dashboard/db/' + id };
          }, function(err) {
            throw 'Failed to save to elasticsearch ' + err.data;
          });
      }
    };

    ElasticDatasource.prototype._removeUnslugifiedDashboard = function(saveResult, title) {
      if (saveResult.statusText !== 'Created') { return; }

      var self = this;
      this._get('/dashboard/' + title).then(function() {
        self.deleteDashboard(title);
      });
    };

    ElasticDatasource.prototype._saveTempDashboard = function(data) {
      return this._request('POST', '/temp/?ttl=' + this.saveTempTTL, this.index, data)
        .then(function(result) {

          var baseUrl = window.location.href.replace(window.location.hash,'');
          var url = baseUrl + "#dashboard/temp/" + result.data._id;

          return { title: data.title, url: url };

        }, function(err) {
          throw "Failed to save to temp dashboard to elasticsearch " + err.data;
        });
    };

    ElasticDatasource.prototype.deleteDashboard = function(id) {
      return this._request('DELETE', '/dashboard/' + id, this.index)
        .then(function(result) {
          return result.data._id;
        }, function(err) {
          throw err.data;
        });
    };

    ElasticDatasource.prototype.searchDashboards = function(queryString) {
      var endsInOpen = function(string, opener, closer) {
        var character;
        var count = 0;
        for (var i=0; i<string.length; i++) {
          character = string[i];

          if (character == opener) {
            count++;
          } else if (character == closer) {
            count--;
          }
        }

        return count > 0;
      }

      var tagsOnly = queryString.indexOf('tags!:') === 0;
      if (tagsOnly) {
        var tagsQuery = queryString.substring(6, queryString.length);
        queryString = 'tags:' + tagsQuery + '*';
      }
      else {
        if (queryString.length === 0) {
          queryString = 'title:';
        }

        // make this a partial search if we're not in some reserved portion of the language,  comments on conditionals, in order:
        // 1. ends in reserved character, boosting, boolean operator ( -foo)
        // 2. typing a reserved word like AND, OR, NOT
        // 3. open parens (groupiing)
        // 4. open " (term phrase)
        // 5. open [ (range)
        // 6. open { (range)
        // see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax
        if (!queryString.match(/(\*|\]|}|~|\)|"|^\d+|\s[\-+]\w+)$/) &&
            !queryString.match(/[A-Z]$/) &&
            !endsInOpen(queryString, '(', ')') &&
            !endsInOpen(queryString, '"', '"') &&
            !endsInOpen(queryString, '[', ']') && !endsInOpen(queryString, '[', '}') &&
            !endsInOpen(queryString, '{', ']') && !endsInOpen(queryString, '{', '}')
        ){
          queryString += '*';
        }
      }

      var query = {
        query: { query_string: { query: queryString } },
        facets: { tags: { terms: { field: "tags", order: "term", size: 50 } } },
        size: this.searchMaxResults,
        sort: ["_uid"]
      };

      return this._post('/dashboard/_search', query)
        .then(function(results) {
          if(_.isUndefined(results.hits)) {
            return { dashboards: [], tags: [] };
          }

          var hits = { dashboards: [], tags: results.facets.tags.terms || [] };

          for (var i = 0; i < results.hits.hits.length; i++) {
            hits.dashboards.push({
              id: results.hits.hits[i]._id,
              title: results.hits.hits[i]._source.title,
              tags: results.hits.hits[i]._source.tags
            });
          }

          hits.tagsOnly = tagsOnly;
          return hits;
        });
    };

    return ElasticDatasource;

  });

});
<|endoftext|>


<commit_before>/* eslint-disable */
/* tslint:disable */

/**
 * Mock Service Worker.
 * @see https://github.com/mswjs/msw
 * - Please do NOT modify this file.
 * - Please do NOT serve this file on production.
 */

const INTEGRITY_CHECKSUM = '82ef9b96d8393b6da34527d1d6e19187'
const bypassHeaderName = 'x-msw-bypass'
const activeClientIds = new Set()

self.addEventListener('install', function () {
  return self.skipWaiting()
})

self.addEventListener('activate', async function (event) {
  return self.clients.claim()
})

self.addEventListener('message', async function (event) {
  const clientId = event.source.id

  if (!clientId || !self.clients) {
    return
  }

  const client = await self.clients.get(clientId)

  if (!client) {
    return
  }

  const allClients = await self.clients.matchAll()

  switch (event.data) {
    case 'KEEPALIVE_REQUEST': {
      sendToClient(client, {
        type: 'KEEPALIVE_RESPONSE',
      })
      break
    }

    case 'INTEGRITY_CHECK_REQUEST': {
      sendToClient(client, {
        type: 'INTEGRITY_CHECK_RESPONSE',
        payload: INTEGRITY_CHECKSUM,
      })
      break
    }

    case 'MOCK_ACTIVATE': {
      activeClientIds.add(clientId)

      sendToClient(client, {
        type: 'MOCKING_ENABLED',
        payload: true,
      })
      break
    }

    case 'MOCK_DEACTIVATE': {
      activeClientIds.delete(clientId)
      break
    }

    case 'CLIENT_CLOSED': {
      activeClientIds.delete(clientId)

      const remainingClients = allClients.filter((client) => {
        return client.id !== clientId
      })

      // Unregister itself when there are no more clients
      if (remainingClients.length === 0) {
        self.registration.unregister()
      }

      break
    }
  }
})

// Resolve the "master" client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMasterClient(event) {
  const client = await self.clients.get(event.clientId)

  if (client.frameType === 'top-level') {
    return client
  }

  const allClients = await self.clients.matchAll()

  return allClients
    .filter((client) => {
      // Get only those clients that are currently visible.
      return client.visibilityState === 'visible'
    })
    .find((client) => {
      // Find the client ID that's recorded in the
      // set of clients that have registered the worker.
      return activeClientIds.has(client.id)
    })
}

async function handleRequest(event, requestId) {
  const client = await resolveMasterClient(event)
  const response = await getResponse(event, client, requestId)

  // Send back the response clone for the "response:*" life-cycle events.
  // Ensure MSW is active and ready to handle the message, otherwise
  // this message will pend indefinitely.
  if (client && activeClientIds.has(client.id)) {
    ;(async function () {
      const clonedResponse = response.clone()
      sendToClient(client, {
        type: 'RESPONSE',
        payload: {
          requestId,
          type: clonedResponse.type,
          ok: clonedResponse.ok,
          status: clonedResponse.status,
          statusText: clonedResponse.statusText,
          body:
            clonedResponse.body === null ? null : await clonedResponse.text(),
          headers: serializeHeaders(clonedResponse.headers),
          redirected: clonedResponse.redirected,
        },
      })
    })()
  }

  return response
}

async function getResponse(event, client, requestId) {
  const { request } = event
  const requestClone = request.clone()
  const getOriginalResponse = () => fetch(requestClone)

  // Bypass mocking when the request client is not active.
  if (!client) {
    return getOriginalResponse()
  }

  // Bypass initial page load requests (i.e. static assets).
  // The absence of the immediate/parent client in the map of the active clients
  // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
  // and is not ready to handle requests.
  if (!activeClientIds.has(client.id)) {
    return await getOriginalResponse()
  }

  // Bypass requests with the explicit bypass header
  if (requestClone.headers.get(bypassHeaderName) === 'true') {
    const cleanRequestHeaders = serializeHeaders(requestClone.headers)

    // Remove the bypass header to comply with the CORS preflight check.
    delete cleanRequestHeaders[bypassHeaderName]

    const originalRequest = new Request(requestClone, {
      headers: new Headers(cleanRequestHeaders),
    })

    return fetch(originalRequest)
  }

  // Send the request to the client-side MSW.
  const reqHeaders = serializeHeaders(request.headers)
  const body = await request.text()

  const clientMessage = await sendToClient(client, {
    type: 'REQUEST',
    payload: {
      id: requestId,
      url: request.url,
      method: request.method,
      headers: reqHeaders,
      cache: request.cache,
      mode: request.mode,
      credentials: request.credentials,
      destination: request.destination,
      integrity: request.integrity,
      redirect: request.redirect,
      referrer: request.referrer,
      referrerPolicy: request.referrerPolicy,
      body,
      bodyUsed: request.bodyUsed,
      keepalive: request.keepalive,
    },
  })

  switch (clientMessage.type) {
    case 'MOCK_SUCCESS': {
      return delayPromise(
        () => respondWithMock(clientMessage),
        clientMessage.payload.delay,
      )
    }

    case 'MOCK_NOT_FOUND': {
      return getOriginalResponse()
    }

    case 'NETWORK_ERROR': {
      const { name, message } = clientMessage.payload
      const networkError = new Error(message)
      networkError.name = name

      // Rejecting a request Promise emulates a network error.
      throw networkError
    }

    case 'INTERNAL_ERROR': {
      const parsedBody = JSON.parse(clientMessage.payload.body)

      console.error(
        `\
[MSW] Request handler function for "%s %s" has thrown the following exception:

${parsedBody.errorType}: ${parsedBody.message}
(see more detailed error stack trace in the mocked response body)

This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error.
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
`,
        request.method,
        request.url,
      )

      return respondWithMock(clientMessage)
    }
  }

  return getOriginalResponse()
}

self.addEventListener('fetch', function (event) {
  const { request } = event

  // Bypass navigation requests.
  if (request.mode === 'navigate') {
    return
  }

  // Opening the DevTools triggers the "only-if-cached" request
  // that cannot be handled by the worker. Bypass such requests.
  if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
    return
  }

  // Bypass all requests when there are no active clients.
  // Prevents the self-unregistered worked from handling requests
  // after it's been deleted (still remains active until the next reload).
  if (activeClientIds.size === 0) {
    return
  }

  const requestId = uuidv4()

  return event.respondWith(
    handleRequest(event, requestId).catch((error) => {
      console.error(
        '[MSW] Failed to mock a "%s" request to "%s": %s',
        request.method,
        request.url,
        error,
      )
    }),
  )
})

function serializeHeaders(headers) {
  const reqHeaders = {}
  headers.forEach((value, name) => {
    reqHeaders[name] = reqHeaders[name]
      ? [].concat(reqHeaders[name]).concat(value)
      : value
  })
  return reqHeaders
}

function sendToClient(client, message) {
  return new Promise((resolve, reject) => {
    const channel = new MessageChannel()

    channel.port1.onmessage = (event) => {
      if (event.data && event.data.error) {
        return reject(event.data.error)
      }

      resolve(event.data)
    }

    client.postMessage(JSON.stringify(message), [channel.port2])
  })
}

function delayPromise(cb, duration) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(cb()), duration)
  })
}

function respondWithMock(clientMessage) {
  return new Response(clientMessage.payload.body, {
    ...clientMessage.payload,
    headers: clientMessage.payload.headers,
  })
}

function uuidv4() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    const r = (Math.random() * 16) | 0
    const v = c == 'x' ? r : (r & 0x3) | 0x8
    return v.toString(16)
  })
}
<commit_msg>chore(deps): update mockServiceWorker.js<commit_after>/* eslint-disable */
/* tslint:disable */

/**
 * Mock Service Worker (0.33.2).
 * @see https://github.com/mswjs/msw
 * - Please do NOT modify this file.
 * - Please do NOT serve this file on production.
 */

const INTEGRITY_CHECKSUM = 'a615cd395ea10f948a628bce3857a385'
const bypassHeaderName = 'x-msw-bypass'
const activeClientIds = new Set()

self.addEventListener('install', function () {
  return self.skipWaiting()
})

self.addEventListener('activate', async function (event) {
  return self.clients.claim()
})

self.addEventListener('message', async function (event) {
  const clientId = event.source.id

  if (!clientId || !self.clients) {
    return
  }

  const client = await self.clients.get(clientId)

  if (!client) {
    return
  }

  const allClients = await self.clients.matchAll()

  switch (event.data) {
    case 'KEEPALIVE_REQUEST': {
      sendToClient(client, {
        type: 'KEEPALIVE_RESPONSE',
      })
      break
    }

    case 'INTEGRITY_CHECK_REQUEST': {
      sendToClient(client, {
        type: 'INTEGRITY_CHECK_RESPONSE',
        payload: INTEGRITY_CHECKSUM,
      })
      break
    }

    case 'MOCK_ACTIVATE': {
      activeClientIds.add(clientId)

      sendToClient(client, {
        type: 'MOCKING_ENABLED',
        payload: true,
      })
      break
    }

    case 'MOCK_DEACTIVATE': {
      activeClientIds.delete(clientId)
      break
    }

    case 'CLIENT_CLOSED': {
      activeClientIds.delete(clientId)

      const remainingClients = allClients.filter((client) => {
        return client.id !== clientId
      })

      // Unregister itself when there are no more clients
      if (remainingClients.length === 0) {
        self.registration.unregister()
      }

      break
    }
  }
})

// Resolve the "master" client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMasterClient(event) {
  const client = await self.clients.get(event.clientId)

  if (client.frameType === 'top-level') {
    return client
  }

  const allClients = await self.clients.matchAll()

  return allClients
    .filter((client) => {
      // Get only those clients that are currently visible.
      return client.visibilityState === 'visible'
    })
    .find((client) => {
      // Find the client ID that's recorded in the
      // set of clients that have registered the worker.
      return activeClientIds.has(client.id)
    })
}

async function handleRequest(event, requestId) {
  const client = await resolveMasterClient(event)
  const response = await getResponse(event, client, requestId)

  // Send back the response clone for the "response:*" life-cycle events.
  // Ensure MSW is active and ready to handle the message, otherwise
  // this message will pend indefinitely.
  if (client && activeClientIds.has(client.id)) {
    ;(async function () {
      const clonedResponse = response.clone()
      sendToClient(client, {
        type: 'RESPONSE',
        payload: {
          requestId,
          type: clonedResponse.type,
          ok: clonedResponse.ok,
          status: clonedResponse.status,
          statusText: clonedResponse.statusText,
          body:
            clonedResponse.body === null ? null : await clonedResponse.text(),
          headers: serializeHeaders(clonedResponse.headers),
          redirected: clonedResponse.redirected,
        },
      })
    })()
  }

  return response
}

async function getResponse(event, client, requestId) {
  const { request } = event
  const requestClone = request.clone()
  const getOriginalResponse = () => fetch(requestClone)

  // Bypass mocking when the request client is not active.
  if (!client) {
    return getOriginalResponse()
  }

  // Bypass initial page load requests (i.e. static assets).
  // The absence of the immediate/parent client in the map of the active clients
  // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
  // and is not ready to handle requests.
  if (!activeClientIds.has(client.id)) {
    return await getOriginalResponse()
  }

  // Bypass requests with the explicit bypass header
  if (requestClone.headers.get(bypassHeaderName) === 'true') {
    const cleanRequestHeaders = serializeHeaders(requestClone.headers)

    // Remove the bypass header to comply with the CORS preflight check.
    delete cleanRequestHeaders[bypassHeaderName]

    const originalRequest = new Request(requestClone, {
      headers: new Headers(cleanRequestHeaders),
    })

    return fetch(originalRequest)
  }

  // Send the request to the client-side MSW.
  const reqHeaders = serializeHeaders(request.headers)
  const body = await request.text()

  const clientMessage = await sendToClient(client, {
    type: 'REQUEST',
    payload: {
      id: requestId,
      url: request.url,
      method: request.method,
      headers: reqHeaders,
      cache: request.cache,
      mode: request.mode,
      credentials: request.credentials,
      destination: request.destination,
      integrity: request.integrity,
      redirect: request.redirect,
      referrer: request.referrer,
      referrerPolicy: request.referrerPolicy,
      body,
      bodyUsed: request.bodyUsed,
      keepalive: request.keepalive,
    },
  })

  switch (clientMessage.type) {
    case 'MOCK_SUCCESS': {
      return delayPromise(
        () => respondWithMock(clientMessage),
        clientMessage.payload.delay,
      )
    }

    case 'MOCK_NOT_FOUND': {
      return getOriginalResponse()
    }

    case 'NETWORK_ERROR': {
      const { name, message } = clientMessage.payload
      const networkError = new Error(message)
      networkError.name = name

      // Rejecting a request Promise emulates a network error.
      throw networkError
    }

    case 'INTERNAL_ERROR': {
      const parsedBody = JSON.parse(clientMessage.payload.body)

      console.error(
        `\
[MSW] Request handler function for "%s %s" has thrown the following exception:

${parsedBody.errorType}: ${parsedBody.message}
(see more detailed error stack trace in the mocked response body)

This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error.
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
`,
        request.method,
        request.url,
      )

      return respondWithMock(clientMessage)
    }
  }

  return getOriginalResponse()
}

self.addEventListener('fetch', function (event) {
  const { request } = event
  const accept = request.headers.get('accept') || ''

  // Bypass server-sent events.
  if (accept.includes('text/event-stream')) {
    return
  }

  // Bypass navigation requests.
  if (request.mode === 'navigate') {
    return
  }

  // Opening the DevTools triggers the "only-if-cached" request
  // that cannot be handled by the worker. Bypass such requests.
  if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
    return
  }

  // Bypass all requests when there are no active clients.
  // Prevents the self-unregistered worked from handling requests
  // after it's been deleted (still remains active until the next reload).
  if (activeClientIds.size === 0) {
    return
  }

  const requestId = uuidv4()

  return event.respondWith(
    handleRequest(event, requestId).catch((error) => {
      console.error(
        '[MSW] Failed to mock a "%s" request to "%s": %s',
        request.method,
        request.url,
        error,
      )
    }),
  )
})

function serializeHeaders(headers) {
  const reqHeaders = {}
  headers.forEach((value, name) => {
    reqHeaders[name] = reqHeaders[name]
      ? [].concat(reqHeaders[name]).concat(value)
      : value
  })
  return reqHeaders
}

function sendToClient(client, message) {
  return new Promise((resolve, reject) => {
    const channel = new MessageChannel()

    channel.port1.onmessage = (event) => {
      if (event.data && event.data.error) {
        return reject(event.data.error)
      }

      resolve(event.data)
    }

    client.postMessage(JSON.stringify(message), [channel.port2])
  })
}

function delayPromise(cb, duration) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(cb()), duration)
  })
}

function respondWithMock(clientMessage) {
  return new Response(clientMessage.payload.body, {
    ...clientMessage.payload,
    headers: clientMessage.payload.headers,
  })
}

function uuidv4() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    const r = (Math.random() * 16) | 0
    const v = c == 'x' ? r : (r & 0x3) | 0x8
    return v.toString(16)
  })
}
<|endoftext|>


<commit_before>import React from 'react';

import { Zeboot } from 'CONTRIBUTORS';
import RegularArticle from 'interface/news/RegularArticle';

import FilterExample from './filter.png';
import ExampleResults from './exampleresults.png';

export default (
  <RegularArticle title="Time Filtering" publishedAt="2019-07-21" publishedBy={Zeboot}>
    If you haven't noticed already, WoWAnalyzer has a new feature : Time Filtering!

    A much requested feature for quite some time now, WoWAnalyzer now lets you filter the report to a range of time of your choosing, giving you the possibility to evaluate for example how well you performed during cooldowns, how much damage you did / took during a certain phase, and more!

    When you apply a time filter, we automatically attempt to try and preserve fight information from outside the time range where necessary - for example cooldown availability so we don't fault you for not using an ability that didn't come off CD during the time period - while preventing non-relevant events from contaminating the filtered data.

The new filter interface element, letting you select timestamps and phases to filter the fight by.

    In addition to the time filter itself, we've also provided a phase selection. If a fight has phase information defined in our source code, we'll automatically try to find the phase transitions and provide you with a dropdown menu letting you select any of these phases by their respective timestamps. In the case of a wipe, we'll of course only show you phases you actually reached.

    Please do keep in mind, however, that filtering events like this can obviously lead to unexpected behaviour as a lot of modules - especially older ones or ones accessing fight timestamps directly - will have to be manually adjusted to work properly with filtered results, so take these with a grain of salt (although the majority of them work as expected) and please report any unexpected behaviour to us via Discord or on GitHub!

An example of the statistics page adjusted by one specific phase.

What this means for developers

    This change also has an impact on contributors in that the fight object has changed. In order to ensure future modules work as expected when the results are filtered, please take the time to read these small list of internal API changes:

    The new fight object now looks like this:

{`
      fight: {
        start_time: 9198573, //required, timestamp of the start of the selected time period
        end_time: 9214374, //required, timestamp of the end of the selected time period
        offset_time: 161450, //required, amount of ms between start of fight and start of time period
        original_end_time: 9214374, //timestamp of the end of the fight
        boss: 2273, //required, boss ID
        filtered: true, //required, true if the fight is in any way filtered to a time period
        phase: "I1", //optional, phase key if a phase is selected
      };
      `}

    When displaying timestamps, for example, make sure to adjust them by the offset_time in order to maintain timestamp accuracy. Additionally you can use the filtered attribute to check whether the user is viewing the entire fight or not and adjust your analysis accordingly. For other, more precise questions feel free to reach out to us on Discord!

    We hope you enjoy this new feature!
  </RegularArticle>
);
<commit_msg>added instance tag<commit_after>import React from 'react';

import { Zeboot } from 'CONTRIBUTORS';
import RegularArticle from 'interface/news/RegularArticle';

import FilterExample from './filter.png';
import ExampleResults from './exampleresults.png';

export default (
  <RegularArticle title="Time Filtering" publishedAt="2019-07-21" publishedBy={Zeboot}>
    If you haven't noticed already, WoWAnalyzer has a new feature : Time Filtering!

    A much requested feature for quite some time now, WoWAnalyzer now lets you filter the report to a range of time of your choosing, giving you the possibility to evaluate for example how well you performed during cooldowns, how much damage you did / took during a certain phase, and more!

    When you apply a time filter, we automatically attempt to try and preserve fight information from outside the time range where necessary - for example cooldown availability so we don't fault you for not using an ability that didn't come off CD during the time period - while preventing non-relevant events from contaminating the filtered data.

The new filter interface element, letting you select timestamps and phases to filter the fight by.

    In addition to the time filter itself, we've also provided a phase selection. If a fight has phase information defined in our source code, we'll automatically try to find the phase transitions and provide you with a dropdown menu letting you select any of these phases by their respective timestamps. In the case of a wipe, we'll of course only show you phases you actually reached.

    Please do keep in mind, however, that filtering events like this can obviously lead to unexpected behaviour as a lot of modules - especially older ones or ones accessing fight timestamps directly - will have to be manually adjusted to work properly with filtered results, so take these with a grain of salt (although the majority of them work as expected) and please report any unexpected behaviour to us via Discord or on GitHub!

An example of the statistics page adjusted by one specific phase.

What this means for developers

    This change also has an impact on contributors in that the fight object has changed. In order to ensure future modules work as expected when the results are filtered, please take the time to read these small list of internal API changes:

    The new fight object now looks like this:

{`
      fight: {
        start_time: 9198573, //required, timestamp of the start of the selected time period
        end_time: 9214374, //required, timestamp of the end of the selected time period
        offset_time: 161450, //required, amount of ms between start of fight and start of time period
        original_end_time: 9214374, //timestamp of the end of the fight
        boss: 2273, //required, boss ID
        filtered: true, //required, true if the fight is in any way filtered to a time period
        phase: "I1", //optional, phase key if a phase is selected
        instance: 0, //optional, instance of a phase if phase is selected and can occur multiple times (aka Lady Ashvane)
      };
      `}

    When displaying timestamps, for example, make sure to adjust them by the offset_time in order to maintain timestamp accuracy. Additionally you can use the filtered attribute to check whether the user is viewing the entire fight or not and adjust your analysis accordingly. For other, more precise questions feel free to reach out to us on Discord!

    We hope you enjoy this new feature!
  </RegularArticle>
);
<|endoftext|>