<commit_before>
// ----------------------------------------------------------------
// Confirm Class

// ----------------------------------------------------------------
// Model

class ConfirmModel extends CommonModel {
  static get TYPE_0BUTTON() { return 0; }
  static get TYPE_1BUTTON() { return 1; }
  static get TYPE_2BUTTON() { return 2; }

  constructor(
    _initSetting = {
      NAME: 'Confirm Object',
      DESTROY: true,
      CONFIRM_ID: 'confirm-id',
      CONFIRM_TITLE: 'title',
      CONFIRM_MESSAGE: 'content',
      EVENT_TRIGGER: 'click',
      EVENT_SELECTOR: null,
      AUTO_OPEN: false,
      DESTROY_TIME_MS: 250,
      YES: 'Yes',
      NO: 'No',
      FUNCTION_YES: () => {},
      FUNCTION_NO: () => {},
      FUNCTION_CLOSE: () => {}
    }
  ) {
    super(_initSetting);

    this.GENERATE_SELECTOR = '#confirm-view';
    this.TEMPLATE_SELECTOR = '#confirm-view-template';
  }

  updateSelector() {
    this.CONFIRM_ID_SELECTOR = `#${this.CONFIRM_ID}`;
    this.CONFIRM_ID_SELECTOR_YES = `#${this.CONFIRM_ID}-yes`;
    this.CONFIRM_ID_SELECTOR_NO = `#${this.CONFIRM_ID}-no`;
    this.CONFIRM_ID_SELECTOR_CLOSE = `#${this.CONFIRM_ID}-close`;
  }
}

// ----------------------------------------------------------------
// View

class ConfirmView extends CommonView {
  constructor(
    _initSetting = {
      NAME: 'Confirm View'
    }
  ) {
    super(_initSetting);
  }

  generateModal() {
    $(this.MODEL.GENERATE_SELECTOR).html(super.getTemplate(
      $(this.MODEL.TEMPLATE_SELECTOR),
      {
        confirmId: this.MODEL.CONFIRM_ID,
        confirmTitle: this.MODEL.CONFIRM_TITLE,
        confirmMessage: this.MODEL.CONFIRM_MESSAGE,
        yes: this.MODEL.YES,
        no: this.MODEL.NO
      }
    ));
  }
}

// ----------------------------------------------------------------
// Event

class ConfirmEvent extends CommonEvent {
  constructor(
    _initSetting = {
      NAME: 'Confirm Event'
    }
  ) {
    super(_initSetting);
  }

  setEvent(_set = null) {
    if (_set != null) {
      if (_set) {
        this.setOnOpen();
        this.setOnYesClick();
        this.setOnNoClick();
        this.setOnCloseClick();
      } else {
        this.setOffOpen();
        this.setOffYesClick();
        this.setOffNoClick();
        this.setOffCloseClick();
      }
    }
  }

  setOnOpen() {
    if (this.MODEL.AUTO_OPEN) {
      this.CONTROLLER.openConfirm();
    } else {
      super.setOn(
        this.MODEL.EVENT_TRIGGER,
        this.MODEL.EVENT_SELECTOR,
        () => {
          this.CONTROLLER.openConfirm();
        }
      );
    }
  }

  setOnYesClick() {
    super.setOn(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_YES,
      () => {
        this.CONTROLLER.selectYes();
      }
    );
  }

  setOnNoClick() {
    super.setOn(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_NO,
      () => {
        this.CONTROLLER.selectNo();
      }
    );
  }

  setOnCloseClick() {
    super.setOn(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_CLOSE,
      () => {
        this.CONTROLLER.selectClose();
      }
    );
  }

  setOffOpen() {
    if (!this.MODEL.AUTO_OPEN) {
      super.setOff(
        this.MODEL.EVENT_TRIGGER,
        this.MODEL.EVENT_SELECTOR
      );
    }
  }

  setOffYesClick() {
    super.setOff(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_YES
    );
  }

  setOffNoClick() {
    super.setOff(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_NO
    );
  }

  setOffCloseClick() {
    super.setOff(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_CLOSE
    );
  }
}

// ----------------------------------------------------------------
// Controller

class ConfirmController extends CommonController {
  constructor(
    _model = {},
    _initSetting = {
      NAME: 'Confirm Controller',
      MODEL: new ConfirmModel(),
      VIEW: new ConfirmView(),
      EVENT: new ConfirmEvent()
    }
  ) {
    super(_model, _initSetting);

    this.initConfirm();
  }

  initConfirm() {
    this.MODEL.updateSelector();
    this.VIEW.generateModal();
    this.EVENT.setEvent(true);
  }

  openConfirm() {
    Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'Open', Log.ARROW_INPUT);
    Log.logClass(this.NAME, this.MODEL.CONFIRM_ID_SELECTOR);
    $(this.MODEL.CONFIRM_ID_SELECTOR).modal();
  }

  selectYes() {
    Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'Yes', Log.ARROW_INPUT);
    this.MODEL.FUNCTION_YES();
    this.destroy();
  }

  selectNo() {
    Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'No', Log.ARROW_INPUT);
    this.MODEL.FUNCTION_NO();
    this.destroy();
  }

  selectClose() {
    Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'Close', Log.ARROW_INPUT);
    this.MODEL.FUNCTION_CLOSE();
    this.destroy();
  }

  destroy() {
    if (this.MODEL.DESTROY) {
      Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'Destroy', Log.ARROW_INPUT);
      this.EVENT.setEvent(false);
      setTimeout(
        () => {
          this.remove();
        },
        this.MODEL.DESTROY_TIME_MS
      );
    }
  }

  remove() {
    Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'Remove', Log.ARROW_INPUT);
    $(this.MODEL.CONFIRM_ID_SELECTOR).remove();
  }
}
<commit_msg>Add TYPE to ConfirmModel constructor<commit_after>
// ----------------------------------------------------------------
// Confirm Class

// ----------------------------------------------------------------
// Model

class ConfirmModel extends CommonModel {
  static get TYPE_0BUTTON() { return 0; }
  static get TYPE_1BUTTON() { return 1; }
  static get TYPE_2BUTTON() { return 2; }

  constructor(
    _initSetting = {
      NAME: 'Confirm Object',
      DESTROY: true,
      CONFIRM_ID: 'confirm-id',
      CONFIRM_TITLE: 'title',
      CONFIRM_MESSAGE: 'content',
      EVENT_TRIGGER: 'click',
      EVENT_SELECTOR: null,
      AUTO_OPEN: false,
      DESTROY_TIME_MS: 250,
      TYPE: 2,
      YES: 'Yes',
      NO: 'No',
      FUNCTION_YES: () => {},
      FUNCTION_NO: () => {},
      FUNCTION_CLOSE: () => {}
    }
  ) {
    super(_initSetting);

    this.GENERATE_SELECTOR = '#confirm-view';
    this.TEMPLATE_SELECTOR = '#confirm-view-template';
  }

  updateSelector() {
    this.CONFIRM_ID_SELECTOR = `#${this.CONFIRM_ID}`;
    this.CONFIRM_ID_SELECTOR_YES = `#${this.CONFIRM_ID}-yes`;
    this.CONFIRM_ID_SELECTOR_NO = `#${this.CONFIRM_ID}-no`;
    this.CONFIRM_ID_SELECTOR_CLOSE = `#${this.CONFIRM_ID}-close`;
  }
}

// ----------------------------------------------------------------
// View

class ConfirmView extends CommonView {
  constructor(
    _initSetting = {
      NAME: 'Confirm View'
    }
  ) {
    super(_initSetting);
  }

  generateModal() {
    $(this.MODEL.GENERATE_SELECTOR).html(super.getTemplate(
      $(this.MODEL.TEMPLATE_SELECTOR),
      {
        confirmId: this.MODEL.CONFIRM_ID,
        confirmTitle: this.MODEL.CONFIRM_TITLE,
        confirmMessage: this.MODEL.CONFIRM_MESSAGE,
        yes: this.MODEL.YES,
        no: this.MODEL.NO
      }
    ));
  }
}

// ----------------------------------------------------------------
// Event

class ConfirmEvent extends CommonEvent {
  constructor(
    _initSetting = {
      NAME: 'Confirm Event'
    }
  ) {
    super(_initSetting);
  }

  setEvent(_set = null) {
    if (_set != null) {
      if (_set) {
        this.setOnOpen();
        this.setOnYesClick();
        this.setOnNoClick();
        this.setOnCloseClick();
      } else {
        this.setOffOpen();
        this.setOffYesClick();
        this.setOffNoClick();
        this.setOffCloseClick();
      }
    }
  }

  setOnOpen() {
    if (this.MODEL.AUTO_OPEN) {
      this.CONTROLLER.openConfirm();
    } else {
      super.setOn(
        this.MODEL.EVENT_TRIGGER,
        this.MODEL.EVENT_SELECTOR,
        () => {
          this.CONTROLLER.openConfirm();
        }
      );
    }
  }

  setOnYesClick() {
    super.setOn(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_YES,
      () => {
        this.CONTROLLER.selectYes();
      }
    );
  }

  setOnNoClick() {
    super.setOn(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_NO,
      () => {
        this.CONTROLLER.selectNo();
      }
    );
  }

  setOnCloseClick() {
    super.setOn(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_CLOSE,
      () => {
        this.CONTROLLER.selectClose();
      }
    );
  }

  setOffOpen() {
    if (!this.MODEL.AUTO_OPEN) {
      super.setOff(
        this.MODEL.EVENT_TRIGGER,
        this.MODEL.EVENT_SELECTOR
      );
    }
  }

  setOffYesClick() {
    super.setOff(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_YES
    );
  }

  setOffNoClick() {
    super.setOff(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_NO
    );
  }

