<commit_before>/* Copyright (C) 2016 othmar52 <othmar52@users.noreply.github.com>
 *
 * This file is part of sliMpd - a php based mpd web client
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * 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 Affero General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/*
 * dependencies: jquery, backbonejs, underscorejs
 */
(function() {
	"use strict";

	var $ = window.jQuery,
		_ = window._;
	$.extend(true, window.sliMpd, {
		modules : {}
	});
	window.sliMpd.modules.LocalPlayer = window.sliMpd.modules.AbstractPlayer.extend({
		mode : "local",
		playerSelector : "#jquery_jplayer_1",

		faviconDoghnutColor : window.sliMpd.conf.color.local.favicon,
		faviconBackgroundColor : "#444",

		timeGridSelectorCanvas : "timegrid-local",
		timeGridSelectorSeekbar : ".jp-seek-bar",
		timeGridStrokeColor : window.sliMpd.conf.color.local.secondary,
		timeGridStrokeColor2 : window.sliMpd.conf.color.local.primary,

		state : {
			repeat : 1,
			random : 1,
			consume : 0
		},

		// TODO: remove property as soon as local player has full functionality
		tempNotSupportedYetNotify : {"message": "not supported in local player yet - use mpd", "type": "danger"},

		initialize(options) {
			var that = this;

			/* init local player */
			$(this.playerSelector).jPlayer({
				cssSelectorAncestor: "#jp_container_1",
				swfPath: window.sliMpd.conf.absFilePrefix + "vendor-dist/happyworm/jplayer/dist/jplayer",
				supplied: "mp3",
				useStateClassSkin: false,
				autoBlur: false,
				smoothPlayBar: true,
				keyEnabled: false,
				remainingDuration: false,
				toggleDuration: true,
				ended() {
					that.soundEnded({});
				},
				progress(e,data){
					//console.log($(this).data("jPlayer").status);
					var jStatus = $(this).data("jPlayer").status;
					that.nowPlayingPercent = jStatus.currentPercentAbsolute;
					that.nowPlayingState = (jStatus.paused === false && jStatus.currentTime > 0) ? "play" : "pause";
					that.nowPlayingDuration = jStatus.duration;
					that.nowPlayingElapsed = jStatus.currentTime;
					//that.nowPlayingItem = jStatus.src;

					// TODO: check why jPlayer event "loadedmetadata" sometimes has no duration (timegrid fails to render)
					// draw the timegrid only once as soon as we know the total duration and remove the progress eventListener
					// @see: http://jplayer.org/latest/developer-guide/#jPlayer-events
					that.drawTimeGrid();
				},
				seeked() {
					window.sliMpd.drawFavicon();
				}
			});

			this.updateStateIcons();
			window.sliMpd.modules.AbstractPlayer.prototype.initialize.call(this, options);
		},

		render(options) {
			window.sliMpd.modules.AbstractPlayer.prototype.render.call(this, options);
		},

		play(item) {
			// TODO: check why item.ext is sometimes "vorbis" instead of "ogg"
			item.ext = (item.ext === "vorbis") ? "ogg" : item.ext;

			// WARNING: jPlayer"s essential Audio formats are: mp3 or m4a.
			// wav, flac, ogg, m4a plays fine in chromium under linux but we have to add an unused mp3-property...
			// TODO: really provide alternative urls instead of adding an invalid url for mp3

			var jPlayerConfObject = {
				"mp3" : window.sliMpd.conf.absRefPrefix + "deliver/" + item.item + "?stream=1",
				"supplied": item.ext + ",mp3"
			};
			jPlayerConfObject[item.ext] = window.sliMpd.conf.absRefPrefix + "deliver/" + item.item + "?stream=1";
			$(this.playerSelector).jPlayer(
				"setMedia",
				jPlayerConfObject
			).jPlayer( "play");
			this.nowPlayingItem = item.hash;
			this.redraw(item);
			//this.reloadCss(item.hash);
		},

		prev(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.prev.call(this, item);
		},

		next(item) {
			this.soundEnded(item);
			window.sliMpd.modules.AbstractPlayer.prototype.next.call(this, item);
		},

		seekzero(item) {
			$(this.playerSelector).jPlayer("playHead", 0);
			window.sliMpd.modules.AbstractPlayer.prototype.seekzero.call(this, item);
		},

		redraw(item) {
			window.sliMpd.modules.AbstractPlayer.prototype.redraw.call(this, item);
		},

		onRedrawComplete(item) {
			// re-bind controls(seeek-bar) on ajax loaded control-markup
			$(this.playerSelector).jPlayer({cssSelectorAncestor: "#jp_container_1"});
			var that = this;
			$(".local-ctrl-seekzero", this.$el).on("click", function(e){
				that.seekzero();
			});
			this.updateStateIcons();
			window.sliMpd.modules.AbstractPlayer.prototype.onRedrawComplete.call(this, item);
		},

		updateStateIcons() {
			window.sliMpd.modules.AbstractPlayer.prototype.updateStateIcons.call(this);
		},

		// TODO: make markup more generic and move this to AbstractPlayer
		setPlayPauseIcon(item) {
			var control = $(".localplayer-play-pause");
			if (this.nowPlayingState === "play") {
				$(control).addClass("localplayer-pause").removeClass("localplayer-play").html("");
			} else {
				$(control).addClass("localplayer-play").removeClass("localplayer-pause").html("");
			}
			window.sliMpd.drawFavicon();
			window.sliMpd.modules.AbstractPlayer.prototype.setPlayPauseIcon.call(this, item);
		},

		pause(item) {
			$(this.playerSelector).jPlayer("pause");
			this.nowPlayingState = "pause";
			window.sliMpd.modules.AbstractPlayer.prototype.pause.call(this, item);
			this.setPlayPauseIcon(item);
		},

		togglePause(item) {
			if (this.nowPlayingState === "play") {
				$(this.playerSelector).jPlayer("pause");
				this.nowPlayingState = "pause";
			} else {
				$(this.playerSelector).jPlayer("play");
				this.nowPlayingState = "play";
			}
			window.sliMpd.modules.AbstractPlayer.prototype.togglePause.call(this, item);
			this.setPlayPauseIcon(item);
		},

		toggleRepeat(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			// deactivating repeat is currently not possible - so dont call AbstractPlayer::toggleRepeat()
			//window.sliMpd.modules.AbstractPlayer.prototype.toggleRepeat.call(this, item);
		},

		toggleRandom(item) {
			window.sliMpd.modules.AbstractPlayer.prototype.toggleRandom.call(this, item);
		},

		toggleConsume(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			// consume mode is currently not possible - so dont call AbstractPlayer::toggleConsume()
			//window.sliMpd.modules.AbstractPlayer.prototype.toggleConsume.call(this, item);
		},

		soundEnded(item) {
			// TODO: add functionality "current playlist" (like mpd) for local player
			// for now use all rendered tracks as playlist

			//console.log("local soundEnded()");
			if(this.state.random === "1") {
				//console.log("local random is active");
				var playableItems = $("#main .track-row:not(.track-"+ this.nowPlayingItem+")");
				if(playableItems.length < 1) {
					return;
				}
				var randomIndex = Math.floor(Math.random() * playableItems.length);
				playableItems.eq(randomIndex).find(".is-playbtn").click();
				return;
			}
			//console.log("local random is NOT active");
			// check if current track is rendered
			var current = $(".track-" + this.nowPlayingItem);
			if(!current.length) {
				//console.log("current track is not rendered. fallback to first rendered track...");
				$("#main .is-playbtn")[0].click();
				return;
			}
			//console.log("current track is rendered");
			var next = current.nextAll(".track-row").find(".is-playbtn");
			if(next.length) {
				//console.log("found next track");
				next[0].click();
				return;
			}
			//console.log("we have no next track. fallback to first rendered track...");
			$("#main .is-playbtn")[0].click();
		},

		removeDupes(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.removeDupes.call(this, item);
		},

		softclearPlaylist(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.softclearPlaylist.call(this, item);
		},

		appendTrack(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.appendTrack.call(this, item);
		},
		appendTrackAndPlay(item) {
			this.play(item);
			window.sliMpd.modules.AbstractPlayer.prototype.appendTrackAndPlay.call(this, item);
		},

		injectTrack(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectTrack.call(this, item);
		},

		injectTrackAndPlay(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectTrackAndPlay.call(this, item);
		},

		replaceTrack(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.replaceTrack.call(this, item);
		},

		softreplaceTrack(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.softreplaceTrack.call(this, item);
		},

		appendDir(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.appendDir.call(this, item);
		},

		appendDirAndPlay(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.appendDirAndPlay.call(this, item);
		},

		injectDir(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectDir.call(this, item);
		},

		injectDirAndPlay(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectDirAndPlay.call(this, item);
		},

		replaceDir(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.replaceDir.call(this, item);
		},

		softreplaceDir(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.softreplaceDir.call(this, item);
		},

		appendPlaylist(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.appendPlaylist.call(this, item);
		},

		appendPlaylistAndPlay(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.appendPlaylistAndPlay.call(this, item);
		},

		injectPlaylist(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectPlaylist.call(this, item);
		},

		injectPlaylistAndPlay(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectPlaylistAndPlay.call(this, item);
		},

		replacePlaylist(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.replacePlaylist.call(this, item);
		},

		softreplacePlaylist(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.softreplacePlaylist.call(this, item);
		}
	});
}());
<commit_msg>[BUGFIX] respact random state in localplayer<commit_after>/* Copyright (C) 2016 othmar52 <othmar52@users.noreply.github.com>
 *
 * This file is part of sliMpd - a php based mpd web client
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * 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 Affero General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/*
 * dependencies: jquery, backbonejs, underscorejs
 */
(function() {
	"use strict";

	var $ = window.jQuery,
		_ = window._;
	$.extend(true, window.sliMpd, {
		modules : {}
	});
	window.sliMpd.modules.LocalPlayer = window.sliMpd.modules.AbstractPlayer.extend({
		mode : "local",
		playerSelector : "#jquery_jplayer_1",

		faviconDoghnutColor : window.sliMpd.conf.color.local.favicon,
		faviconBackgroundColor : "#444",

		timeGridSelectorCanvas : "timegrid-local",
		timeGridSelectorSeekbar : ".jp-seek-bar",
		timeGridStrokeColor : window.sliMpd.conf.color.local.secondary,
		timeGridStrokeColor2 : window.sliMpd.conf.color.local.primary,

		state : {
			repeat : 1,
			random : 1,
			consume : 0
		},

		// TODO: remove property as soon as local player has full functionality
		tempNotSupportedYetNotify : {"message": "not supported in local player yet - use mpd", "type": "danger"},

		initialize(options) {
			var that = this;

			/* init local player */
			$(this.playerSelector).jPlayer({
				cssSelectorAncestor: "#jp_container_1",
				swfPath: window.sliMpd.conf.absFilePrefix + "vendor-dist/happyworm/jplayer/dist/jplayer",
				supplied: "mp3",
				useStateClassSkin: false,
				autoBlur: false,
				smoothPlayBar: true,
				keyEnabled: false,
				remainingDuration: false,
				toggleDuration: true,
				ended() {
					that.soundEnded({});
				},
				progress(e,data){
					//console.log($(this).data("jPlayer").status);
					var jStatus = $(this).data("jPlayer").status;
					that.nowPlayingPercent = jStatus.currentPercentAbsolute;
					that.nowPlayingState = (jStatus.paused === false && jStatus.currentTime > 0) ? "play" : "pause";
					that.nowPlayingDuration = jStatus.duration;
					that.nowPlayingElapsed = jStatus.currentTime;
					//that.nowPlayingItem = jStatus.src;

					// TODO: check why jPlayer event "loadedmetadata" sometimes has no duration (timegrid fails to render)
					// draw the timegrid only once as soon as we know the total duration and remove the progress eventListener
					// @see: http://jplayer.org/latest/developer-guide/#jPlayer-events
					that.drawTimeGrid();
				},
				seeked() {
					window.sliMpd.drawFavicon();
				}
			});

			this.updateStateIcons();
			window.sliMpd.modules.AbstractPlayer.prototype.initialize.call(this, options);
		},

		render(options) {
			window.sliMpd.modules.AbstractPlayer.prototype.render.call(this, options);
		},

		play(item) {
			// TODO: check why item.ext is sometimes "vorbis" instead of "ogg"
			item.ext = (item.ext === "vorbis") ? "ogg" : item.ext;

			// WARNING: jPlayer"s essential Audio formats are: mp3 or m4a.
			// wav, flac, ogg, m4a plays fine in chromium under linux but we have to add an unused mp3-property...
			// TODO: really provide alternative urls instead of adding an invalid url for mp3

			var jPlayerConfObject = {
				"mp3" : window.sliMpd.conf.absRefPrefix + "deliver/" + item.item + "?stream=1",
				"supplied": item.ext + ",mp3"
			};
			jPlayerConfObject[item.ext] = window.sliMpd.conf.absRefPrefix + "deliver/" + item.item + "?stream=1";
			$(this.playerSelector).jPlayer(
				"setMedia",
				jPlayerConfObject
			).jPlayer( "play");
			this.nowPlayingItem = item.hash;
			this.redraw(item);
			//this.reloadCss(item.hash);
		},

		prev(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.prev.call(this, item);
		},

		next(item) {
			this.soundEnded(item);
			window.sliMpd.modules.AbstractPlayer.prototype.next.call(this, item);
		},

		seekzero(item) {
			$(this.playerSelector).jPlayer("playHead", 0);
			window.sliMpd.modules.AbstractPlayer.prototype.seekzero.call(this, item);
		},

		redraw(item) {
			window.sliMpd.modules.AbstractPlayer.prototype.redraw.call(this, item);
		},

		onRedrawComplete(item) {
			// re-bind controls(seeek-bar) on ajax loaded control-markup
			$(this.playerSelector).jPlayer({cssSelectorAncestor: "#jp_container_1"});
			var that = this;
			$(".local-ctrl-seekzero", this.$el).on("click", function(e){
				that.seekzero();
			});
			this.updateStateIcons();
			window.sliMpd.modules.AbstractPlayer.prototype.onRedrawComplete.call(this, item);
		},

		updateStateIcons() {
			window.sliMpd.modules.AbstractPlayer.prototype.updateStateIcons.call(this);
		},

		// TODO: make markup more generic and move this to AbstractPlayer
		setPlayPauseIcon(item) {
			var control = $(".localplayer-play-pause");
			if (this.nowPlayingState === "play") {
				$(control).addClass("localplayer-pause").removeClass("localplayer-play").html("");
			} else {
				$(control).addClass("localplayer-play").removeClass("localplayer-pause").html("");
			}
			window.sliMpd.drawFavicon();
			window.sliMpd.modules.AbstractPlayer.prototype.setPlayPauseIcon.call(this, item);
		},

		pause(item) {
			$(this.playerSelector).jPlayer("pause");
			this.nowPlayingState = "pause";
			window.sliMpd.modules.AbstractPlayer.prototype.pause.call(this, item);
			this.setPlayPauseIcon(item);
		},

		togglePause(item) {
			if (this.nowPlayingState === "play") {
				$(this.playerSelector).jPlayer("pause");
				this.nowPlayingState = "pause";
			} else {
				$(this.playerSelector).jPlayer("play");
				this.nowPlayingState = "play";
			}
			window.sliMpd.modules.AbstractPlayer.prototype.togglePause.call(this, item);
			this.setPlayPauseIcon(item);
		},

		toggleRepeat(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			// deactivating repeat is currently not possible - so dont call AbstractPlayer::toggleRepeat()
			//window.sliMpd.modules.AbstractPlayer.prototype.toggleRepeat.call(this, item);
		},

		toggleRandom(item) {
			window.sliMpd.modules.AbstractPlayer.prototype.toggleRandom.call(this, item);
		},

		toggleConsume(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			// consume mode is currently not possible - so dont call AbstractPlayer::toggleConsume()
			//window.sliMpd.modules.AbstractPlayer.prototype.toggleConsume.call(this, item);
		},

		soundEnded(item) {
			// TODO: add functionality "current playlist" (like mpd) for local player
			// for now use all rendered tracks as playlist

			//console.log("local soundEnded()");
			if(this.state.random === 1) {
				//console.log("local random is active");
				var playableItems = $("#main .track-row:not(.track-"+ this.nowPlayingItem+")");
				if(playableItems.length < 1) {
					return;
				}
				var randomIndex = Math.floor(Math.random() * playableItems.length);
				playableItems.eq(randomIndex).find(".is-playbtn").click();
				return;
			}
			//console.log("local random is NOT active");
			// check if current track is rendered
			var current = $(".track-" + this.nowPlayingItem);
			if(!current.length) {
				//console.log("current track is not rendered. fallback to first rendered track...");
				$("#main .is-playbtn")[0].click();
				return;
			}
			//console.log("current track is rendered");
			var next = current.nextAll(".track-row").find(".is-playbtn");
			if(next.length) {
				//console.log("found next track");
				next[0].click();
				return;
			}
			//console.log("we have no next track. fallback to first rendered track...");
			$("#main .is-playbtn")[0].click();
		},

		removeDupes(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.removeDupes.call(this, item);
		},

		softclearPlaylist(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.softclearPlaylist.call(this, item);
		},

		appendTrack(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.appendTrack.call(this, item);
		},
		appendTrackAndPlay(item) {
			this.play(item);
			window.sliMpd.modules.AbstractPlayer.prototype.appendTrackAndPlay.call(this, item);
		},

		injectTrack(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectTrack.call(this, item);
		},

		injectTrackAndPlay(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectTrackAndPlay.call(this, item);
		},

		replaceTrack(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.replaceTrack.call(this, item);
		},

		softreplaceTrack(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.softreplaceTrack.call(this, item);
		},

		appendDir(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.appendDir.call(this, item);
		},

		appendDirAndPlay(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.appendDirAndPlay.call(this, item);
		},

		injectDir(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectDir.call(this, item);
		},

		injectDirAndPlay(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectDirAndPlay.call(this, item);
		},

		replaceDir(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.replaceDir.call(this, item);
		},

		softreplaceDir(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.softreplaceDir.call(this, item);
		},

		appendPlaylist(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.appendPlaylist.call(this, item);
		},

		appendPlaylistAndPlay(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.appendPlaylistAndPlay.call(this, item);
		},

		injectPlaylist(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectPlaylist.call(this, item);
		},

		injectPlaylistAndPlay(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.injectPlaylistAndPlay.call(this, item);
		},

		replacePlaylist(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.replacePlaylist.call(this, item);
		},

		softreplacePlaylist(item) {
			window.sliMpd.notify(this.tempNotSupportedYetNotify);
			window.sliMpd.modules.AbstractPlayer.prototype.softreplacePlaylist.call(this, item);
		}
	});
}());
<|endoftext|>


<commit_before>import {DateRangePicker} from 'react-dates';
import moment from 'moment';
import R from 'ramda';
import React, {Component, PropTypes} from 'react';