  setOffCloseClick() {
    super.setOff(
      'click',
      this.MODEL.CONFIRM_ID_SELECTOR_CLOSE
    );
  }
}

// ----------------------------------------------------------------
// Controller

class ConfirmController extends CommonController {
  constructor(
    _model = {},
    _initSetting = {
      NAME: 'Confirm Controller',
      MODEL: new ConfirmModel(),
      VIEW: new ConfirmView(),
      EVENT: new ConfirmEvent()
    }
  ) {
    super(_model, _initSetting);

    this.initConfirm();
  }

  initConfirm() {
    this.MODEL.updateSelector();
    this.VIEW.generateModal();
    this.EVENT.setEvent(true);
  }

  openConfirm() {
    Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'Open', Log.ARROW_INPUT);
    Log.logClass(this.NAME, this.MODEL.CONFIRM_ID_SELECTOR);
    $(this.MODEL.CONFIRM_ID_SELECTOR).modal();
  }

  selectYes() {
    Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'Yes', Log.ARROW_INPUT);
    this.MODEL.FUNCTION_YES();
    this.destroy();
  }

  selectNo() {
    Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'No', Log.ARROW_INPUT);
    this.MODEL.FUNCTION_NO();
    this.destroy();
  }

  selectClose() {
    Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'Close', Log.ARROW_INPUT);
    this.MODEL.FUNCTION_CLOSE();
    this.destroy();
  }

  destroy() {
    if (this.MODEL.DESTROY) {
      Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'Destroy', Log.ARROW_INPUT);
      this.EVENT.setEvent(false);
      setTimeout(
        () => {
          this.remove();
        },
        this.MODEL.DESTROY_TIME_MS
      );
    }
  }

  remove() {
    Log.logClassKey(this.NAME, this.MODEL.CONFIRM_TITLE, 'Remove', Log.ARROW_INPUT);
    $(this.MODEL.CONFIRM_ID_SELECTOR).remove();
  }
}
<|endoftext|>


<commit_before>
// ----------------------------------------------------------------
// Popover Class

class PopoverModel extends CommonModel {
  constructor({
    name = 'Popover',
    selector = null,
    help = 'popover',
    trigger = 'hover'
  } = {})
  {
    super({
      name: name
    });

    this.NAME = name;
    this.SELECTOR = selector;
    this.HELP = help;
    this.TRIGGER = trigger;
  }
}

class PopoverView extends CommonView {
  constructor(_model = new PopoverModel()) {
    super(_model);

    this.setPopover();
  }

  setPopover() {
    if (this.model.SELECTOR != null) {
      $(this.model.SELECTOR).attr('data-toggle', 'popover');
      $(this.model.SELECTOR).attr('data-content', this.model.HELP);
      $(this.model.SELECTOR).attr('data-trigger', this.model.TRIGGER);
      $(this.model.SELECTOR).popover();
    }
  }
}

// ----------------------------------------------------------------
// Controllers

class PopoverController extends CommonController {
  constructor(_obj) {
    super({
      name: 'Popover Controller'
    });

    this.model = new PopoverModel(_obj);
    this.view = new PopoverView(this.model);
  }
}
<commit_msg>Add echoPermission option in PopoverModel<commit_after>
// ----------------------------------------------------------------
// Popover Class

class PopoverModel extends CommonModel {
  constructor({
    name = 'Popover',
    selector = null,
    help = 'popover',
    trigger = 'hover'
  } = {})
  {
    super({
      name: name,
      echoPermission: false
    });

    this.NAME = name;
    this.SELECTOR = selector;
    this.HELP = help;
    this.TRIGGER = trigger;
  }
}

class PopoverView extends CommonView {
  constructor(_model = new PopoverModel()) {
    super(_model);

    this.setPopover();
  }

  setPopover() {
    if (this.model.SELECTOR != null) {
      $(this.model.SELECTOR).attr('data-toggle', 'popover');
      $(this.model.SELECTOR).attr('data-content', this.model.HELP);
      $(this.model.SELECTOR).attr('data-trigger', this.model.TRIGGER);
      $(this.model.SELECTOR).popover();
    }
  }
}

// ----------------------------------------------------------------
// Controllers

class PopoverController extends CommonController {
  constructor(_obj) {
    super({
      name: 'Popover Controller'
    });

    this.model = new PopoverModel(_obj);
    this.view = new PopoverView(this.model);
  }
}
<|endoftext|>


<commit_before>enyo.kind({
	name: "ProjectViewTest",
	kind: "Ares.TestSuite",
	debug: true,
	registry: null,
	home: null,

	create: function () {
		this.inherited(arguments);
		this.services = ProjectViewTest.services;
		this.home = ProjectViewTest.home;
	},
	/**
	* onCreateProject event is fired onclick: "doCreateProject" in ProjectList.js
	*/
	testDoCreateProjectAction: function() {
		var pList = this.aresObj.$.projectView.$.projectList;
		if (pList) {
			pList.doCreateProject();
			this.finish();
		} else {
			this.finish("ProjectList Object: "+pList+ " is not found!");
		}
	},
	/**
	* handleSelectProvider
	*/
	testHandleSelectProvider: function(){
		var dirPopup =  this.aresObj.$.projectView.$.projectWizardCreate.$.selectDirectoryPopup;
		var pWizard = this.aresObj.$.projectView.$.projectWizardCreate;
		if (dirPopup) {
			// retrieve the service
			var pList = dirPopup.$.providerList;
			var that = this;
			var myService = pList.services[0];
			this.log(myService);

			var userPushOk = function() {
				// simulate on projectWizardProperties the Ok event which is handled by createProject
				pWizard.createProject(that,{service: myService });
				pWizard.$.propertiesWidget.confirmTap(that, {service: myService, callBack: that.finish });

			};

			var userSelectDir = function() {
				var hFileTree = dirPopup.$.hermesFileTree;
				var nodes = hFileTree.getNodeFiles();

				// simulate nodeTap & selectFolder
				hFileTree.doFolderClick(nodes[0]);
				nodes[0].doNodeTap();

				// simulate confirmTap
				dirPopup.doDirectorySelected({serviceId: dirPopup.selectedDir.id, directory: dirPopup.selectedDir, testCallBack: userPushOk});
			};
			// selectProvider
			dirPopup.handleSelectProvider(this, {service: myService, callBack: userSelectDir});
		} else {
			this.finish("SelectDirectoryPopup: "+this.aresObj.$.projectView.$.projectWizardCreate.$.SelectDirectoryPopup+ " is not available!");
		}
	},
	statics: {
		services: null,
		home: null
	}
});<commit_msg>ENYO-1693: added logs Enyo-DCO-1.1-Signed-off-by: Dominique Dumont <dominique.dumont@hp.com><commit_after>enyo.kind({
	name: "ProjectViewTest",
	kind: "Ares.TestSuite",
	debug: true,
	registry: null,
	home: null,

	create: function () {
		this.inherited(arguments);
		this.services = ProjectViewTest.services;
		this.home = ProjectViewTest.home;
	},
	/**
	* onCreateProject event is fired onclick: "doCreateProject" in ProjectList.js
	*/
	testDoCreateProjectAction: function() {
		var pList = this.aresObj.$.projectView.$.projectList;
		if (pList) {
			pList.doCreateProject();
			this.finish();
		} else {
			this.finish("ProjectList Object: "+pList+ " is not found!");
		}
	},
	/**
	* handleSelectProvider
	*/
	testHandleSelectProvider: function(){
		var dirPopup =  this.aresObj.$.projectView.$.projectWizardCreate.$.selectDirectoryPopup;
		var pWizard = this.aresObj.$.projectView.$.projectWizardCreate;
		this.log("testHandleSelectProvider: started") ;

		if (dirPopup) {
			// retrieve the service
			var pList = dirPopup.$.providerList;
			var that = this;
			var myService = pList.services[0];
			this.log(myService);

			var userPushOk = function() {
				enyo.log("testHandleSelectProvider: user push ok") ;
				// simulate on projectWizardProperties the Ok event which is handled by createProject
				pWizard.createProject(that,{service: myService });
				pWizard.$.propertiesWidget.confirmTap(that, {service: myService, callBack: that.finish });

			};

			var userSelectDir = function() {
				var hFileTree = dirPopup.$.hermesFileTree;
				var nodes = hFileTree.getNodeFiles();

				enyo.log("testHandleSelectProvider: user select dir") ;

				// simulate nodeTap & selectFolder
				hFileTree.doFolderClick(nodes[0]);
				nodes[0].doNodeTap();

				enyo.log("testHandleSelectProvider: user confirms dir") ;
				// simulate confirmTap
				dirPopup.doDirectorySelected({serviceId: dirPopup.selectedDir.id, directory: dirPopup.selectedDir, testCallBack: userPushOk});
			};
			// selectProvider
			this.log("testHandleSelectProvider: handleSelectProvider called") ;
			dirPopup.handleSelectProvider(this, {service: myService, callBack: userSelectDir});
		} else {
			this.finish("SelectDirectoryPopup: "+this.aresObj.$.projectView.$.projectWizardCreate.$.SelectDirectoryPopup+ " is not available!");
		}
	},
	statics: {
		services: null,
		home: null
	}
});<|endoftext|>


<commit_before>const expect = require('chai').expect;
const Polar = require('../../../src/coord/polar');

describe('Polar', function() {
  const coord = new Polar({
    start: {
      x: 0,
      y: 300
    },
    end: {
      x: 200,
      y: 0
    }
  });

  it('construction', function() {
    const center = coord.getCenter();
    expect(center.x).to.equal(100);
    expect(center.y).to.equal(150);
    expect(coord.radius).to.equal(100);
  });

  it('convert', function() {
    let point = {
      x: 0,
      y: 1
    };
    point = coord.convert(point);
    expect(point.x).to.equal(100);
    expect(point.y).to.equal(50);
    point = {
      x: 0.25,
      y: 1
    };
    point = coord.convert(point);
    expect(point.x).to.equal(200);
    expect(point.y).to.equal(150);

    point = {
      x: 0.5,
      y: 0.5
    };
    point = coord.convert(point);
    expect(point.x).to.equal(100);
    expect(point.y).to.equal(200);
  });

  it('invert', function() {
    let point = {
      x: 100,
      y: 200
    };
    point = coord.invert(point);
    expect(point.x).to.equal(0.5);
    expect(point.y).to.equal(0.5);
  });

  it('getWidth and getHeight', function() {
    expect(coord.getWidth()).to.equal(200);
    expect(coord.getHeight()).to.equal(300);
  });

  it('translate', function() {
    let point = {
      x: 0.25,
      y: 1
    };
    coord.translate(100, 20);
    point = coord.convert(point);
    expect(point.x).to.equal(300);
    expect(point.y).to.equal(170);
    coord.translate(-100, -20);
  });

  it('rotate', function() {
    let point = {
      x: 0.5,
      y: 0.5
    };
    coord.rotate(Math.PI / 2);
    point = coord.convert(point);
    expect(point.x).to.equal(50);
    expect(point.y).to.equal(150);
    coord.rotate(-Math.PI / 2);
  });

  it('scale', function() {
    let point = {
      x: 0.5,
      y: 0.5
    };
    coord.scale(2, 2);
    point = coord.convert(point);
    expect(point.x).to.equal(100);
    expect(point.y).to.equal(250);
    coord.scale(0.5, 0.5);
  });

  it('reflect x', function() {
    let point = {
      x: 0.25,
      y: 0.5
    };
    coord.reflect('x');
    point = coord.convert(point);
    expect(point.x).to.equal(50);
    expect(point.y).to.equal(150);
    coord.reflect('x');
  });

  it('reflect y', function() {
    let point = {
      x: 0.75,
      y: 0.5
    };
    coord.reflect('y');
    point = coord.convert(point);
    expect(point.x).to.equal(50);
    expect(point.y).to.equal(150);
    coord.reflect('y');
  });
});
<commit_msg>fix: add unit test of Polar coord when startAngle > endAngle<commit_after>const expect = require('chai').expect;
const Polar = require('../../../src/coord/polar');

describe('Polar', function() {
  const coord = new Polar({
    start: {
      x: 0,
      y: 300
    },
    end: {
      x: 200,
      y: 0
    }
  });

  it('construction', function() {
    const center = coord.getCenter();
    expect(center.x).to.equal(100);
    expect(center.y).to.equal(150);
    expect(coord.radius).to.equal(100);
  });

  it('convert', function() {
    let point = {
      x: 0,
      y: 1
    };
    point = coord.convert(point);
    expect(point.x).to.equal(100);
    expect(point.y).to.equal(50);
    point = {
      x: 0.25,
      y: 1
    };
    point = coord.convert(point);
    expect(point.x).to.equal(200);
    expect(point.y).to.equal(150);

    point = {
      x: 0.5,
      y: 0.5
    };
    point = coord.convert(point);
    expect(point.x).to.equal(100);
    expect(point.y).to.equal(200);
  });

  it('invert', function() {
    let point = {
      x: 100,
      y: 200
    };
    point = coord.invert(point);
    expect(point.x).to.equal(0.5);
    expect(point.y).to.equal(0.5);
  });

  it('getWidth and getHeight', function() {
    expect(coord.getWidth()).to.equal(200);
    expect(coord.getHeight()).to.equal(300);
  });

  it('translate', function() {
    let point = {
      x: 0.25,
      y: 1
    };
    coord.translate(100, 20);
    point = coord.convert(point);
    expect(point.x).to.equal(300);
    expect(point.y).to.equal(170);
    coord.translate(-100, -20);
  });

  it('rotate', function() {
    let point = {
      x: 0.5,
      y: 0.5
    };
    coord.rotate(Math.PI / 2);
    point = coord.convert(point);
    expect(point.x).to.equal(50);
    expect(point.y).to.equal(150);
    coord.rotate(-Math.PI / 2);
  });

  it('scale', function() {
    let point = {
      x: 0.5,
      y: 0.5
    };
    coord.scale(2, 2);
    point = coord.convert(point);
    expect(point.x).to.equal(100);
    expect(point.y).to.equal(250);
    coord.scale(0.5, 0.5);
  });

  it('reflect x', function() {
    let point = {
      x: 0.25,
      y: 0.5
    };
    coord.reflect('x');
    point = coord.convert(point);
    expect(point.x).to.equal(50);
    expect(point.y).to.equal(150);
    coord.reflect('x');
  });

  it('reflect y', function() {
    let point = {
      x: 0.75,
      y: 0.5
    };
    coord.reflect('y');
    point = coord.convert(point);
    expect(point.x).to.equal(50);
    expect(point.y).to.equal(150);
    coord.reflect('y');
  });

  it('endAngle < startAngle', () => {
    const coord = new Polar({
      start: { x: 80, y: 355 },
      end: { x: 480, y: 20 },
      startAngle: 1 / 2 * Math.PI,
      endAngle: -1 / 2 * Math.PI
    });
    expect(coord.startAngle).to.equal(1 / 2 * Math.PI);
    expect(coord.endAngle).to.equal(3 / 2 * Math.PI);
  });
});
<|endoftext|>


<commit_before>/**
 * Initializing of the main interface on Otworlds
 */

var $viewport;
var $canvas;
jQuery(document).ready(function(){
	$viewport = jQuery("#viewport");
	$canvas = jQuery("#canvas");

	//Calculate positions and dimensions on toolbars

	//Load the map and fire up the grid
	jQuery.ajax(Mapeditor.config.urls.backend, {
		dataType: "jsonp",
		data: {
			'action' : 'init'
		},
		success: function(data){

			Mapeditor.map.meta.name = data.name;
			Mapeditor.map.meta.description = data.description;
			Mapeditor.map.meta.width = data.width;
			Mapeditor.map.meta.height = data.height;

			//add tiles to cache
			jQuery.each(data.tiles, function(posz, zValue){
				jQuery.each(zValue, function(posx, xValue){
					jQuery.each(xValue, function(posy, tileValue){
						Mapeditor.map.cacheTile(posx, posy, posz, tileValue);
					});
				});
			});

			Mapeditor.materials.load('xml/materials.xml', function(){
				Mapeditor.internals.infinitedrag = jQuery.infinitedrag("#canvas", {},
					{
						width: 32,
						height: 32,
						range_col: [0, Mapeditor.map.meta.width],
						range_row: [0, Mapeditor.map.meta.height],
						start_col: (Mapeditor.map.meta.width/2)-7,
						start_row: (Mapeditor.map.meta.height/2)-5,
						class_name: 'tile',
						oncreate: function($element, col, row) {
							var tileObject = Object.create(Mapeditor.Tile);
							tileObject.load(col, row, Mapeditor.internals.currentFloor, $element);
						}
					}
				);
			});
		}
	});

	//Keep track of whether we need to lookup ".tile.hovered" all the time
	var hoveredElements = 0;
	$viewport.on({
		mousemove: function(e) {
			if(hoveredElements > 0) jQuery(".tile.hovered").removeClass('hovered');
			var $target = Mapeditor.internals.figureOutTile(e);
			$target.addClass('hovered');
			hoveredElements++;
		}
	}, '.tile');
});<commit_msg>update document title with map name<commit_after>/**
 * Initializing of the main interface on Otworlds
 */

var $viewport;
var $canvas;
jQuery(document).ready(function(){
	$viewport = jQuery("#viewport");
	$canvas = jQuery("#canvas");

	//Calculate positions and dimensions on toolbars

	//Load the map and fire up the grid
	jQuery.ajax(Mapeditor.config.urls.backend, {
		dataType: "jsonp",
		data: {
			'action' : 'init'
		},
		success: function(data){

			Mapeditor.map.meta.name = data.name;
			Mapeditor.map.meta.description = data.description;
			Mapeditor.map.meta.width = data.width;
			Mapeditor.map.meta.height = data.height;

			document.title = Mapeditor.map.meta.name + ' - ' + document.title;

			//add tiles to cache
			jQuery.each(data.tiles, function(posz, zValue){
				jQuery.each(zValue, function(posx, xValue){
					jQuery.each(xValue, function(posy, tileValue){
						Mapeditor.map.cacheTile(posx, posy, posz, tileValue);
					});
				});
			});

			Mapeditor.materials.load('xml/materials.xml', function(){
				Mapeditor.internals.infinitedrag = jQuery.infinitedrag("#canvas", {},
					{
						width: 32,
						height: 32,
						range_col: [0, Mapeditor.map.meta.width],
						range_row: [0, Mapeditor.map.meta.height],
						start_col: (Mapeditor.map.meta.width/2)-7,
						start_row: (Mapeditor.map.meta.height/2)-5,
						class_name: 'tile',
						oncreate: function($element, col, row) {
							var tileObject = Object.create(Mapeditor.Tile);
							tileObject.load(col, row, Mapeditor.internals.currentFloor, $element);
						}
					}
				);
			});
		}
	});

	//Keep track of whether we need to lookup ".tile.hovered" all the time
	var hoveredElements = 0;
	$viewport.on({
		mousemove: function(e) {
			if(hoveredElements > 0) jQuery(".tile.hovered").removeClass('hovered');
			var $target = Mapeditor.internals.figureOutTile(e);
			$target.addClass('hovered');
			hoveredElements++;
		}
	}, '.tile');
});<|endoftext|>