/**
 * DatePickerRange is a tailor made component designed for selecting
 * timespan across multiple days off of a calendar.
 *
 * The DatePicker integrates well with the Python datetime module with the
 * startDate and endDate being returned in a string format suitable for
 * creating datetime objects.
 *
 * This component is based off of Airbnb's react-dates react component
 * which can be found here: https://github.com/airbnb/react-dates
 */
export default class DatePickerRange extends Component {
    constructor() {
        super();
        this.propsToState = this.propsToState.bind(this);
        this.onDatesChange = this.onDatesChange.bind(this);
        this.isOutsideRange = this.isOutsideRange.bind(this);
    }

    propsToState(newProps) {
        /*
         * state includes:
         * - user modifiable attributes
         * - moment converted attributes
         */
        const newState = {};
        const momentProps = [
            'start_date',
            'end_date',
            'initial_visible_month',
            'max_date_allowed',
            'min_date_allowed'
        ]
        momentProps.forEach(prop => {
            if (R.type(newProps[prop]) !== 'Undefined') {
                newState[prop] = moment(newProps[prop]);
            }
            if (prop === 'max_date_allowed' && R.has(prop, newState)) {
                newState[prop].add(1, 'days');
            }
        });
        this.setState(newState);
    }

    componentWillReceiveProps(newProps) {
        this.propsToState(newProps)
    }

    componentWillMount() {
        this.propsToState(this.props);
    }

    onDatesChange({startDate: start_date, endDate: end_date}) {
        const {setProps, fireEvent} = this.props;
        const newState = {};
        if (setProps && start_date !== null) {
            setProps({start_date: start_date.format('YYYY-MM-DD')});
        } else {
            newState.start_date = start_date;
        }

        if (setProps && end_date !== null) {
            setProps({end_date: end_date.format('YYYY-MM-DD')});
        } else {
            newState.end_date = end_date;
        }

        if (fireEvent) {
            fireEvent('change');
        }
    }

    isOutsideRange(date) {
        const {min_date_allowed, max_date_allowed} = this.state;
        const notUndefined = R.complement(R.pipe(R.type, R.equals('Undefined')));
        return (
            (notUndefined(min_date_allowed) && date < min_date_allowed) ||
            (notUndefined(max_date_allowed) && date >= min_date_allowed)
        );
    }

    render() {
        const {
            start_date,
            end_date,
            focusedInput,
            initial_visible_month
        } = this.state;

        const {
            calendar_orientation,
            clearable,
            day_size,
            disabled,
            display_format,
            end_date_placeholder_text,
            first_day_of_week,
            is_RTL,
            minimum_nights,
            month_format,
            number_of_months_shown,
            reopen_calendar_on_clear,
            show_outside_days,
            start_date_placeholder_text,
            stay_open_on_select,
            with_full_screen_portal,
            with_portal
        } = this.props;

        const verticalFlag = (calendar_orientation !== 'vertical');

        return (
            <DateRangePicker
                daySize={day_size}
                disabled={disabled}
                displayFormat={display_format}
                enableOutsideDays={show_outside_days}
                endDate={end_date}
                endDatePlaceholderText={end_date_placeholder_text}
                firstDayOfWeek={first_day_of_week}
                focusedInput={focusedInput}
                initialVisibleMonth={() => start_date || initial_visible_month}
                isOutsideRange={this.isOutsideRange}
                isRTL={is_RTL}
                keepOpenOnDateSelect={stay_open_on_select}
                minimumNights={minimum_nights}
                monthFormat={month_format}
                numberOfMonths={number_of_months_shown}
                onDatesChange={this.onDatesChange}
                onFocusChange={focusedInput => this.setState({ focusedInput })}
                orientation={calendar_orientation}
                reopenPickerOnClearDates={reopen_calendar_on_clear}
                showClearDates={clearable}
                startDate={start_date}
                startDatePlaceholderText={start_date_placeholder_text}
                withFullScreenPortal={with_full_screen_portal && verticalFlag}
                withPortal={with_portal && verticalFlag}
            />
        );
    }
}

DatePickerRange.propTypes = {
    id: PropTypes.string,

    /**
     * Specifies the starting date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    start_date: PropTypes.string,

    /**
     * Specifies the ending date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    end_date: PropTypes.string,

    /**
     * Specifies the lowest selectable date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    min_date_allowed: PropTypes.string,

    /**
     * Specifies the highest selectable date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    max_date_allowed: PropTypes.string,

    /**
     * Specifies the month that is initially presented when the user
     * opens the calendar. Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     *
     */
    initial_visible_month: PropTypes.string,

    /**
     * Text that will be displayed in the first input
     * box of the date picker when no date is selected. Default value is 'Start Date'
     */
    start_date_placeholder_text: PropTypes.string,

    /**
     * Text that will be displayed in the second input
     * box of the date picker when no date is selected. Default value is 'End Date'
     */
    end_date_placeholder_text: PropTypes.string,

    /**
     * Size of rendered calendar days, higher number
     * means bigger day size and larger calendar overall
     */
    day_size: PropTypes.number,

    /**
     * Orientation of calendar, either vertical or horizontal.
     * Valid options are 'vertical' or 'horizontal'.
     */
    calendar_orientation: PropTypes.oneOf(['vertical', 'horizontal']),

    /**
     * Determines whether the calendar and days operate
     * from left to right or from right to left
     */
    is_RTL: PropTypes.bool,

    /**
     * If True, the calendar will automatically open when cleared
     */
    reopen_calendar_on_clear: PropTypes.bool,

    /**
     * Number of calendar months that are shown when calendar is opened
     */
    number_of_months_shown: PropTypes.number,

    /**
     * If True, calendar will open in a screen overlay portal,
     * not supported on vertical calendar
     */
    with_portal: PropTypes.bool,

    /**
     * If True, calendar will open in a full screen overlay portal, will
     * take precedent over 'withPortal' if both are set to true,
     * not supported on vertical calendar
     */
    with_full_screen_portal: PropTypes.bool,

    /**
     * Specifies what day is the first day of the week, values must be
     * from [0, ..., 6] with 0 denoting Sunday and 6 denoting Saturday
     */
    first_day_of_week: PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6]),

    /**
     * Specifies a minimum number of nights that must be selected between
     * the startDate and the endDate
     */
    minimum_nights: PropTypes.number,

    /**
     * If True the calendar will not close when the user has selected a value
     * and will wait until the user clicks off the calendar
     */
    stay_open_on_select: PropTypes.bool,

    /**
     * If True the calendar will display days that rollover into
     * the next month
     */
    show_outside_days: PropTypes.bool,

    /**
     * Specifies the format that the month will be displayed in the calendar,
     * valid formats are variations of "MM YY". For example:
     * "MM YY" renders as '05 97' for May 1997
     * "MMMM, YYYY" renders as 'May, 1997' for May 1997
     * "MMM, YY" renders as 'Sep, 97' for September 1997
     */
    month_format: PropTypes.string,

    /**
     * Specifies the format that the selected dates will be displayed
     * valid formats are variations of "MM YY DD". For example:
     * "MM YY DD" renders as '05 10 97' for May 10th 1997
     * "MMMM, YY" renders as 'May, 1997' for May 10th 1997
     * "M, D, YYYY" renders as '07, 10, 1997' for September 10th 1997
     * "MMMM" renders as 'May' for May 10 1997
     */
    display_format: PropTypes.string,

    /**
     * If True, no dates can be selected.
     */
    disabled: PropTypes.bool,

     /**
     * Whether or not the dropdown is "clearable", that is, whether or
     * not a small "x" appears on the right of the dropdown that removes
     * the selected value.
     */
    clearable: PropTypes.bool,

    /**
     * Dash-assigned callback that gets fired when the value changes.
     */
    setProps: PropTypes.func,

    /**
     * Dash-assigned callback that gets fired when the value changes.
     */
    dashEvents: PropTypes.oneOf(['change'])
};

DatePickerRange.defaultProps = {
    calendar_orientation: 'horizontal',
    is_RTL: false,
    day_size: 39,
    with_portal: false,
    with_full_screen_portal: false,
    first_day_of_week: 0,
    number_of_months_shown: 1,
    stay_open_on_select: false,
    reopen_calendar_on_clear: false,
    clearable: false,
    disabled: false
};
<commit_msg>forgot `setState`<commit_after>import {DateRangePicker} from 'react-dates';
import moment from 'moment';
import R from 'ramda';
import React, {Component, PropTypes} from 'react';

/**
 * DatePickerRange is a tailor made component designed for selecting
 * timespan across multiple days off of a calendar.
 *
 * The DatePicker integrates well with the Python datetime module with the
 * startDate and endDate being returned in a string format suitable for
 * creating datetime objects.
 *
 * This component is based off of Airbnb's react-dates react component
 * which can be found here: https://github.com/airbnb/react-dates
 */
export default class DatePickerRange extends Component {
    constructor() {
        super();
        this.propsToState = this.propsToState.bind(this);
        this.onDatesChange = this.onDatesChange.bind(this);
        this.isOutsideRange = this.isOutsideRange.bind(this);
    }

    propsToState(newProps) {
        /*
         * state includes:
         * - user modifiable attributes
         * - moment converted attributes
         */
        const newState = {};
        const momentProps = [
            'start_date',
            'end_date',
            'initial_visible_month',
            'max_date_allowed',
            'min_date_allowed'
        ]
        momentProps.forEach(prop => {
            if (R.type(newProps[prop]) !== 'Undefined') {
                newState[prop] = moment(newProps[prop]);
            }
            if (prop === 'max_date_allowed' && R.has(prop, newState)) {
                newState[prop].add(1, 'days');
            }
        });
        this.setState(newState);
    }