<commit_before>// modal pattern.
//
// Author: Rok Garbas
// Contact: rok@garbas.si
// Version: 1.0
// Depends: jquery.js patterns.js pickadate.js
//
// Description:
//
// License:
//
// Copyright (C) 2010 Plone Foundation
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

/*jshint bitwise:true, curly:true, eqeqeq:true, immed:true, latedef:true,
  newcap:true, noarg:true, noempty:true, nonew:true, plusplus:true,
  undef:true, strict:true, trailing:true, browser:true, evil:true */
/*global define:false */
define([
  'jquery',
  'js/pattern.base',
  'js/pattern.backdrop',
  'jam/Patterns/src/registry',
  'jam/Patterns/src/core/parser'
], function($, Base, Backdrop, Registry, Parser, undefined) {
  "use strict";

  var parser = new Parser("modal");

  parser.add_argument("triggers");
  // left|center|right top|middle|button
  parser.add_argument("position", "center middle");
  parser.add_argument("width", "auto");
  parser.add_argument("height", "auto");
  parser.add_argument("margin", "20px");
  parser.add_argument("klass", "modal");
  parser.add_argument("klassWrapper", "modal-wrapper");
  parser.add_argument("klassWrapperInner", "modal-wrapper-inner");
  parser.add_argument("klassActive", "active");

  // XXX: should support same options as $.ajax
  parser.add_argument("ajaxUrl");
  parser.add_argument("ajaxType", "GET");

  parser.add_argument("backdrop", "body");
  parser.add_argument("backdropZIndex", "1000");
  parser.add_argument("backdropOpacity", "0.8");
  parser.add_argument("backdropKlass", "backdrop");
  parser.add_argument("backdropKlassActive", 'backdrop-active');
  parser.add_argument("backdropCloseOnEsc", true);
  parser.add_argument("backdropCloseOnClick", true);

  var Modal = Base.extend({
    name: "modal",
    parser: parser,
    init: function() {
      var self = this;

      self.backdrop = new Backdrop($(self.options.backdrop), {
        zindex: self.options.backdropZIndex,
        klass: self.options.backdropKlass,
        klassActive: self.options.backdropKlassActive,
        styles: self.options.backdropStyles,
        opacity: self.options.backdropOpacity,
        closeOnEsc: self.options.backdropCloseOnEsc,
        closeOnClick: self.options.backdropCloseOnClick
      });

      self.$wrapper = $('> .' + self.options.klassWrapper, self.backdrop.$el);
      if (self.$wrapper.size() === 0) {
        self.$wrapperInner = $('
')
          .addClass(self.options.klassWrapperInner)
          .css({
            'margin': self.options.margin,
            'position': 'absolute',
            'bottom': '0',
            'left': '0',
            'right': '0',
            'top': '0'
          });
        self.$wrapper = $('
')
          .css({
            'z-index': self.options.backdropZIndex + 1,
            'overflow-y': 'scroll',
            'position': 'fixed',
            'bottom': '0',
            'left': '0',
            'right': '0',
            'top': '0'
          }).hide()
          .on('click', function(e) {
            e.stopPropagation();
            e.preventDefault();
            self.backdrop.hide();
          })
          .addClass(self.options.klassWrapper)
          .append(self.$wrapperInner)
          .insertBefore(self.backdrop.$backdrop);
      }

      if (self.options.triggers) {
        $.each(self.options.triggers, function(i, item) {
          item = item.split(' ');
          $(item[1] || self.$el).on(item[0], function() {
            self.show();
          });
        });
      }

      if (self.$el.is('a')) {
        if (self.$el.attr('href')) {
          if (!self.options.target && self.$el.attr('href').substr(0, 1) === '#') {
            self.options.target = self.$el.attr('href');
          }
          if (!self.options.ajaxUrl && self.$el.attr('href').substr(0, 1) !== '#') {
            self.options.ajaxUrl = self.$el.attr('href');
          }
        }
        self.$el.on('click', function(e) {
          e.stopPropagation();
          e.preventDefault();
          self.show();
        });
      }

      self.initModal();
      self.backdrop.on('hidden', function() { self.hide(); });
    },
    initModal: function() {
      var self = this,
          $modal = $('
')
            .addClass(self.options.klass)
            .on('click', function(e) { e.stopPropagation(); });

      if (self.options.ajaxUrl) {
        self.trigger('beforeajax');
        self.$modal = function() {
          $.ajax({
              url: self.options.ajaxUrl,
              type: self.options.ajaxType
          }).done(function(response, textStatus, xhr) {
            self.$modal = $((/]*>((.|[\n\r])*)<\/body>/im).exec(response)[0]
              .replace('<body', '<div').replace('', '
'))
                .addClass(self.options.klass)
                .appendTo(self.$wrapperInner);
            self.trigger('afterajax', self, textStatus, xhr);
            self.show();
          });
        };
      } else if (self.options.target) {
        self.$modal = function() {
          self.$modal = $modal
              .html($(self.options.target).clone())
              .appendTo(self.$wrapperInner);
          self.show();
        };
      } else {
        self.$modal = $modal
              .html(self.$el.clone())
              .appendTo(self.$wrapperInner);
      }

    },
    positionModal: function() {
      var self = this;
      if (self.$el.hasClass(self.options.klassActive) &&
          typeof self.$modal !== 'function') {
        var postionHorizontal = self.options.position.split(' ')[0],
            postionVertical = self.options.position.split(' ')[1],
            margin = parseInt(self.$modal.css('margin-left'), 10);

        self.$wrapper.parent().css('overflow', 'hidden');

        self.$modal.css({
          'width': self.options.width === 'auto' ? self.$modal.width() : self.options.width,
          'height': self.options.height === 'auto' ? self.$modal.height() : self.options.height,
          'position': 'absolute',
          'bottom': '0',
          'left': '0',
          'right': '0',
          'top': '0'
        });
        self.$wrapperInner.css({ 'margin': '0' });
        var wrapperOffsetBefore = self.$wrapperInner.offset();
        self.$wrapperInner.css({ 'margin': self.options.margin });
        var wrapperOffset = self.$wrapperInner.offset(),
            wrapperOuterWidth = self.$wrapperInner.outerWidth(true),
            wrapperInnerWidth = self.$wrapperInner.innerWidth(),
            wrapperOuterHeight = self.$wrapperInner.outerHeight(true),
            wrapperInnerHeight = self.$wrapperInner.innerHeight();

        var topMargin = wrapperOffset.top - wrapperOffsetBefore.top,
            bottomMargin = wrapperOuterHeight - wrapperInnerHeight - topMargin,
            leftMargin = wrapperOffset.left - wrapperOffsetBefore.left,
            rightMargin = wrapperOuterWidth - wrapperInnerWidth - leftMargin;

        if (postionHorizontal === 'left') {
          self.$modal.css('left', leftMargin);
        } else if (postionHorizontal === 'center') {
          self.$modal.css('margin-left',
              self.$wrapper.width()/2 - self.$modal.width()/2 - leftMargin);
        } else if (postionHorizontal === 'right') {
          self.$modal.css('right', rightMargin);
        }

        if (self.$modal.height() > self.$wrapper.height()) {
          self.$wrapperInner.height(self.$modal.height() + bottomMargin);
        } else {
          if (postionVertical === 'top') {
            self.$modal.css('margin-top', topMargin);
          } else if (postionVertical === 'middle') {
            self.$modal.css('margin-top', self.$wrapper.height()/2 -
                self.$modal.height()/2 - topMargin);
          } else if (postionVertical === 'bottom') {
            self.$modal.css('margin-top', self.$wrapper.height() -
                self.$modal.height() - topMargin);
          }
        }

        $(window).resize(function() {
          self.positionModal();
        });
      }
    },
    show: function() {
      var self = this;
      if (!self.$el.hasClass(self.options.klassActive)) {
        if (typeof self.$modal === 'function') {
          self._$modal = self.$modal;
          self.$modal();
        } else {
          self.trigger('show');
          self.$modal
            .addClass(self.options.klassActive)
            .on('hide.modal.patterns', function(e) {
              e.stopPropagation();
              self.hide();
            });
          self.$el.addClass(self.options.klassActive);
          self.backdrop.show();
          self.$wrapper.show();
          Registry.scan(self.$modal);
          self.positionModal();
          self.trigger('shown');
        }
      }
    },
    hide: function() {
      var self = this;
      if (self.$el.hasClass(self.options.klassActive)) {
        self.trigger('hide');
        self.backdrop.hide();
        if (self.$modal.remove) {
          self.$modal.remove();
          self.$wrapper.hide();
          self.$wrapper.parent().css('overflow', 'visible');
          self.initModal();
        }
        self.$el.removeClass(self.options.klassActive);
        self.trigger('hidden');
      }
    }
  });

  return Modal;

});
<commit_msg>fix for modal: breaking for all except on (missing $wrapperInner)<commit_after>// modal pattern.
//
// Author: Rok Garbas
// Contact: rok@garbas.si
// Version: 1.0
// Depends: jquery.js patterns.js pickadate.js
//
// Description:
//
// License:
//
// Copyright (C) 2010 Plone Foundation
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 of the License.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

/*jshint bitwise:true, curly:true, eqeqeq:true, immed:true, latedef:true,
  newcap:true, noarg:true, noempty:true, nonew:true, plusplus:true,
  undef:true, strict:true, trailing:true, browser:true, evil:true */
/*global define:false */
define([
  'jquery',
  'js/pattern.base',
  'js/pattern.backdrop',
  'jam/Patterns/src/registry',
  'jam/Patterns/src/core/parser'
], function($, Base, Backdrop, Registry, Parser, undefined) {
  "use strict";

  var parser = new Parser("modal");

  parser.add_argument("triggers");
  // left|center|right top|middle|button
  parser.add_argument("position", "center middle");
  parser.add_argument("width", "auto");
  parser.add_argument("height", "auto");
  parser.add_argument("margin", "20px");
  parser.add_argument("klass", "modal");
  parser.add_argument("klassWrapper", "modal-wrapper");
  parser.add_argument("klassWrapperInner", "modal-wrapper-inner");
  parser.add_argument("klassActive", "active");

  // XXX: should support same options as $.ajax
  parser.add_argument("ajaxUrl");
  parser.add_argument("ajaxType", "GET");

  parser.add_argument("backdrop", "body");
  parser.add_argument("backdropZIndex", "1000");
  parser.add_argument("backdropOpacity", "0.8");
  parser.add_argument("backdropKlass", "backdrop");
  parser.add_argument("backdropKlassActive", 'backdrop-active');
  parser.add_argument("backdropCloseOnEsc", true);
  parser.add_argument("backdropCloseOnClick", true);

  var Modal = Base.extend({
    name: "modal",
    parser: parser,
    init: function() {
      var self = this;

      self.backdrop = new Backdrop($(self.options.backdrop), {
        zindex: self.options.backdropZIndex,
        klass: self.options.backdropKlass,
        klassActive: self.options.backdropKlassActive,
        styles: self.options.backdropStyles,
        opacity: self.options.backdropOpacity,
        closeOnEsc: self.options.backdropCloseOnEsc,
        closeOnClick: self.options.backdropCloseOnClick
      });

      self.$wrapper = $('> .' + self.options.klassWrapper, self.backdrop.$el);
      if (self.$wrapper.size() === 0) {
        self.$wrapper = $('
')
          .css({
            'z-index': self.options.backdropZIndex + 1,
            'overflow-y': 'scroll',
            'position': 'fixed',
            'bottom': '0',
            'left': '0',
            'right': '0',
            'top': '0'
          }).hide()
          .on('click', function(e) {
            e.stopPropagation();
            e.preventDefault();
            self.backdrop.hide();
          })
          .addClass(self.options.klassWrapper)
          .insertBefore(self.backdrop.$backdrop);
      }

      self.$wrapperInner = $('> .' + self.options.klassWrapperInner, self.$wrapper);
      if (self.$wrapperInner.size() === 0) {
        self.$wrapperInner = $('
')
          .addClass(self.options.klassWrapperInner)
          .css({
            'margin': self.options.margin,
            'position': 'absolute',
            'bottom': '0',
            'left': '0',
            'right': '0',
            'top': '0'
          })
          .appendTo(self.$wrapper);
      }

      if (self.options.triggers) {
        $.each(self.options.triggers, function(i, item) {
          item = item.split(' ');
          $(item[1] || self.$el).on(item[0], function() {
            self.show();
          });
        });
      }

      if (self.$el.is('a')) {
        if (self.$el.attr('href')) {
          if (!self.options.target && self.$el.attr('href').substr(0, 1) === '#') {
            self.options.target = self.$el.attr('href');
          }
          if (!self.options.ajaxUrl && self.$el.attr('href').substr(0, 1) !== '#') {
            self.options.ajaxUrl = self.$el.attr('href');
          }
        }
        self.$el.on('click', function(e) {
          e.stopPropagation();
          e.preventDefault();
          self.show();
        });
      }

      self.initModal();
      self.backdrop.on('hidden', function() { self.hide(); });
    },
    initModal: function() {
      var self = this,
          $modal = $('
')
            .addClass(self.options.klass)
            .on('click', function(e) { e.stopPropagation(); });

      if (self.options.ajaxUrl) {
        self.trigger('beforeajax');
        self.$modal = function() {
          $.ajax({
              url: self.options.ajaxUrl,
              type: self.options.ajaxType
          }).done(function(response, textStatus, xhr) {
            self.$modal = $((/]*>((.|[\n\r])*)<\/body>/im).exec(response)[0]
              .replace('<body', '<div').replace('', '
'))
                .addClass(self.options.klass)
                .appendTo(self.$wrapperInner);
            self.trigger('afterajax', self, textStatus, xhr);
            self.show();
          });
        };
      } else if (self.options.target) {
        self.$modal = function() {
          self.$modal = $modal
              .html($(self.options.target).clone())
              .appendTo(self.$wrapperInner);
          self.show();
        };
      } else {
        self.$modal = $modal
              .html(self.$el.clone())
              .appendTo(self.$wrapperInner);
      }

    },
    positionModal: function() {
      var self = this;
      if (self.$el.hasClass(self.options.klassActive) &&
          typeof self.$modal !== 'function') {
        var postionHorizontal = self.options.position.split(' ')[0],
            postionVertical = self.options.position.split(' ')[1];

        self.$wrapper.parent().css('overflow', 'hidden');

        self.$modal.css({
          'width': self.options.width === 'auto' ? self.$modal.width() : self.options.width,
          'height': self.options.height === 'auto' ? self.$modal.height() : self.options.height,
          'position': 'absolute',
          'bottom': '0',
          'left': '0',
          'right': '0',
          'top': '0'
        });
        self.$wrapperInner.css({ 'margin': '0' });
        var wrapperOffsetBefore = self.$wrapperInner.offset();
        self.$wrapperInner.css({ 'margin': self.options.margin });
        var wrapperOffset = self.$wrapperInner.offset(),
            wrapperOuterWidth = self.$wrapperInner.outerWidth(true),
            wrapperInnerWidth = self.$wrapperInner.innerWidth(),
            wrapperOuterHeight = self.$wrapperInner.outerHeight(true),
            wrapperInnerHeight = self.$wrapperInner.innerHeight();

        var topMargin = wrapperOffset.top - wrapperOffsetBefore.top,
            bottomMargin = wrapperOuterHeight - wrapperInnerHeight - topMargin,
            leftMargin = wrapperOffset.left - wrapperOffsetBefore.left,
            rightMargin = wrapperOuterWidth - wrapperInnerWidth - leftMargin;

        if (postionHorizontal === 'left') {
          self.$modal.css('left', leftMargin);
        } else if (postionHorizontal === 'center') {
          self.$modal.css('margin-left',
              self.$wrapper.width()/2 - self.$modal.width()/2 - leftMargin);
        } else if (postionHorizontal === 'right') {
          self.$modal.css('right', rightMargin);
        }

        if (self.$modal.height() > self.$wrapper.height()) {
          self.$wrapperInner.height(self.$modal.height() + bottomMargin);
        } else {
          if (postionVertical === 'top') {
            self.$modal.css('margin-top', topMargin);
          } else if (postionVertical === 'middle') {
            self.$modal.css('margin-top', self.$wrapper.height()/2 -
                self.$modal.height()/2 - topMargin);
          } else if (postionVertical === 'bottom') {
            self.$modal.css('margin-top', self.$wrapper.height() -
                self.$modal.height() - topMargin);
          }
        }

        $(window).resize(function() {
          self.positionModal();
        });
      }
    },
    show: function() {
      var self = this;
      if (!self.$el.hasClass(self.options.klassActive)) {
        if (typeof self.$modal === 'function') {
          self._$modal = self.$modal;
          self.$modal();
        } else {
          self.trigger('show');
          self.$modal
            .addClass(self.options.klassActive)
            .on('hide.modal.patterns', function(e) {
              e.stopPropagation();
              self.hide();
            });
          self.$el.addClass(self.options.klassActive);
          self.backdrop.show();
          self.$wrapper.show();
          Registry.scan(self.$modal);
          self.positionModal();
          self.trigger('shown');
        }
      }
    },
    hide: function() {
      var self = this;
      if (self.$el.hasClass(self.options.klassActive)) {
        self.trigger('hide');
        self.backdrop.hide();
        if (self.$modal.remove) {
          self.$modal.remove();
          self.$wrapper.hide();
          self.$wrapper.parent().css('overflow', 'visible');
          self.initModal();
        }
        self.$el.removeClass(self.options.klassActive);
        self.trigger('hidden');
      }
    }
  });

  return Modal;

});
<|endoftext|>


<commit_before>/* eslint-disable no-underscore-dangle */
/* global MediaMetadata playerSendMessage MediaService */
/* global Howl Howler */

{
  /**
   * Player class containing the state of our playlist and where we are in it.
   * Includes all methods for playing, skipping, updating the display, etc.
   * @param {Array} playlist Array of objects with playlist song details ({title, file, howl}).
   */
  class Player {
    constructor() {
      this.playlist = [];
      this.index = -1;
      this._loop_mode = 0;
      this._media_uri_list = {};
      this.playedFrom = 0;
      this.mode = 'background';
      this.skipTime = 15;
    }

    setMode(newMode) {
      this.mode = newMode;
    }

    setRefreshRate(rate = 10) {
      clearInterval(this.refreshTimer);
      this.refreshTimer = setInterval(() => {
        if (this.playing) {
          this.sendFrameUpdate();
        }
      }, 1000 / rate);
    }

    get currentAudio() {
      return this.playlist[this.index];
    }

    get currentHowl() {
      return this.currentAudio && this.currentAudio.howl;
    }

    get playing() {
      return this.currentHowl ? this.currentHowl.playing() : false;
    }

    static get muted() {
      return !!Howler._muted;
    }

    insertAudio(audio, idx) {
      if (this.playlist.find((i) => audio.id === i.id)) return;

      const audioData = {
        ...audio,
        disabled: false, // avoid first time load block
        howl: null,
      };
      if (idx) {
        this.playlist.splice(idx, 0, [audio]);
      } else {
        this.playlist.push(audioData);
      }
      this.sendPlaylistEvent();
      this.sendLoadEvent();
    }

    removeAudio(idx) {
      if (!this.playlist[idx]) {
        return;
      }
      // restore playing status before change
      const isPlaying = this.playing;

      const { id: trackId } = this.currentAudio;

      this.playlist.splice(idx, 1);
      const newIndex = this.playlist.findIndex((i) => i.id === trackId);
      if (newIndex >= 0) {
        this.index = newIndex;
      } else {
        // current playing is deleted
        if (idx >= this.playlist.length) {
          this.index = this.playlist.length - 1;
        } else {
          this.index = idx;
        }
        if (isPlaying) {
          this.play();
        }
      }

      this.sendPlaylistEvent();
      this.sendLoadEvent();
    }

    appendAudioList(list) {
      if (!Array.isArray(list)) {
        return;
      }
      list.forEach((audio) => {
        this.insertAudio(audio);
      });
    }

    clearPlaylist() {
      this.playlist = [];
      Howler.stop();
      this.sendPlaylistEvent();
      this.sendLoadEvent();
    }

    setNewPlaylist(list) {
      if (list.length) {
        Howler.stop();

        this.playlist = list.map((audio) => ({
          ...audio,
          howl: null,
        }));
        // TODO: random mode need random choose first song to load
        this.index = 0;
        this.load(0);
      }
      this.sendPlaylistEvent();
    }

    playById(id) {
      const idx = this.playlist.findIndex((audio) => audio.id === id);
      this.play(idx);
    }

    loadById(id) {
      const idx = this.playlist.findIndex((audio) => audio.id === id);
      this.load(idx);
    }

    /**
     * Play a song in the playlist.
     * @param  {Number} index Index of the song in the playlist
     * (leave empty to play the first or current).
     */
    play(idx) {
      this.load(idx);

      const data = this.playlist[this.index];
      if (!data.howl || !this._media_uri_list[data.id]) {
        this.retrieveMediaUrl(this.index, true);
      } else {
        this.finishLoad(this.index, true);
      }
    }

    retrieveMediaUrl(index, playNow) {
      const msg = {
        type: 'BG_PLAYER:RETRIEVE_URL',
        data: {
          ...this.playlist[index],
          howl: undefined,
          index,
          playNow,
        },
      };
      MediaService.bootstrapTrack(
        msg.data,
        (bootinfo) => {
          msg.type = 'BG_PLAYER:RETRIEVE_URL_SUCCESS';

          msg.data = { ...msg.data, ...bootinfo };

          this.playlist[index].bitrate = bootinfo.bitrate;
          this.playlist[index].platform = bootinfo.platform;

          this.setMediaURI(msg.data.url, msg.data.id);
          this.setAudioDisabled(false, msg.data.index);
          this.finishLoad(msg.data.index, playNow);
          playerSendMessage(this.mode, msg);
        },
        () => {
          msg.type = 'BG_PLAYER:RETRIEVE_URL_FAIL';

          this.setAudioDisabled(true, msg.data.index);
          playerSendMessage(this.mode, msg);

          this.skip('next');
        }
      );
    }

    /**
     * Load a song from the playlist.
     * @param  {Number} index Index of the song in the playlist
     * (leave empty to load the first or current).
     */
    load(idx) {
      let index = typeof idx === 'number' ? idx : this.index;
      if (index < 0) return;
      if (!this.playlist[index]) {
        index = 0;
      }
      // stop when load new track to avoid multiple songs play in same time
      Howler.stop();
      this.index = index;

      this.sendLoadEvent();
    }

    finishLoad(index, playNow) {
      const data = this.playlist[index];

      // If we already loaded this track, use the current one.
      // Otherwise, setup and load a new Howl.
      const self = this;
      if (!data.howl) {
        data.howl = new Howl({
          src: [self._media_uri_list[data.url || data.id]],
          volume: 1,
          mute: self.muted,
          html5: true, // Force to HTML5 so that the audio can stream in (best for large files).
          onplay() {
            if ('mediaSession' in navigator) {
              const { mediaSession } = navigator;
              mediaSession.playbackState = 'playing';
              mediaSession.metadata = new MediaMetadata({
                title: self.currentAudio.title,
                artist: self.currentAudio.artist,
                album: `Listen1  •  ${(
                  self.currentAudio.album || '<???>'
                ).padEnd(100)}`,
                artwork: [
                  {
                    src: self.currentAudio.img_url,
                    sizes: '300x300',
                  },
                ],
              });
            }
            self.currentAudio.disabled = false;
            self.playedFrom = Date.now();
            self.sendPlayingEvent('Playing');
          },
          onload() {
            self.currentAudio.disabled = false;
            self.sendPlayingEvent('Loaded');
          },
          onend() {
            switch (self.loop_mode) {
              case 2:
                self.skip('random');
                break;

              case 1:
                self.play();
                break;

              case 0:
              default:
                self.skip('next');
                break;
            }
            self.sendPlayingEvent('Ended');
          },
          onpause() {
            navigator.mediaSession.playbackState = 'paused';
            self.sendPlayingEvent('Paused');
          },
          onstop() {
            self.sendPlayingEvent('Stopped');
          },
          onseek() {},
          onvolume() {},
          onloaderror(id, err) {
            playerSendMessage(this.mode, {
              type: 'BG_PLAYER:PLAY_FAILED',
              data: err,
            });
            self.currentAudio.disabled = true;
            self.sendPlayingEvent('err');
            self.currentHowl.unload();
            delete self._media_uri_list[data.id];
          },
          onplayerror(id, err) {
            playerSendMessage(this.mode, {
              type: 'BG_PLAYER:PLAY_FAILED',
              data: err,
            });
            self.currentAudio.disabled = true;
            self.sendPlayingEvent('err');
          },
        });
      }

      if (playNow && !this.currentHowl.playing()) {
        this.currentHowl.play();
      }
    }

    /**
     * Pause the currently playing track.
     */
    pause() {
      if (!this.currentHowl) return;

      // Puase the sound.
      this.currentHowl.pause();
    }

    /**
     * Skip to the next or previous track.
     * @param  {String} direction 'next' or 'prev'.
     */
    skip(direction) {
      Howler.stop();
      // Get the next track based on the direction of the track.
      let nextIndexFn = null;
      if (this._loop_mode === 2 || direction === 'random') {
        // TODO: shuffle algorithm instead of random
        nextIndexFn = () => Math.floor(Math.random() * this.playlist.length);
      } else if (direction === 'prev') {
        nextIndexFn = (idx) => (idx - 1) % this.playlist.length;
      } else if (direction === 'next') {
        nextIndexFn = (idx) => (idx + 1) % this.playlist.length;
      }
      this.index = nextIndexFn(this.index);

      let tryCount = 0;
      while (tryCount < this.playlist.length) {
        if (!this.playlist[this.index].disabled) {
          this.play(this.index);
          return;
        }
        this.index = nextIndexFn(this.index);
        tryCount += 1;
      }
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:RETRIEVE_URL_FAIL_ALL',
      });
      this.sendLoadEvent();
    }

    set loop_mode(input) {
      const LOOP_MODE = {
        all: 0,
        one: 1,
        shuffle: 2,
      };
      let myMode = 0;
      if (typeof input === 'string') {
        myMode = LOOP_MODE[input];
      } else {
        myMode = input;
      }
      if (!Object.values(LOOP_MODE).includes(myMode)) {
        return;
      }
      this._loop_mode = myMode;
    }

    get loop_mode() {
      return this._loop_mode;
    }

    /**
     * Set the volume and update the volume slider display.
     * @param  {Number} val Volume between 0 and 1.
     */
    set volume(val) {
      // Update the global volume (affecting all Howls).
      if (typeof val === 'number') {
        Howler.volume(val);
        this.sendVolumeEvent();
        this.sendFrameUpdate();
      }
    }

    // eslint-disable-next-line class-methods-use-this
    get volume() {
      return Howler.volume();
    }

    adjustVolume(inc) {
      this.volume = inc
        ? Math.min(this.volume + 0.1, 1)
        : Math.max(this.volume - 0.1, 0);
      this.sendVolumeEvent();
      this.sendFrameUpdate();
    }

    mute() {
      Howler.mute(true);
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:MUTE',
        data: true,
      });
    }

    unmute() {
      Howler.mute(false);
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:MUTE',
        data: false,
      });
    }

    /**
     * Seek to a new position in the currently playing track.
     * @param  {Number} per Percentage through the song to skip.
     */
    seek(per) {
      if (!this.currentHowl) return;

      // Get the Howl we want to manipulate.
      const audio = this.currentHowl;

      // Convert the percent into a seek position.
      // if (audio.playing()) {
      // }
      audio.seek(audio.duration() * per);
    }

    /**
     * Format the time from seconds to M:SS.
     * @param  {Number} secs Seconds to format.
     * @return {String}      Formatted time.
     */
    static formatTime(secs) {
      const minutes = Math.floor(secs / 60) || 0;
      const seconds = secs - minutes * 60 || 0;

      return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
    }

    setMediaURI(uri, url) {
      if (url) {
        this._media_uri_list[url] = uri;
      }
    }

    setAudioDisabled(disabled, idx) {
      if (this.playlist[idx]) {
        this.playlist[idx].disabled = disabled;
      }
    }

    async sendFrameUpdate() {
      const data = {
        id: this.currentAudio ? this.currentAudio.id : 0,
        duration: this.currentHowl ? this.currentHowl.duration() : 0,
        pos: this.currentHowl ? this.currentHowl.seek() : 0,
        playedFrom: this.playedFrom,
        playing: this.playing,
      };
      if ('setPositionState' in navigator.mediaSession) {
        navigator.mediaSession.setPositionState({
          duration: this.currentHowl ? this.currentHowl.duration() : 0,
          playbackRate: this.currentHowl ? this.currentHowl.rate() : 1,
          position: this.currentHowl ? this.currentHowl.seek() : 0,
        });
      }

      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:FRAME_UPDATE',
        data,
      });
    }

    async sendPlayingEvent(reason = 'UNKNOWN') {
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:PLAY_STATE',
        data: {
          isPlaying: this.playing,
          reason,
        },
      });
    }

    async sendLoadEvent() {
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:LOAD',
        data: {
          ...this.currentAudio,
          howl: undefined,
        },
      });
    }

    async sendVolumeEvent() {
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:VOLUME',
        data: this.volume * 100,
      });
    }

    async sendPlaylistEvent() {
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:PLAYLIST',
        data: this.playlist.map((audio) => ({ ...audio, howl: undefined })),
      });
    }
  }

  // Setup our new audio player class and pass it the playlist.

  window.threadPlayer = new Player();
  window.threadPlayer.setRefreshRate();

  const { threadPlayer } = window;
  if ('mediaSession' in navigator) {
    const { mediaSession } = navigator;
    mediaSession.setActionHandler('play', () => {
      threadPlayer.play();
    });
    mediaSession.setActionHandler('pause', () => {
      threadPlayer.pause();
    });
    mediaSession.setActionHandler('seekforward', () => {
      // User clicked "Seek Forward" media notification icon.
      const { currentHowl } = threadPlayer;
      const newTime = Math.min(
        currentHowl.seek() + threadPlayer.skipTime,
        currentHowl.duration()
      );
      currentHowl.seek(newTime);
    });
    mediaSession.setActionHandler('seekbackward', () => {
      // User clicked "Seek Backward" media notification icon.
      const { currentHowl } = threadPlayer;
      const newTime = Math.max(currentHowl.seek() - threadPlayer.skipTime, 0);
      currentHowl.seek(newTime);
    });
    mediaSession.setActionHandler('nexttrack', () => {
      threadPlayer.skip('next');
    });
    mediaSession.setActionHandler('previoustrack', () => {
      threadPlayer.skip('prev');
    });
  }
  playerSendMessage(this.mode, {
    type: 'BG_PLAYER:READY',
  });
}
<commit_msg>fix: resume play will restart bug<commit_after>/* eslint-disable no-underscore-dangle */
/* global MediaMetadata playerSendMessage MediaService */
/* global Howl Howler */

{
  /**
   * Player class containing the state of our playlist and where we are in it.
   * Includes all methods for playing, skipping, updating the display, etc.
   * @param {Array} playlist Array of objects with playlist song details ({title, file, howl}).
   */
  class Player {
    constructor() {
      this.playlist = [];
      this.index = -1;
      this._loop_mode = 0;
      this._media_uri_list = {};
      this.playedFrom = 0;
      this.mode = 'background';
      this.skipTime = 15;
    }

    setMode(newMode) {
      this.mode = newMode;
    }

    setRefreshRate(rate = 10) {
      clearInterval(this.refreshTimer);
      this.refreshTimer = setInterval(() => {
        if (this.playing) {
          this.sendFrameUpdate();
        }
      }, 1000 / rate);
    }

    get currentAudio() {
      return this.playlist[this.index];
    }

    get currentHowl() {
      return this.currentAudio && this.currentAudio.howl;
    }

    get playing() {
      return this.currentHowl ? this.currentHowl.playing() : false;
    }

    static get muted() {
      return !!Howler._muted;
    }

    insertAudio(audio, idx) {
      if (this.playlist.find((i) => audio.id === i.id)) return;

      const audioData = {
        ...audio,
        disabled: false, // avoid first time load block
        howl: null,
      };
      if (idx) {
        this.playlist.splice(idx, 0, [audio]);
      } else {
        this.playlist.push(audioData);
      }
      this.sendPlaylistEvent();
      this.sendLoadEvent();
    }

    removeAudio(idx) {
      if (!this.playlist[idx]) {
        return;
      }
      // restore playing status before change
      const isPlaying = this.playing;
      const { id: trackId } = this.currentAudio;

      if (isPlaying && this.playlist[idx].id === trackId) {
        this.pause();
      }

      this.playlist.splice(idx, 1);
      const newIndex = this.playlist.findIndex((i) => i.id === trackId);
      if (newIndex >= 0) {
        this.index = newIndex;
      } else {
        // current playing is deleted
        if (idx >= this.playlist.length) {
          this.index = this.playlist.length - 1;
        } else {
          this.index = idx;
        }
        if (isPlaying) {
          this.play();
        }
      }

      this.sendPlaylistEvent();
      this.sendLoadEvent();
    }

    appendAudioList(list) {
      if (!Array.isArray(list)) {
        return;
      }
      list.forEach((audio) => {
        this.insertAudio(audio);
      });
    }

    clearPlaylist() {
      this.playlist = [];
      Howler.stop();
      this.sendPlaylistEvent();
      this.sendLoadEvent();
    }

    setNewPlaylist(list) {
      if (list.length) {
        Howler.stop();

        this.playlist = list.map((audio) => ({
          ...audio,
          howl: null,
        }));
        // TODO: random mode need random choose first song to load
        this.index = 0;
        this.load(0);
      }
      this.sendPlaylistEvent();
    }

    playById(id) {
      const idx = this.playlist.findIndex((audio) => audio.id === id);
      this.play(idx);
    }

    loadById(id) {
      const idx = this.playlist.findIndex((audio) => audio.id === id);
      this.load(idx);
    }

    /**
     * Play a song in the playlist.
     * @param  {Number} index Index of the song in the playlist
     * (leave empty to play the first or current).
     */
    play(idx) {
      this.load(idx);

      const data = this.playlist[this.index];
      if (!data.howl || !this._media_uri_list[data.id]) {
        this.retrieveMediaUrl(this.index, true);
      } else {
        this.finishLoad(this.index, true);
      }
    }

    retrieveMediaUrl(index, playNow) {
      const msg = {
        type: 'BG_PLAYER:RETRIEVE_URL',
        data: {
          ...this.playlist[index],
          howl: undefined,
          index,
          playNow,
        },
      };
      MediaService.bootstrapTrack(
        msg.data,
        (bootinfo) => {
          msg.type = 'BG_PLAYER:RETRIEVE_URL_SUCCESS';

          msg.data = { ...msg.data, ...bootinfo };

          this.playlist[index].bitrate = bootinfo.bitrate;
          this.playlist[index].platform = bootinfo.platform;

          this.setMediaURI(msg.data.url, msg.data.id);
          this.setAudioDisabled(false, msg.data.index);
          this.finishLoad(msg.data.index, playNow);
          playerSendMessage(this.mode, msg);
        },
        () => {
          msg.type = 'BG_PLAYER:RETRIEVE_URL_FAIL';

          this.setAudioDisabled(true, msg.data.index);
          playerSendMessage(this.mode, msg);

          this.skip('next');
        }
      );
    }

    /**
     * Load a song from the playlist.
     * @param  {Number} index Index of the song in the playlist
     * (leave empty to load the first or current).
     */
    load(idx) {
      let index = typeof idx === 'number' ? idx : this.index;
      if (index < 0) return;
      if (!this.playlist[index]) {
        index = 0;
      }
      // stop when load new track to avoid multiple songs play in same time
      if (index !== this.index) {
        Howler.stop();
      }
      this.index = index;

      this.sendLoadEvent();
    }

    finishLoad(index, playNow) {
      const data = this.playlist[index];

      // If we already loaded this track, use the current one.
      // Otherwise, setup and load a new Howl.
      const self = this;
      if (!data.howl) {
        data.howl = new Howl({
          src: [self._media_uri_list[data.url || data.id]],
          volume: 1,
          mute: self.muted,
          html5: true, // Force to HTML5 so that the audio can stream in (best for large files).
          onplay() {
            if ('mediaSession' in navigator) {
              const { mediaSession } = navigator;
              mediaSession.playbackState = 'playing';
              mediaSession.metadata = new MediaMetadata({
                title: self.currentAudio.title,
                artist: self.currentAudio.artist,
                album: `Listen1  •  ${(
                  self.currentAudio.album || '<???>'
                ).padEnd(100)}`,
                artwork: [
                  {
                    src: self.currentAudio.img_url,
                    sizes: '300x300',
                  },
                ],
              });
            }
            self.currentAudio.disabled = false;
            self.playedFrom = Date.now();
            self.sendPlayingEvent('Playing');
          },
          onload() {
            self.currentAudio.disabled = false;
            self.sendPlayingEvent('Loaded');
          },
          onend() {
            switch (self.loop_mode) {
              case 2:
                self.skip('random');
                break;

              case 1:
                self.play();
                break;

              case 0:
              default:
                self.skip('next');
                break;
            }
            self.sendPlayingEvent('Ended');
          },
          onpause() {
            navigator.mediaSession.playbackState = 'paused';
            self.sendPlayingEvent('Paused');
          },
          onstop() {
            self.sendPlayingEvent('Stopped');
          },
          onseek() {},
          onvolume() {},
          onloaderror(id, err) {
            playerSendMessage(this.mode, {
              type: 'BG_PLAYER:PLAY_FAILED',
              data: err,
            });
            self.currentAudio.disabled = true;
            self.sendPlayingEvent('err');
            self.currentHowl.unload();
            delete self._media_uri_list[data.id];
          },
          onplayerror(id, err) {
            playerSendMessage(this.mode, {
              type: 'BG_PLAYER:PLAY_FAILED',
              data: err,
            });
            self.currentAudio.disabled = true;
            self.sendPlayingEvent('err');
          },
        });
      }

      if (playNow && !this.currentHowl.playing()) {
        this.currentHowl.play();
      }
    }

    /**
     * Pause the currently playing track.
     */
    pause() {
      if (!this.currentHowl) return;

      // Puase the sound.
      this.currentHowl.pause();
    }

    /**
     * Skip to the next or previous track.
     * @param  {String} direction 'next' or 'prev'.
     */
    skip(direction) {
      Howler.stop();
      // Get the next track based on the direction of the track.
      let nextIndexFn = null;
      if (this._loop_mode === 2 || direction === 'random') {
        // TODO: shuffle algorithm instead of random
        nextIndexFn = () => Math.floor(Math.random() * this.playlist.length);
      } else if (direction === 'prev') {
        nextIndexFn = (idx) => (idx - 1) % this.playlist.length;
      } else if (direction === 'next') {
        nextIndexFn = (idx) => (idx + 1) % this.playlist.length;
      }
      this.index = nextIndexFn(this.index);

      let tryCount = 0;
      while (tryCount < this.playlist.length) {
        if (!this.playlist[this.index].disabled) {
          this.play(this.index);
          return;
        }
        this.index = nextIndexFn(this.index);
        tryCount += 1;
      }
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:RETRIEVE_URL_FAIL_ALL',
      });
      this.sendLoadEvent();
    }

    set loop_mode(input) {
      const LOOP_MODE = {
        all: 0,
        one: 1,
        shuffle: 2,
      };
      let myMode = 0;
      if (typeof input === 'string') {
        myMode = LOOP_MODE[input];
      } else {
        myMode = input;
      }
      if (!Object.values(LOOP_MODE).includes(myMode)) {
        return;
      }
      this._loop_mode = myMode;
    }

    get loop_mode() {
      return this._loop_mode;
    }

    /**
     * Set the volume and update the volume slider display.
     * @param  {Number} val Volume between 0 and 1.
     */
    set volume(val) {
      // Update the global volume (affecting all Howls).
      if (typeof val === 'number') {
        Howler.volume(val);
        this.sendVolumeEvent();
        this.sendFrameUpdate();
      }
    }

    // eslint-disable-next-line class-methods-use-this
    get volume() {
      return Howler.volume();
    }

    adjustVolume(inc) {
      this.volume = inc
        ? Math.min(this.volume + 0.1, 1)
        : Math.max(this.volume - 0.1, 0);
      this.sendVolumeEvent();
      this.sendFrameUpdate();
    }

    mute() {
      Howler.mute(true);
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:MUTE',
        data: true,
      });
    }

    unmute() {
      Howler.mute(false);
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:MUTE',
        data: false,
      });
    }

    /**
     * Seek to a new position in the currently playing track.
     * @param  {Number} per Percentage through the song to skip.
     */
    seek(per) {
      if (!this.currentHowl) return;

      // Get the Howl we want to manipulate.
      const audio = this.currentHowl;

      // Convert the percent into a seek position.
      // if (audio.playing()) {
      // }
      audio.seek(audio.duration() * per);
    }

    /**
     * Format the time from seconds to M:SS.
     * @param  {Number} secs Seconds to format.
     * @return {String}      Formatted time.
     */
    static formatTime(secs) {
      const minutes = Math.floor(secs / 60) || 0;
      const seconds = secs - minutes * 60 || 0;

      return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
    }

    setMediaURI(uri, url) {
      if (url) {
        this._media_uri_list[url] = uri;
      }
    }

    setAudioDisabled(disabled, idx) {
      if (this.playlist[idx]) {
        this.playlist[idx].disabled = disabled;
      }
    }

    async sendFrameUpdate() {
      const data = {
        id: this.currentAudio ? this.currentAudio.id : 0,
        duration: this.currentHowl ? this.currentHowl.duration() : 0,
        pos: this.currentHowl ? this.currentHowl.seek() : 0,
        playedFrom: this.playedFrom,
        playing: this.playing,
      };
      if ('setPositionState' in navigator.mediaSession) {
        navigator.mediaSession.setPositionState({
          duration: this.currentHowl ? this.currentHowl.duration() : 0,
          playbackRate: this.currentHowl ? this.currentHowl.rate() : 1,
          position: this.currentHowl ? this.currentHowl.seek() : 0,
        });
      }

      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:FRAME_UPDATE',
        data,
      });
    }

    async sendPlayingEvent(reason = 'UNKNOWN') {
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:PLAY_STATE',
        data: {
          isPlaying: this.playing,
          reason,
        },
      });
    }

    async sendLoadEvent() {
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:LOAD',
        data: {
          ...this.currentAudio,
          howl: undefined,
        },
      });
    }

    async sendVolumeEvent() {
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:VOLUME',
        data: this.volume * 100,
      });
    }

    async sendPlaylistEvent() {
      playerSendMessage(this.mode, {
        type: 'BG_PLAYER:PLAYLIST',
        data: this.playlist.map((audio) => ({ ...audio, howl: undefined })),
      });
    }
  }

  // Setup our new audio player class and pass it the playlist.

  window.threadPlayer = new Player();
  window.threadPlayer.setRefreshRate();

  const { threadPlayer } = window;
  if ('mediaSession' in navigator) {
    const { mediaSession } = navigator;
    mediaSession.setActionHandler('play', () => {
      threadPlayer.play();
    });
    mediaSession.setActionHandler('pause', () => {
      threadPlayer.pause();
    });
    mediaSession.setActionHandler('seekforward', () => {
      // User clicked "Seek Forward" media notification icon.
      const { currentHowl } = threadPlayer;
      const newTime = Math.min(
        currentHowl.seek() + threadPlayer.skipTime,
        currentHowl.duration()
      );
      currentHowl.seek(newTime);
    });
    mediaSession.setActionHandler('seekbackward', () => {
      // User clicked "Seek Backward" media notification icon.
      const { currentHowl } = threadPlayer;
      const newTime = Math.max(currentHowl.seek() - threadPlayer.skipTime, 0);
      currentHowl.seek(newTime);
    });
    mediaSession.setActionHandler('nexttrack', () => {
      threadPlayer.skip('next');
    });
    mediaSession.setActionHandler('previoustrack', () => {
      threadPlayer.skip('prev');
    });
  }
  playerSendMessage(this.mode, {
    type: 'BG_PLAYER:READY',
  });
}
<|endoftext|>