    componentWillReceiveProps(newProps) {
        this.propsToState(newProps)
    }

    componentWillMount() {
        this.propsToState(this.props);
    }

    onDatesChange({startDate: start_date, endDate: end_date}) {
        const {setProps, fireEvent} = this.props;
        const newState = {};
        if (setProps && start_date !== null) {
            setProps({start_date: start_date.format('YYYY-MM-DD')});
        } else {
            newState.start_date = start_date;
        }

        if (setProps && end_date !== null) {
            setProps({end_date: end_date.format('YYYY-MM-DD')});
        } else {
            newState.end_date = end_date;
        }

        if (fireEvent) {
            fireEvent('change');
        }

        this.setState(newState);
    }

    isOutsideRange(date) {
        const {min_date_allowed, max_date_allowed} = this.state;
        const notUndefined = R.complement(R.pipe(R.type, R.equals('Undefined')));
        return (
            (notUndefined(min_date_allowed) && date < min_date_allowed) ||
            (notUndefined(max_date_allowed) && date >= min_date_allowed)
        );
    }

    render() {
        const {
            start_date,
            end_date,
            focusedInput,
            initial_visible_month
        } = this.state;

        const {
            calendar_orientation,
            clearable,
            day_size,
            disabled,
            display_format,
            end_date_placeholder_text,
            first_day_of_week,
            is_RTL,
            minimum_nights,
            month_format,
            number_of_months_shown,
            reopen_calendar_on_clear,
            show_outside_days,
            start_date_placeholder_text,
            stay_open_on_select,
            with_full_screen_portal,
            with_portal
        } = this.props;

        const verticalFlag = (calendar_orientation !== 'vertical');

        return (
            <DateRangePicker
                daySize={day_size}
                disabled={disabled}
                displayFormat={display_format}
                enableOutsideDays={show_outside_days}
                endDate={end_date}
                endDatePlaceholderText={end_date_placeholder_text}
                firstDayOfWeek={first_day_of_week}
                focusedInput={focusedInput}
                initialVisibleMonth={() => start_date || initial_visible_month}
                isOutsideRange={this.isOutsideRange}
                isRTL={is_RTL}
                keepOpenOnDateSelect={stay_open_on_select}
                minimumNights={minimum_nights}
                monthFormat={month_format}
                numberOfMonths={number_of_months_shown}
                onDatesChange={this.onDatesChange}
                onFocusChange={focusedInput => this.setState({ focusedInput })}
                orientation={calendar_orientation}
                reopenPickerOnClearDates={reopen_calendar_on_clear}
                showClearDates={clearable}
                startDate={start_date}
                startDatePlaceholderText={start_date_placeholder_text}
                withFullScreenPortal={with_full_screen_portal && verticalFlag}
                withPortal={with_portal && verticalFlag}
            />
        );
    }
}

DatePickerRange.propTypes = {
    id: PropTypes.string,

    /**
     * Specifies the starting date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    start_date: PropTypes.string,

    /**
     * Specifies the ending date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    end_date: PropTypes.string,

    /**
     * Specifies the lowest selectable date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    min_date_allowed: PropTypes.string,

    /**
     * Specifies the highest selectable date for the component.
     * Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     */
    max_date_allowed: PropTypes.string,

    /**
     * Specifies the month that is initially presented when the user
     * opens the calendar. Accepts datetime.datetime objects or strings
     * in the format 'YYYY-MM-DD'
     *
     */
    initial_visible_month: PropTypes.string,

    /**
     * Text that will be displayed in the first input
     * box of the date picker when no date is selected. Default value is 'Start Date'
     */
    start_date_placeholder_text: PropTypes.string,

    /**
     * Text that will be displayed in the second input
     * box of the date picker when no date is selected. Default value is 'End Date'
     */
    end_date_placeholder_text: PropTypes.string,

    /**
     * Size of rendered calendar days, higher number
     * means bigger day size and larger calendar overall
     */
    day_size: PropTypes.number,

    /**
     * Orientation of calendar, either vertical or horizontal.
     * Valid options are 'vertical' or 'horizontal'.
     */
    calendar_orientation: PropTypes.oneOf(['vertical', 'horizontal']),

    /**
     * Determines whether the calendar and days operate
     * from left to right or from right to left
     */
    is_RTL: PropTypes.bool,

    /**
     * If True, the calendar will automatically open when cleared
     */
    reopen_calendar_on_clear: PropTypes.bool,

    /**
     * Number of calendar months that are shown when calendar is opened
     */
    number_of_months_shown: PropTypes.number,

    /**
     * If True, calendar will open in a screen overlay portal,
     * not supported on vertical calendar
     */
    with_portal: PropTypes.bool,

    /**
     * If True, calendar will open in a full screen overlay portal, will
     * take precedent over 'withPortal' if both are set to true,
     * not supported on vertical calendar
     */
    with_full_screen_portal: PropTypes.bool,

    /**
     * Specifies what day is the first day of the week, values must be
     * from [0, ..., 6] with 0 denoting Sunday and 6 denoting Saturday
     */
    first_day_of_week: PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6]),

    /**
     * Specifies a minimum number of nights that must be selected between
     * the startDate and the endDate
     */
    minimum_nights: PropTypes.number,

    /**
     * If True the calendar will not close when the user has selected a value
     * and will wait until the user clicks off the calendar
     */
    stay_open_on_select: PropTypes.bool,

    /**
     * If True the calendar will display days that rollover into
     * the next month
     */
    show_outside_days: PropTypes.bool,

    /**
     * Specifies the format that the month will be displayed in the calendar,
     * valid formats are variations of "MM YY". For example:
     * "MM YY" renders as '05 97' for May 1997
     * "MMMM, YYYY" renders as 'May, 1997' for May 1997
     * "MMM, YY" renders as 'Sep, 97' for September 1997
     */
    month_format: PropTypes.string,

    /**
     * Specifies the format that the selected dates will be displayed
     * valid formats are variations of "MM YY DD". For example:
     * "MM YY DD" renders as '05 10 97' for May 10th 1997
     * "MMMM, YY" renders as 'May, 1997' for May 10th 1997
     * "M, D, YYYY" renders as '07, 10, 1997' for September 10th 1997
     * "MMMM" renders as 'May' for May 10 1997
     */
    display_format: PropTypes.string,

    /**
     * If True, no dates can be selected.
     */
    disabled: PropTypes.bool,

     /**
     * Whether or not the dropdown is "clearable", that is, whether or
     * not a small "x" appears on the right of the dropdown that removes
     * the selected value.
     */
    clearable: PropTypes.bool,

    /**
     * Dash-assigned callback that gets fired when the value changes.
     */
    setProps: PropTypes.func,

    /**
     * Dash-assigned callback that gets fired when the value changes.
     */
    dashEvents: PropTypes.oneOf(['change'])
};

DatePickerRange.defaultProps = {
    calendar_orientation: 'horizontal',
    is_RTL: false,
    day_size: 39,
    with_portal: false,
    with_full_screen_portal: false,
    first_day_of_week: 0,
    number_of_months_shown: 1,
    stay_open_on_select: false,
    reopen_calendar_on_clear: false,
    clearable: false,
    disabled: false
};
<|endoftext|>


<commit_before>import React, { useState, useMemo } from 'react'
import PropTypes from 'prop-types'
import { useDatepicker, START_DATE } from '@datepicker-react/hooks'
import { noop } from '../../utilities/other'
import { getJSDateFromString, getValidDateTimeString } from './Datepicker.utils'
import DatepickerContext from './Datepicker.Context'
import { CalendarContainerUI } from './Datepicker.css'
import PeriodCalendar from './Datepicker.PeriodCalendar'
import DailyCalendar from './Datepicker.DailyCalendar'