<commit_before>import fetch from 'isomorphic-fetch';

// Get updated lists of scheduled and due medication
let fetchSchedule = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Get a complete list of all medications
let fetchMeds = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Get a list of medication history
let fetchHistory = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Add a new medication to the database
let addMed = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Add a new entry to the medication history
let addHistory = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Get the details for a particular medication
let selectMed = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Reset state for medication details
let deselectMed = () => {
    return {
        type: 'updateMedDetails',
        data: {
            id: '',
            name: '',
            dosage: '',
            numDoses: ''
            nextDoseSecs: '',
            nextDoseDate: '',
            instructions: '',
            precautions: '',
            edit: ''
        }
    };
};

// Begin editing property of a selected medication
let editMed = (property) => {
    return {
        type: 'updateMedDetails',
        data: {
        	edit: property
        }
    };
};

// Cancel medication edit
let cancelEdit = () => {
    return {
        type: 'updateMedDetails',
        data: {
            edit: ''
        }
    };
};

// Update medication details in the database
let submitEdit = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Delete medication from the database
let deleteMed = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

/*---------- EXPORTS ---------*/
exports.fetchSchedule = fetchSchedule;
exports.fetchMeds = fetchMeds;
exports.fetchHistory = fetchHistory;
exports.addMed = addMed;
exports.addHistory = addHistory;
exports.selectMed = selectMed;
exports.deselectMed = deselectMed;
exports.editMed = editMed;
exports.cancelEdit = cancelEdit;
exports.submitEdit = submitEdit;
exports.deleteMed = deleteMed;<commit_msg>completes fetchMed action<commit_after>import fetch from 'isomorphic-fetch';