function Datepicker({
  allowFutureDatePick = true,
  endDate = null,
  firstDayOfWeek = 1,
  minBookingDays = 1,
  numberOfMonths = 1,
  onDateChange = noop,
  startDate = null,
}) {
  const [dates, setDates] = useState({
    startDate: getJSDateFromString(startDate),
    endDate: getJSDateFromString(endDate) || getJSDateFromString(startDate),
    focusedInput: START_DATE,
  })
  const [prevDates, setPrevDates] = useState({})
  const [deepNavVisible, setDeepNavVisibility] = useState(false)

  const {
    activeMonths,
    isDateSelected,
    isDateHovered,
    isFirstOrLastSelectedDate,
    isDateBlocked,
    isDateFocused,
    onDateSelect,
    onDateHover,
    goToDate,
    goToPreviousMonthsByOneMonth,
    goToNextMonthsByOneMonth,
    goToPreviousYear,
    goToNextYear,
  } = useDatepicker({
    startDate: dates.startDate,
    endDate: dates.endDate,
    focusedInput: dates.focusedInput,
    onDatesChange: handleDateChange,
    numberOfMonths,
    exactMinBookingDays: minBookingDays === 1,
    maxBookingDate: !allowFutureDatePick ? new Date() : undefined,
  })

  /**
   * This section performs a `getDerivedStateFromProps` style logic where we
   * store the prop in state (as prevDate) so we can compare in the next render
   * and only if the prop changes this gets run again
   */
  if (
    getValidDateTimeString(startDate) !==
    getValidDateTimeString(prevDates.startDate)
  ) {
    setDates({
      startDate: getJSDateFromString(startDate),
      endDate: getJSDateFromString(endDate) || getJSDateFromString(startDate),
      focusedInput: START_DATE,
    })
    setPrevDates({
      startDate: getJSDateFromString(startDate),
      endDate: getJSDateFromString(endDate) || getJSDateFromString(startDate),
      focusedInput: START_DATE,
    })
    goToDate(getJSDateFromString(startDate))
  }

  function handleDateChange(data) {
    if (!data.focusedInput) {
      setDates({ ...data, focusedInput: START_DATE })
    } else {
      setDates(data)
    }

    onDateChange(data)
  }

  // We don't recreate the context value object unless it's needed.
  // That stop all the childrens from being re-rendering on each datepicker evokation
  const contextValue = useMemo(
    () => ({
      isDateBlocked,
      isDateFocused,
      isDateHovered,
      isDateSelected,
      isFirstOrLastSelectedDate,
      onDateSelect,
      onDateHover,
      startDate: dates.startDate,
      endDate: dates.endDate,
      minBookingDays,
    }),
    [
      isDateBlocked,
      isDateFocused,
      isDateHovered,
      isDateSelected,
      isFirstOrLastSelectedDate,
      onDateSelect,
      onDateHover,
      dates.startDate,
      dates.endDate,
      minBookingDays,
    ]
  )

  return (
    <DatepickerContext.Provider value={contextValue}>
      <CalendarContainerUI
        className="c-Calendar"
        numberOfMonths={numberOfMonths}
        role="dialog"
      >
        {!deepNavVisible ? (
          <DailyCalendar
            activeMonths={activeMonths}
            allowFutureDatePick={allowFutureDatePick}
            firstDayOfWeek={firstDayOfWeek}
            goToPreviousMonth={goToPreviousMonthsByOneMonth}
            goToNextMonth={goToNextMonthsByOneMonth}
            numberOfMonths={numberOfMonths}
            onDeepNavigationClick={() => {
              setDeepNavVisibility(!deepNavVisible)
            }}
          />
        ) : (
          <PeriodCalendar
            activeMonths={activeMonths}
            allowFutureDatePick={allowFutureDatePick}
            goToDate={(newDate, switchCalendar) => {
              goToDate(newDate)
              switchCalendar && setDeepNavVisibility(false)
            }}
            goToNextYear={goToNextYear}
            goToPreviousYear={goToPreviousYear}
            startDate={dates.startDate}
          />
        )}
      </CalendarContainerUI>
    </DatepickerContext.Provider>
  )
}

Datepicker.propTypes = {
  /** Whether is possible to pick a date in the future */
  allowFutureDatePick: PropTypes.bool,
  /** Pass an ending date to the calendar to select range. Note: Range not supported yet */
  endDate: PropTypes.instanceOf(Date),
  /** Which day of the week is first in the calendar (0 -> sunday, 1 -> monday, etc) */
  firstDayOfWeek: PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6]),
  /** How many days can be selected in a range (minimum). Note: Currently supported 1 */
  minBookingDays: PropTypes.number,
  /** How many months to display at the same time. Note: Currently supported 1 */
  numberOfMonths: PropTypes.number,
  /** Pass a starting date to the calendar to be selected. Accepts a valid Date object or a valid date iso string */
  startDate: PropTypes.oneOfType([
    PropTypes.instanceOf(Date),
    PropTypes.string,
  ]),
  /** Callback when the date is changed */
  onDateChange: PropTypes.func,
}

export default Datepicker
<commit_msg>MinBookingDays has effect. Clears range when making a new selection.<commit_after>import React, { useState, useMemo } from 'react'
import PropTypes from 'prop-types'
import { useDatepicker, START_DATE } from '@datepicker-react/hooks'
import { noop } from '../../utilities/other'
import { getJSDateFromString, getValidDateTimeString } from './Datepicker.utils'
import DatepickerContext from './Datepicker.Context'
import { CalendarContainerUI } from './Datepicker.css'
import PeriodCalendar from './Datepicker.PeriodCalendar'
import DailyCalendar from './Datepicker.DailyCalendar'

function Datepicker({
  allowFutureDatePick = true,
  endDate = null,
  firstDayOfWeek = 1,
  minBookingDays = 1,
  numberOfMonths = 1,
  onDateChange = noop,
  startDate = null,
}) {
  const [dates, setDates] = useState({
    startDate: getJSDateFromString(startDate),
    endDate: getJSDateFromString(endDate) || getJSDateFromString(startDate),
    focusedInput: START_DATE,
  })
  const [prevDates, setPrevDates] = useState({})
  const [deepNavVisible, setDeepNavVisibility] = useState(false)

  const {
    activeMonths,
    isDateSelected,
    isDateHovered,
    isFirstOrLastSelectedDate,
    isDateBlocked,
    isDateFocused,
    onDateSelect,
    onDateHover,
    goToDate,
    goToPreviousMonthsByOneMonth,
    goToNextMonthsByOneMonth,
    goToPreviousYear,
    goToNextYear,
  } = useDatepicker({
    startDate: dates.startDate,
    endDate: dates.endDate,
    focusedInput: dates.focusedInput,
    onDatesChange: handleDateChange,
    numberOfMonths,
    minBookingDays,
    exactMinBookingDays: minBookingDays === 1,
    maxBookingDate: !allowFutureDatePick ? new Date() : undefined,
  })

  /**
   * This section performs a `getDerivedStateFromProps` style logic where we
   * store the prop in state (as prevDate) so we can compare in the next render
   * and only if the prop changes this gets run again
   */
  if (
    getValidDateTimeString(startDate) !==
    getValidDateTimeString(prevDates.startDate)
  ) {
    setDates({
      startDate: getJSDateFromString(startDate),
      endDate: getJSDateFromString(endDate) || getJSDateFromString(startDate),
      focusedInput: START_DATE,
    })
    setPrevDates({
      startDate: getJSDateFromString(startDate),
      endDate: getJSDateFromString(endDate) || getJSDateFromString(startDate),
      focusedInput: START_DATE,
    })
    goToDate(getJSDateFromString(startDate))
  }

  function handleDateChange(data) {
    if (!data.focusedInput) {
      setDates({ ...data, focusedInput: START_DATE })
    } else {
      if (minBookingDays > 1 && data.endDate) {
        setDates({ ...data, endDate: null })
      } else {
        setDates(data)
      }
    }

    onDateChange(data)
  }

  // We don't recreate the context value object unless it's needed.
  // That stop all the childrens from being re-rendering on each datepicker evokation
  const contextValue = useMemo(
    () => ({
      isDateBlocked,
      isDateFocused,
      isDateHovered,
      isDateSelected,
      isFirstOrLastSelectedDate,
      onDateSelect,
      onDateHover,
      startDate: dates.startDate,
      endDate: dates.endDate,
      minBookingDays,
    }),
    [
      isDateBlocked,
      isDateFocused,
      isDateHovered,
      isDateSelected,
      isFirstOrLastSelectedDate,
      onDateSelect,
      onDateHover,
      dates.startDate,
      dates.endDate,
      minBookingDays,
    ]
  )

  return (
    <DatepickerContext.Provider value={contextValue}>
      <CalendarContainerUI
        className="c-Calendar"
        numberOfMonths={numberOfMonths}
        role="dialog"
      >
        {!deepNavVisible ? (
          <DailyCalendar
            activeMonths={activeMonths}
            allowFutureDatePick={allowFutureDatePick}
            firstDayOfWeek={firstDayOfWeek}
            goToPreviousMonth={goToPreviousMonthsByOneMonth}
            goToNextMonth={goToNextMonthsByOneMonth}
            numberOfMonths={numberOfMonths}
            onDeepNavigationClick={() => {
              setDeepNavVisibility(!deepNavVisible)
            }}
          />
        ) : (
          <PeriodCalendar
            activeMonths={activeMonths}
            allowFutureDatePick={allowFutureDatePick}
            goToDate={(newDate, switchCalendar) => {
              goToDate(newDate)
              switchCalendar && setDeepNavVisibility(false)
            }}
            goToNextYear={goToNextYear}
            goToPreviousYear={goToPreviousYear}
            startDate={dates.startDate}
          />
        )}
      </CalendarContainerUI>
    </DatepickerContext.Provider>
  )
}

Datepicker.propTypes = {
  /** Whether is possible to pick a date in the future */
  allowFutureDatePick: PropTypes.bool,
  /** Pass an ending date to the calendar to select range. Note: Range not supported yet */
  endDate: PropTypes.instanceOf(Date),
  /** Which day of the week is first in the calendar (0 -> sunday, 1 -> monday, etc) */
  firstDayOfWeek: PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6]),
  /** How many days can be selected in a range (minimum). Note: Currently supported 1 */
  minBookingDays: PropTypes.number,
  /** How many months to display at the same time. Note: Currently supported 1 */
  numberOfMonths: PropTypes.number,
  /** Pass a starting date to the calendar to be selected. Accepts a valid Date object or a valid date iso string */
  startDate: PropTypes.oneOfType([
    PropTypes.instanceOf(Date),
    PropTypes.string,
  ]),
  /** Callback when the date is changed */
  onDateChange: PropTypes.func,
}

export default Datepicker
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { Field, reduxForm } from 'redux-form';
import { Link } from 'react-router';
import FontAwesome from 'react-fontawesome';
import { Form, Input, FormGroup, Col, Label, Button, ButtonGroup, Alert} from 'reactstrap';
import Spinner from '../Spinner';

import { required, email, minLength } from './validate';
import renderField from './renderField';

class AccountSettings extends Component {
  renderError(){
    if(!this.props.loginError){
      return null;
    }

    return (
      <Alert color="danger">
        Error { this.props.loginError }
      </Alert>
    );
  }

  render() {

    const { handleSubmit,
            pristine,
            reset,
            submitting,
            submitFailed,
            submitSucceeded,
            error
      } = this.props;

    return (
      <Form onSubmit={handleSubmit}>
        <Field
          icon="user"
          name="username"
          type="text"
          label="Username"
          component={renderField}
          validate={[ required ]}
        />
        <Field
          icon="user"
          name="email"
          type="text"
          label="Email"
          component={renderField}
          validate={[ required ]}
        />
        <Field
          icon="user"
          name="first_name"
          type="text"
          label="First Name"
          component={renderField}
          validate={[ required ]}
        />
        <Field
          icon="user"
          name="last_name"
          type="text"
          label="Last Name"
          component={renderField}
          validate={[ required ]}
        />

        {submitting && (
          <Spinner />
        )}

        {!submitting && submitFailed && error && (
          <Alert color="danger">
            Error { error }
          </Alert>
        )}

        {!submitting && submitSucceeded && (
          <Alert color="success">
            Info Updated!
          </Alert>
        )}

        <ButtonGroup>
          <Button type="submit" color="primary" disabled={submitting}>Save</Button>
        </ButtonGroup>

      </Form>
    );
  }
}

export default reduxForm({
  form: 'accountSettings'
})(AccountSettings);
<commit_msg>Removed unused function<commit_after>import React, { Component, PropTypes } from 'react';
import { Field, reduxForm } from 'redux-form';
import { Link } from 'react-router';
import FontAwesome from 'react-fontawesome';
import { Form, Input, FormGroup, Col, Label, Button, ButtonGroup, Alert} from 'reactstrap';
import Spinner from '../Spinner';

import { required, email, minLength } from './validate';
import renderField from './renderField';

class AccountSettings extends Component {
  render() {

    const { handleSubmit,
            pristine,
            reset,
            submitting,
            submitFailed,
            submitSucceeded,
            error
      } = this.props;

    return (
      <Form onSubmit={handleSubmit}>
        <Field
          icon="user"
          name="username"
          type="text"
          label="Username"
          component={renderField}
          validate={[ required ]}
        />
        <Field
          icon="user"
          name="email"
          type="text"
          label="Email"
          component={renderField}
          validate={[ required ]}
        />
        <Field
          icon="user"
          name="first_name"
          type="text"
          label="First Name"
          component={renderField}
          validate={[ required ]}
        />
        <Field
          icon="user"
          name="last_name"
          type="text"
          label="Last Name"
          component={renderField}
          validate={[ required ]}
        />

        {submitting && (
          <Spinner />
        )}

        {!submitting && submitFailed && error && (
          <Alert color="danger">
            Error { error }
          </Alert>
        )}

        {!submitting && submitSucceeded && (
          <Alert color="success">
            Info Updated!
          </Alert>
        )}

        <ButtonGroup>
          <Button type="submit" color="primary" disabled={submitting}>Save</Button>
        </ButtonGroup>

      </Form>
    );
  }
}

export default reduxForm({
  form: 'accountSettings'
})(AccountSettings);
<|endoftext|>


<commit_before>import React from "react";

import Container from "./Container.js";
import Header from "./Header.js";
import Sidebar from "./Sidebar.js";
import MiddleContainer from "./MiddleContainer.js";

import GameGrid from "./GameGrid.js";
import GameStats from "./GameStats.js";
import Button from "./../Shared/Button.js";

const MENU_BUTTON = "Menu";

const GameScreen = React.createClass({
  PropTypes: {
    buttonClickHandler: React.PropTypes.func.isRequired,
    cellClickHandler: React.PropTypes.func.isRequired,
    cellValues: React.PropTypes.array,
    victoryStatistics: React.PropTypes.object
  },

  containerStyle: {
    fontSize: "3rem",
    fontWeight: "bold",
    textAlign: "center",
    width: "60rem",
    margin: "auto"
  },

  render () {
    return (
      <Container>
        <Header style={this.containerStyle}
            text="Tic Tac Toe React"
        />
        <Sidebar />
        <MiddleContainer>
          <GameGrid cellClickHandler={this.props.cellClickHandler}
             cellValues={this.props.cellValues} />
        </MiddleContainer>
        <Sidebar>
          <GameStats victoryStatistics={this.props.victoryStatistics} />
          <Button buttonClickHandler={this.props.buttonClickHandler}
              text={MENU_BUTTON}
          />
        </Sidebar>
      </Container>
    );
  }
});

export default GameScreen;
<commit_msg>Refactor: Major improvements<commit_after>import React from "react";

import Container from "./Container.js";
import Header from "./Header.js";
import Sidebar from "./Sidebar.js";
import MiddleContainer from "./MiddleContainer.js";

import GameGrid from "./GameGrid.js";
import GameStats from "./GameStats.js";
import Button from "./../Shared/Button.js";

import setCurrentScreenAction from "../../actions/ScreenActions.js";
import { MENU_SCREEN } from "../../actions/ScreenActions.js";
import changeGridAction from "../../actions/GameActions.js";

const GameScreen = React.createClass({
  PropTypes: {
    dispatch: React.PropTypes.func,
    cellValues: React.PropTypes.array,
    victoryStatistics: React.PropTypes.object
  },

  containerStyle: {
    fontSize: "3rem",
    fontWeight: "bold",
    textAlign: "center",
    width: "60rem",
    margin: "auto"
  },

  render () {
    return (
      <Container>
        <Header style={this.containerStyle}
            text="Tic Tac Toe React"
        />
        <Sidebar />
        <MiddleContainer>
          <GameGrid cellClickHandler={(x, y) => this.props.dispatch(
                  changeGridAction(x, y)
                )}
              cellValues={this.props.cellValues}
          />
        </MiddleContainer>
        <Sidebar>
          <GameStats victoryStatistics={this.props.victoryStatistics} />
          <Button onClick={() => this.props.dispatch(
                  setCurrentScreenAction(MENU_SCREEN)
                )}
              text="Menu"
          />
        </Sidebar>
      </Container>
    );
  }
});

export default GameScreen;
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react'
import {findPage, map} from '../../utils/navigation'
import {isRelative, resolve, parse} from '../../utils/url'
import {getBlobUrl} from '../../utils/github'

const absUrl = (url, currPageName) => {
  if (!isRelative(url)) return url
  const currPage = map[currPageName]
  const resolvedUrl = resolve(currPage.path, url)
  const resolvedPath = parse(resolvedUrl).pathname
  return getBlobUrl(currPage.repo, resolvedPath, undefined, currPage.org)
}

export default function A(props) {
  const {href, page: pageName, children, ...rest} = props
  let newHref = href
  let target

  if (href) {
    const page = findPage(absUrl(href, pageName))
    if (page && !page.external) newHref = `#${page.name}`
    else target = '_blank'
  }

  return {children}
}

A.propTypes = {
  href: PropTypes.string,
  page: PropTypes.string.isRequired,
  target: PropTypes.string,
  children: PropTypes.node
}
<commit_msg>don't rewrite hash hrefs<commit_after>import React, {PropTypes} from 'react'
import {findPage, map} from '../../utils/navigation'
import {isRelative, resolve, parse} from '../../utils/url'
import {getBlobUrl} from '../../utils/github'

const absUrl = (url, currPageName) => {
  if (!isRelative(url)) return url
  const currPage = map[currPageName]
  const resolvedUrl = resolve(currPage.path, url)
  const resolvedPath = parse(resolvedUrl).pathname
  return getBlobUrl(currPage.repo, resolvedPath, undefined, currPage.org)
}

export default function A(props) {
  const {href, page: pageName, children, ...rest} = props
  let newHref = href
  let target

  if (href && href[0] !== '#') {
    const page = findPage(absUrl(href, pageName))
    if (page && !page.external) newHref = `/${page.name}`
    else target = '_blank'
  }

  return {children}
}

A.propTypes = {
  href: PropTypes.string,
  page: PropTypes.string.isRequired,
  target: PropTypes.string,
  children: PropTypes.node
}
<|endoftext|>