// Get updated lists of scheduled and due medication
let fetchSchedule = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Get a complete list of all medications
let fetchMeds = () => {
    return (dispatch) => {
        let url = '/medication/all';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    list: data.allMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Get a list of medication history
let fetchHistory = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Add a new medication to the database
let addMed = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Add a new entry to the medication history
let addHistory = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Get the details for a particular medication
let selectMed = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Reset state for medication details
let deselectMed = () => {
    return {
        type: 'updateMedDetails',
        data: {
            id: '',
            name: '',
            dosage: '',
            numDoses: ''
            nextDoseSecs: '',
            nextDoseDate: '',
            instructions: '',
            precautions: '',
            edit: ''
        }
    };
};

// Begin editing property of a selected medication
let editMed = (property) => {
    return {
        type: 'updateMedDetails',
        data: {
        	edit: property
        }
    };
};

// Cancel medication edit
let cancelEdit = () => {
    return {
        type: 'updateMedDetails',
        data: {
            edit: ''
        }
    };
};

// Update medication details in the database
let submitEdit = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

// Delete medication from the database
let deleteMed = () => {
    return (dispatch) => {
        let url = '/medication';
        let request = {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            }
        };
        return fetch(url, request)
        .then((response) => {
            if (response.status < 200 || response.status >= 300) {
                let error = new Error(response.statusText);
                error.response = response;
                throw error;
            }
            return response;
        })
        .then((response) =>{
            return response.json();
        })
        .then((data) => {
            return {
                type: 'updateMedList',
                data: {
                    due: data.dueMeds,
                    list: data.upcomingMeds
                }
            };
        })
        .catch((error) => {
            console.error(error);
        });
    }
};

/*---------- EXPORTS ---------*/
exports.fetchSchedule = fetchSchedule;
exports.fetchMeds = fetchMeds;
exports.fetchHistory = fetchHistory;
exports.addMed = addMed;
exports.addHistory = addHistory;
exports.selectMed = selectMed;
exports.deselectMed = deselectMed;
exports.editMed = editMed;
exports.cancelEdit = cancelEdit;
exports.submitEdit = submitEdit;
exports.deleteMed = deleteMed;<|endoftext|>