<commit_before>import React, { Fragment, Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { FormControl, FormGroup, Table } from 'react-bootstrap';
import moment from 'moment';
import parse from 'html-react-parser';
import MarkdownIt from 'markdown-it';
import IconButton from '../IconButton/IconButton';
import { createMark } from '../../actions/units';
import User from '../User';
import { getRole } from '../../util/course';
import TextEditor from '../TextEditor/TextEditor';

class MarksTable extends Component {
  static propTypes = {
    answer: PropTypes.shape({
      id: PropTypes.string,
      marks: PropTypes.arrayOf(
        PropTypes.shape({
          id: PropTypes.string,
          mark: PropTypes.number,
          comment: PropTypes.string,
          createdAt: PropTypes.string,
          author: PropTypes.shape({
            id: PropTypes.string,
            email: PropTypes.string,
          }),
        }),
      ),
    }).isRequired,
    course: PropTypes.shape({
      id: PropTypes.string,
    }).isRequired,
    user: PropTypes.shape({
      id: PropTypes.string,
    }).isRequired,
    createMark: PropTypes.func.isRequired,
  };

  constructor(props) {
    super(props);

    this.state = {
      mark: '0',
      comment: '',
    };
  }

  getValidationState() {
    const { mark } = this.state;
    if (mark < 0 || mark > 100) return 'error';
    else if (mark > 0) return 'success';
    return null;
  }

  handleSubmit = e => {
    e.preventDefault();
    const md = new MarkdownIt();
    this.props.createMark({
      ...this.state,
      comment: md.render(this.state.comment),
      answerId: this.props.answer.id,
    });
  };

  handleChange = name => ({ target: { value } }) =>
    this.setState({
      [name]: value,
    });

  render() {
    const { mark, comment } = this.state;
    const { answer, course, user } = this.props;
    const { marks = [] } = answer;

    const role = getRole(course, user);

    return (
      <Fragment>

Marks list

        <form>

                #
                Mark
                Comment
                Date
                By

              {marks.map((m, index) => (

                  {index + 1}
                  {m.mark.toFixed(2)}
                  {parse(m.comment)}

                    {moment(m.createdAt).fromNow()}

                    <User user={m.author} />

              ))}
              {!marks.length && (

                    No marks yet

              )}
              {(user.isAdmin || role === 'teacher') && (

                    <FormGroup
                      bsClass="mb-0"
                      controlId="mark"
                      validationState={this.getValidationState()}
                    >
                      <FormControl
                        type="number"
                        width="100px"
                        min="0"
                        max="100"
                        step="10"
                        placeholder="Mark from 0 to 100"
                        value={mark}
                        onChange={this.handleChange('mark')}
                      />
                      <FormControl.Feedback />
                    </FormGroup>

                    <TextEditor
                      value={comment}
                      mode="markdown"
                      onChange={val => this.setState({ comment: val })}
                    />

                    <IconButton
                      disabled={this.getValidationState() === 'error'}
                      onClick={this.handleSubmit}
                      glyph="ok"
                    />

              )}

        </form>
      </Fragment>
    );
  }
}

const mapStateToProps = state => ({
  answer: state.answer,
  user: state.user,
  course: state.course,
});

export default connect(
  mapStateToProps,
  { createMark },
)(MarksTable);
<commit_msg>Add TODO<commit_after>import React, { Fragment, Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { FormControl, FormGroup, Table } from 'react-bootstrap';
import moment from 'moment';
// TODO: reuse it with html-to-react
import parse from 'html-react-parser';
import MarkdownIt from 'markdown-it';
import IconButton from '../IconButton/IconButton';
import { createMark } from '../../actions/units';
import User from '../User';
import { getRole } from '../../util/course';
import TextEditor from '../TextEditor/TextEditor';

class MarksTable extends Component {
  static propTypes = {
    answer: PropTypes.shape({
      id: PropTypes.string,
      marks: PropTypes.arrayOf(
        PropTypes.shape({
          id: PropTypes.string,
          mark: PropTypes.number,
          comment: PropTypes.string,
          createdAt: PropTypes.string,
          author: PropTypes.shape({
            id: PropTypes.string,
            email: PropTypes.string,
          }),
        }),
      ),
    }).isRequired,
    course: PropTypes.shape({
      id: PropTypes.string,
    }).isRequired,
    user: PropTypes.shape({
      id: PropTypes.string,
    }).isRequired,
    createMark: PropTypes.func.isRequired,
  };

  constructor(props) {
    super(props);

    this.state = {
      mark: '0',
      comment: '',
    };
  }

  getValidationState() {
    const { mark } = this.state;
    if (mark < 0 || mark > 100) return 'error';
    else if (mark > 0) return 'success';
    return null;
  }

  handleSubmit = e => {
    e.preventDefault();
    const md = new MarkdownIt();
    this.props.createMark({
      ...this.state,
      comment: md.render(this.state.comment),
      answerId: this.props.answer.id,
    });
  };

  handleChange = name => ({ target: { value } }) =>
    this.setState({
      [name]: value,
    });

  render() {
    const { mark, comment } = this.state;
    const { answer, course, user } = this.props;
    const { marks = [] } = answer;

    const role = getRole(course, user);

    return (
      <Fragment>

Marks list

        <form>

                #
                Mark
                Comment
                Date
                By

              {marks.map((m, index) => (

                  {index + 1}
                  {m.mark.toFixed(2)}
                  {parse(m.comment)}

                    {moment(m.createdAt).fromNow()}

                    <User user={m.author} />

              ))}
              {!marks.length && (

                    No marks yet

              )}
              {(user.isAdmin || role === 'teacher') && (

                    <FormGroup
                      bsClass="mb-0"
                      controlId="mark"
                      validationState={this.getValidationState()}
                    >
                      <FormControl
                        type="number"
                        width="100px"
                        min="0"
                        max="100"
                        step="10"
                        placeholder="Mark from 0 to 100"
                        value={mark}
                        onChange={this.handleChange('mark')}
                      />
                      <FormControl.Feedback />
                    </FormGroup>

                    <TextEditor
                      value={comment}
                      mode="markdown"
                      onChange={val => this.setState({ comment: val })}
                    />

                    <IconButton
                      disabled={this.getValidationState() === 'error'}
                      onClick={this.handleSubmit}
                      glyph="ok"
                    />

              )}

        </form>
      </Fragment>
    );
  }
}

const mapStateToProps = state => ({
  answer: state.answer,
  user: state.user,
  course: state.course,
});

export default connect(
  mapStateToProps,
  { createMark },
)(MarksTable);
<|endoftext|>


<commit_before>define(['./module'],function (services){
  'use strict';
  services.service('SubjectService', function($http, $q, Subject, limitToFilter){
    // we will store and cache the subjects here
    var subjects = [];
    // store the colors for reuse on delete
    var assignedColors = {};
    var colors = [
      'turquoise', 'emerland', 'peter-river', 'amethyst', 'wet-asphalt',
      'green-sea', 'nephritis', 'belize-hole', 'wisteria', 'midnight-blue',
      'sun-flower', 'carrot', 'alizarin', 'concrete', 'orange',
      'pumpkin', 'pomegranate', 'asbestos'
    ];
    var getColor = function(code){
      if(assignedColors[code] === undefined){
        var rand = Math.floor(Math.random() * colors.length);
        var randcolor = colors[rand];
        assignedColors[code] = randcolor;
        colors.splice(rand,1);
      }
      return assignedColors[code];
    };
    var freeColor = function(code){
      colors.push(assignedColors[code]);
      delete assignedColors[code];
    };
    var getSubject = function(code){
      var search = subjects.filter(function(subject){
        return parseInt(subject.code) === parseInt(code);
      });
      if (search.length > 0) {
        return search[0];
      } else {
        return false;
      }
    };
    /*
     * exposing the service methods
     */
    return {
      get : function(){
        return subjects;
      },
      del : function(code){
        for (var i =0; i < subjects.length; i++) {
          if (subjects[i].code === code) {
            subjects.splice(i,1);
            freeColor(code);
            break;
          }
        }
      },
      add : function(item){
        /*
         * Searching in the subjects array
         */
        var isSubject = getSubject(item.code);
        if(!isSubject){
          var s = {
            name : item.name,
            code : item.code,
            departament : 'BIO',
            color: getColor(item.code)
          };
          var subject = new Subject(s);
          subjects.push(subject);
        }
      },
      getByCode: function(code){
        return getSubject(parseInt(code));
      },
      getTooltip: function(subjectCode,groupCode){

        var subject = getSubject(subjectCode);
        console.log(subject);
        var teacherName = "";
        _.forEach(subject.teachers,function(teacher){
          _.forEach(teacher.groups,function(group){
            if(group.code === groupCode){
              teacherName = teacher.name;
            }
          });
        });
        return teacherName;
      },
      autoComplete : function(name){
        return $http.get('/api/v1.0/subject/autocomplete/' + name + '/?format=json')
        .then(function(response){
          return limitToFilter(response.data,15);
        });
      },
      getQuery: function(){
        var query = '';
        subjects.forEach(function(subject){
          var appendOnce = false;
          if ( subject.teachers === null ){
            return "";
          } else {
            subject.teachers.forEach(function(teacher){
              teacher.groups.forEach(function(group){
                if (group.isChecked) {
                  if(!appendOnce) {
                    query += ',' + group.subject;
                    appendOnce = true;
                  }
                  query += '|' + group.code;
                }
              });
            });
          }
        });
        if(query.length > 0) query = query.substring(1);
        return query;
      }
    };
  });
});
<commit_msg>deleting a console log<commit_after>define(['./module'],function (services){
  'use strict';
  services.service('SubjectService', function($http, $q, Subject, limitToFilter){
    // we will store and cache the subjects here
    var subjects = [];
    // store the colors for reuse on delete
    var assignedColors = {};
    var colors = [
      'turquoise', 'emerland', 'peter-river', 'amethyst', 'wet-asphalt',
      'green-sea', 'nephritis', 'belize-hole', 'wisteria', 'midnight-blue',
      'sun-flower', 'carrot', 'alizarin', 'concrete', 'orange',
      'pumpkin', 'pomegranate', 'asbestos'
    ];
    var getColor = function(code){
      if(assignedColors[code] === undefined){
        var rand = Math.floor(Math.random() * colors.length);
        var randcolor = colors[rand];
        assignedColors[code] = randcolor;
        colors.splice(rand,1);
      }
      return assignedColors[code];
    };
    var freeColor = function(code){
      colors.push(assignedColors[code]);
      delete assignedColors[code];
    };
    var getSubject = function(code){
      var search = subjects.filter(function(subject){
        return parseInt(subject.code) === parseInt(code);
      });
      if (search.length > 0) {
        return search[0];
      } else {
        return false;
      }
    };
    /*
     * exposing the service methods
     */
    return {
      get : function(){
        return subjects;
      },
      del : function(code){
        for (var i =0; i < subjects.length; i++) {
          if (subjects[i].code === code) {
            subjects.splice(i,1);
            freeColor(code);
            break;
          }
        }
      },
      add : function(item){
        /*
         * Searching in the subjects array
         */
        var isSubject = getSubject(item.code);
        if(!isSubject){
          var s = {
            name : item.name,
            code : item.code,
            departament : 'BIO',
            color: getColor(item.code)
          };
          var subject = new Subject(s);
          subjects.push(subject);
        }
      },
      getByCode: function(code){
        return getSubject(parseInt(code));
      },
      getTooltip: function(subjectCode,groupCode){

        var subject = getSubject(subjectCode);
        var teacherName = "";
        _.forEach(subject.teachers,function(teacher){
          _.forEach(teacher.groups,function(group){
            if(group.code === groupCode){
              teacherName = teacher.name;
            }
          });
        });
        return teacherName;
      },
      autoComplete : function(name){
        return $http.get('/api/v1.0/subject/autocomplete/' + name + '/?format=json')
        .then(function(response){
          return limitToFilter(response.data,15);
        });
      },
      getQuery: function(){
        var query = '';
        subjects.forEach(function(subject){
          var appendOnce = false;
          if ( subject.teachers === null ){
            return "";
          } else {
            subject.teachers.forEach(function(teacher){
              teacher.groups.forEach(function(group){
                if (group.isChecked) {
                  if(!appendOnce) {
                    query += ',' + group.subject;
                    appendOnce = true;
                  }
                  query += '|' + group.code;
                }
              });
            });
          }
        });
        if(query.length > 0) query = query.substring(1);
        return query;
      }
    };
  });
});
<|endoftext|>


<commit_before>var web = {

    init: function init(canvasId, axisId) {

        var that = this, perf = VENT.performance, i;

        // Initialize Canvas
        this.canvas = document.getElementById(canvasId);
        this.canvas.width = window.innerWidth;
        this.canvas.height = window.innerHeight;

        this.axis = document.getElementById(axisId);
        this.axis.width = window.innerWidth;
        this.axis.height = window.innerHeight;

        this.center = {
            x: Math.floor(this.canvas.width / 2),
            y: Math.floor(this.canvas.height / 2)
        };

        // Initialize Context
        this.ctx = this.canvas.getContext("2d");
        this.axisCtx = this.axis.getContext("2d");

        // Instantiate Data
        this.pointCount = 600;
        this.step = 0;
        this.activity = [];
        this.strength = [];

        // Generate Data
        for (i = 0; i<this.pointCount; i++) {
            this.activity.push(this.calculateStack(this.center, this.pointCount, i, 300, 250, "sin"));
            this.strength.push(this.calculateStack(this.center, this.pointCount, i, -100, 249, "random"));
        }

        // Enable Performance Monitoring
        perf.init("vent-fps-value");

        // Register Render Function
        VENT.renderer.registerRender(function() {
            that.render();
        });

        this.drawAxis();

    },

    render: function render() {

        // Clear Screen
        this.clear();

        // Draw Lines
        this.drawHaystack(this.activity, "rgba(132,216,77,1)", 2);
        this.drawHaystack(this.strength, "rgba(100,100,100,1)", 2);

        // Increment Step
        this.step++;
        if (this.step >= this.activity.length) { this.step = 0; }

        // Draw Values
        this.activity[this.step] = this.calculateStack(this.center, this.pointCount, this.step, 300, 250, "sin");
        this.strength[this.step] = this.calculateStack(this.center, this.pointCount, this.step, -100, 249, "random");

        // Draw Step Indicator
        this.drawPath([this.center, this.activity[this.step][0]], "rgba(255,255,255,1)", 2);

    },

    calculateStack: function calculateStack(center, points, step, maxSize, radius, type) {

        var size, a, ang, c;

        var tangent = VENT.utilities.pointTangent;
        var degrees = VENT.utilities.degrees;

        if (type === "random") {
            size = (Math.random()*Math.random()) * maxSize;
        } else if (type === "sin") {
            size = (maxSize * 0.5) +  ((Math.sin(VENT.performance.elapsed() * 2) * Math.cos(VENT.performance.elapsed())) * (maxSize * 0.4)) + (maxSize * 0.1 * Math.random());
        }

        a = tangent(center, step * (360/points), radius);
        ang = Math.atan2(center.y - a.y, center.x - a.x);
        c = tangent(a, degrees(ang), size / -2);

        return [c, a];

    },

    clear: function clear() {
        VENT.canvas.clearRect(this.ctx, 0, 0, this.canvas.width, this.canvas.height);
    },

    drawHaystack: function drawHaystack(steps, strokeStyle, lineWidth) {
        VENT.canvas.drawHaystack(this.ctx, steps, strokeStyle, lineWidth);
    },

    drawPath: function drawPath(steps, strokeStyle, lineWidth) {
        VENT.canvas.drawPath(this.ctx, steps, strokeStyle, lineWidth);
    },

    drawAxis: function drawAxis() {
        var drawCircle = VENT.canvas.drawCircle;
        drawCircle(this.axisCtx, this.center, 200, null, "rgba(255,255,255,0.2)", 1);
        drawCircle(this.axisCtx, this.center, 300, null, "rgba(255,255,255,0.2)", 1);
        drawCircle(this.axisCtx, this.center, 350, null, "rgba(255,255,255,0.2)", 1);
        drawCircle(this.axisCtx, this.center, 400, null, "rgba(255,255,255,0.2)", 1);
    }

};

window.onload = function onload() {

    web.init("canvas", "axis");

};

<commit_msg>Implemented and converted to new Canvas class.<commit_after>var web = {

    init: function init(canvasId, axisId) {

        var that = this, perf = VENT.performance, i;

        // Initialize Canvas

        this.canvas = new VENT.Canvas(canvasId, {
            fullscreen: true,
            zindex: 5
        });

        this.axis = new VENT.Canvas(axisId, {
            fullscreen: true,
            zindex: 0
        });

        this.center = {
            x: Math.floor(this.canvas.width / 2),
            y: Math.floor(this.canvas.height / 2)
        };

        // Instantiate Data
        this.pointCount = 600;
        this.step = 0;
        this.hover = null;
        this.activity = [];
        this.strength = [];

        // Generate Data
        for (i = 0; i<this.pointCount; i++) {
            this.activity.push(this.calculateStack(this.center, this.pointCount, i, 300, 250, "sin"));
            this.strength.push(this.calculateStack(this.center, this.pointCount, i, -100, 249, "random"));
        }

        // Enable Performance Monitoring
        perf.init("vent-fps-value");

        // Initialize Mouse
        this.mouse = { x: 0, y: 0 };

        // Register Render Function
        VENT.renderer.registerRender(function() {
            that.render();
        });

        // Register Mouse Move Function
        this.canvas.dom.onmousemove = function(e) {
            that.handleMouse.call(that, e);
        };

        this.drawAxis(this.center);

    },

    handleMouse: function handleMouse(e) {

        var distance = VENT.utilities.pointDistance(this.mouse, this.center);

        this.mouse = {
            x: e.clientX,
            y: e.clientY
        };

        if (distance <= 400 && distance >= 200) {
            var utils = VENT.utilities;
            this.hover = Math.round((utils.degrees(utils.pointAngle(this.mouse, this.center)) + 180) / (360/this.pointCount));
            if (this.hover === this.pointCount) { this.hover = 0; }
        } else { this.hover = null; }

    },

    render: function render() {

        // Clear Screen
        this.canvas.clear();

        // Draw Lines
        this.canvas.drawHaystack(this.activity, "rgba(132,216,77,1)", 2);
        this.canvas.drawHaystack(this.strength, "rgba(100,100,100,1)", 2);

        // Increment Step
        this.step++;
        if (this.step >= this.activity.length) { this.step = 0; }

        // Draw Values
        this.activity[this.step] = this.calculateStack(this.center, this.pointCount, this.step, 300, 250, "sin");
        this.strength[this.step] = this.calculateStack(this.center, this.pointCount, this.step, -100, 249, "random");

        // Draw Step Indicator
        this.canvas.drawPath([this.center, this.activity[this.step][0]], "rgba(255,255,255,1)", 2);

        // Draw Hover Indicator
        if (this.hover !== null) {
            this.canvas.drawPath([this.strength[this.hover][0], this.activity[this.hover][0]], "rgba(255,123,0,1)", 2);
        }

    },

    calculateStack: function calculateStack(center, points, step, maxSize, radius, type) {

        var size, a, ang, c;

        var tangent = VENT.utilities.pointTangent;
        var degrees = VENT.utilities.degrees;

        if (type === "random") {
            size = (Math.random()*Math.random()) * maxSize;
        } else if (type === "sin") {
            size = (maxSize * 0.5) +  ((Math.sin(VENT.performance.elapsed() * 2) * Math.cos(VENT.performance.elapsed())) * (maxSize * 0.4)) + (maxSize * 0.1 * Math.random());
        }

        a = tangent(center, step * (360/points), radius);
        ang = Math.atan2(center.y - a.y, center.x - a.x);
        c = tangent(a, degrees(ang), size / -2);

        return [c, a];

    },

    drawAxis: function drawAxis(center) {
        console.log(this);
        this.axis.drawCircle(center, 10, "rgba(132,216,77,0.5)");
        this.axis.drawCircle(center, 5, "rgba(255,255,255,1)");
        this.axis.drawCircle(center, 200, null, "rgba(255,255,255,0.2)", 1);
        this.axis.drawCircle(center, 300, null, "rgba(255,255,255,0.2)", 1);
        this.axis.drawCircle(center, 350, null, "rgba(255,255,255,0.2)", 1);
        this.axis.drawCircle(center, 400, null, "rgba(255,255,255,0.2)", 1);
    }

};

window.onload = function onload() {

    web.init("canvas", "axis");

};

<|endoftext|>