<commit_before>/* global Module */

/* Magic Mirror
 * Module: CurrentWeather
 *
 * By Michael Teeuw http://michaelteeuw.nl
 * MIT Licensed.
 */

Module.register("currentweather",{

	// Default module config.
	defaults: {
		location: "",
		appid: "",
		units: config.units,
		updateInterval: 10 * 60 * 1000, // every 10 minutes
		animationSpeed: 1000,
		timeFormat: config.timeFormat,
		showPeriod: true,
		showPeriodUpper: false,
		showDirection: false,
		lang: config.language,

		initialLoadDelay: 0, // 0 seconds delay
		retryDelay: 2500,

		apiVersion: "2.5",
		apiBase: "http://api.openweathermap.org/data/",
		weatherEndpoint: "weather",

		iconTable: {
			"01d": "wi-day-sunny",
			"02d": "wi-day-cloudy",
			"03d": "wi-cloudy",
			"04d": "wi-cloudy-windy",
			"09d": "wi-showers",
			"10d": "wi-rain",
			"11d": "wi-thunderstorm",
			"13d": "wi-snow",
			"50d": "wi-fog",
			"01n": "wi-night-clear",
			"02n": "wi-night-cloudy",
			"03n": "wi-night-cloudy",
			"04n": "wi-night-cloudy",
			"09n": "wi-night-showers",
			"10n": "wi-night-rain",
			"11n": "wi-night-thunderstorm",
			"13n": "wi-night-snow",
			"50n": "wi-night-alt-cloudy-windy"
		},
	},

	// Define required scripts.
	getScripts: function() {
		return ["moment.js"];
	},

	// Define required scripts.
	getStyles: function() {
		return ["weather-icons.css", "currentweather.css"];
	},

	// Define start sequence.
	start: function() {
		Log.info("Starting module: " + this.name);

		// Set locale.
		moment.locale(config.language);

		this.windSpeed = null;
		this.windDirection = null;
		this.sunriseSunsetTime = null;
		this.sunriseSunsetIcon = null;
		this.temperature = null;
		this.weatherType = null;

		this.loaded = false;
		this.scheduleUpdate(this.config.initialLoadDelay);

		this.updateTimer = null;

	},

	// Override dom generator.
	getDom: function() {
		var wrapper = document.createElement("div");

		if (this.config.appid === "") {
			wrapper.innerHTML = "Please set the correct openweather appid in the config for module: " + this.name + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (this.config.location === "") {
			wrapper.innerHTML = "Please set the openweather location in the config for module: " + this.name + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (!this.loaded) {
			wrapper.innerHTML = "Loading weather ...";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		var small = document.createElement("div");
		small.className = "normal medium";

		var windIcon = document.createElement("span");
		windIcon.className = "wi wi-strong-wind dimmed";
		small.appendChild(windIcon);

		var windSpeed = document.createElement("span");
		windSpeed.innerHTML = " " + this.windSpeed;
		small.appendChild(windSpeed);

		var windDirection = document.createElement("span");
		windDirection.innerHTML = " " + this.windDirection;
		small.appendChild(windDirection);

		var spacer = document.createElement("span");
		spacer.innerHTML = " ";
		small.appendChild(spacer);

		var sunriseSunsetIcon = document.createElement("span");
		sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
		small.appendChild(sunriseSunsetIcon);

		var sunriseSunsetTime = document.createElement("span");
		sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
		small.appendChild(sunriseSunsetTime);

		var large = document.createElement("div");
		large.className = "large light";

		var weatherIcon = document.createElement("span");
		weatherIcon.className = "wi weathericon " + this.weatherType;
		large.appendChild(weatherIcon);

		var temperature = document.createElement("span");
		temperature.className = "bright";
		temperature.innerHTML = " " + this.temperature + "°";
		large.appendChild(temperature);

		wrapper.appendChild(small);
		wrapper.appendChild(large);
		return wrapper;
	},

	/* updateWeather(compliments)
	 * Requests new data from openweather.org.
	 * Calls processWeather on succesfull response.
	 */
	updateWeather: function() {
		var url = this.config.apiBase + this.config.apiVersion + "/" + this.config.weatherEndpoint + this.getParams();
		var self = this;
		var retry = true;

		var weatherRequest = new XMLHttpRequest();
		weatherRequest.open("GET", url, true);
		weatherRequest.onreadystatechange = function() {
			if (this.readyState === 4) {
				if (this.status === 200) {
					self.processWeather(JSON.parse(this.response));
				} else if (this.status === 401) {
					self.config.appid = "";
					self.updateDom(self.config.animationSpeed);

					Log.error(self.name + ": Incorrect APPID.");
					retry = false;
				} else {
					Log.error(self.name + ": Could not load weather.");
				}

				if (retry) {
					self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay);
				}
			}
		};
		weatherRequest.send();
	},

	/* getParams(compliments)
	 * Generates an url with api parameters based on the config.
	 *
	 * return String - URL params.
	 */
	getParams: function() {
		var params = "?";
		params += "q=" + this.config.location;
		params += "&units=" + this.config.units;
		params += "&lang=" + this.config.lang;
		params += "&APPID=" + this.config.appid;

		return params;
	},

	/* processWeather(data)
	 * Uses the received data to set the various values.
	 *
	 * argument data object - Weather information received form openweather.org.
	 */
	processWeather: function(data) {
		this.temperature = this.roundValue(data.main.temp);
		this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
		if (this.showDirection) {
			this.windDirection = this.deg2Cardinal(data.wind.deg);
		} else {}
		this.weatherType = this.config.iconTable[data.weather[0].icon];

		var now = new Date();
		var sunrise = new Date(data.sys.sunrise * 1000);
		var sunset = new Date(data.sys.sunset * 1000);

		// The moment().format('h') method has a bug on the Raspberry Pi.
		// So we need to generate the timestring manually.
		// See issue: https://github.com/MichMich/MagicMirror/issues/181
		var sunriseSunsetDateObject = (sunrise < now && sunset > now) ? sunset : sunrise;
		var timeString = moment(sunriseSunsetDateObject).format('HH:mm');
		if (this.config.timeFormat !== 24) {
			var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
			if (this.config.showPeriod) {
				if (this.config.showPeriodUpper) {
					timeString = hours + moment(sunriseSunsetDateObject).format(':mm A');
				} else {
					timeString = hours + moment(sunriseSunsetDateObject).format(':mm a');
				}
			} else {
    				timeString = hours + moment(sunriseSunsetDateObject).format(':mm');
			}
		}

		this.sunriseSunsetTime = timeString;
		this.sunriseSunsetIcon = (sunrise < now && sunset > now) ? "wi-sunset" : "wi-sunrise";

		this.loaded = true;
		this.updateDom(this.config.animationSpeed);
	},

	/* scheduleUpdate()
	 * Schedule next update.
	 *
	 * argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
	 */
	scheduleUpdate: function(delay) {
		var nextLoad = this.config.updateInterval;
		if (typeof delay !== "undefined" && delay >= 0) {
			nextLoad = delay;
		}

		var self = this;
		setTimeout(function() {
			self.updateWeather();
		}, nextLoad);
	},

	/* ms2Beaufort(ms)
	 * Converts m2 to beaufort (windspeed).
	 *
	 * argument ms number - Windspeed in m/s.
	 *
	 * return number - Windspeed in beaufort.
	 */
	ms2Beaufort: function(ms) {
		var kmh = ms * 60 * 60 / 1000;
		var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
		for (var beaufort in speeds) {
			var speed = speeds[beaufort];
			if (speed > kmh) {
				return beaufort;
			}
		}
		return 12;
	},

	/* function(temperature)
	 * Rounds a temperature to 1 decimal.
	 *
	 * argument temperature number - Temperature.
	 *
	 * return number - Rounded Temperature.
	 */

	deg2Card: function(deg) {
                if (deg>11.25 && deg<33.75){
                        return "NNE";
                }else if (deg>33.75 && deg<56.25){
                        return "ENE";
                }else if (deg>56.25 && deg<78.75){
                        return "E";
                }else if (deg>78.75 && deg<101.25){
                        return "ESE";
                }else if (deg>101.25 && deg<123.75){
                        return "ESE";
                }else if (deg>123.75 && deg<146.25){
                        return "SE";
                }else if (deg>146.25 && deg<168.75){
                        return "SSE";
                }else if (deg>168.75 && deg<191.25){
                        return "S";
                }else if (deg>191.25 && deg<213.75){
                        return "SSW";
                }else if (deg>213.75 && deg<236.25){
                        return "SW";
                }else if (deg>236.25 && deg<258.75){
                        return "WSW";
                }else if (deg>258.75 && deg<281.25){
                        return "W";
                }else if (deg>281.25 && deg<303.75){
                        return "WNW";
                }else if (deg>303.75 && deg<326.25){
                        return "NW";
                }else if (deg>326.25 && deg<348.75){
                        return "NNW";
                }else{
                         return "N";
                }
	}

	roundValue: function(temperature) {
		return parseFloat(temperature).toFixed(1);
	}
});
<commit_msg>Update currentweather.js<commit_after>/* global Module */

/* Magic Mirror
 * Module: CurrentWeather
 *
 * By Michael Teeuw http://michaelteeuw.nl
 * MIT Licensed.
 */

Module.register("currentweather",{

	// Default module config.
	defaults: {
		location: "",
		appid: "",
		units: config.units,
		updateInterval: 10 * 60 * 1000, // every 10 minutes
		animationSpeed: 1000,
		timeFormat: config.timeFormat,
		showPeriod: true,
		showPeriodUpper: false,
		showDirection: false,
		lang: config.language,

		initialLoadDelay: 0, // 0 seconds delay
		retryDelay: 2500,

		apiVersion: "2.5",
		apiBase: "http://api.openweathermap.org/data/",
		weatherEndpoint: "weather",

		iconTable: {
			"01d": "wi-day-sunny",
			"02d": "wi-day-cloudy",
			"03d": "wi-cloudy",
			"04d": "wi-cloudy-windy",
			"09d": "wi-showers",
			"10d": "wi-rain",
			"11d": "wi-thunderstorm",
			"13d": "wi-snow",
			"50d": "wi-fog",
			"01n": "wi-night-clear",
			"02n": "wi-night-cloudy",
			"03n": "wi-night-cloudy",
			"04n": "wi-night-cloudy",
			"09n": "wi-night-showers",
			"10n": "wi-night-rain",
			"11n": "wi-night-thunderstorm",
			"13n": "wi-night-snow",
			"50n": "wi-night-alt-cloudy-windy"
		},
	},

	// Define required scripts.
	getScripts: function() {
		return ["moment.js"];
	},

	// Define required scripts.
	getStyles: function() {
		return ["weather-icons.css", "currentweather.css"];
	},

	// Define start sequence.
	start: function() {
		Log.info("Starting module: " + this.name);

		// Set locale.
		moment.locale(config.language);

		this.windSpeed = null;
		this.windDirection = null;
		this.sunriseSunsetTime = null;
		this.sunriseSunsetIcon = null;
		this.temperature = null;
		this.weatherType = null;

		this.loaded = false;
		this.scheduleUpdate(this.config.initialLoadDelay);

		this.updateTimer = null;

	},

	// Override dom generator.
	getDom: function() {
		var wrapper = document.createElement("div");

		if (this.config.appid === "") {
			wrapper.innerHTML = "Please set the correct openweather appid in the config for module: " + this.name + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (this.config.location === "") {
			wrapper.innerHTML = "Please set the openweather location in the config for module: " + this.name + ".";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		if (!this.loaded) {
			wrapper.innerHTML = "Loading weather ...";
			wrapper.className = "dimmed light small";
			return wrapper;
		}

		var small = document.createElement("div");
		small.className = "normal medium";

		var windIcon = document.createElement("span");
		windIcon.className = "wi wi-strong-wind dimmed";
		small.appendChild(windIcon);

		var windSpeed = document.createElement("span");
		windSpeed.innerHTML = " " + this.windSpeed;
		small.appendChild(windSpeed);

		var windDirection = document.createElement("span");
		windDirection.innerHTML = " " + this.windDirection;
		small.appendChild(windDirection);

		var spacer = document.createElement("span");
		spacer.innerHTML = " ";
		small.appendChild(spacer);

		var sunriseSunsetIcon = document.createElement("span");
		sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
		small.appendChild(sunriseSunsetIcon);

		var sunriseSunsetTime = document.createElement("span");
		sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
		small.appendChild(sunriseSunsetTime);

		var large = document.createElement("div");
		large.className = "large light";

		var weatherIcon = document.createElement("span");
		weatherIcon.className = "wi weathericon " + this.weatherType;
		large.appendChild(weatherIcon);

		var temperature = document.createElement("span");
		temperature.className = "bright";
		temperature.innerHTML = " " + this.temperature + "°";
		large.appendChild(temperature);

		wrapper.appendChild(small);
		wrapper.appendChild(large);
		return wrapper;
	},

	/* updateWeather(compliments)
	 * Requests new data from openweather.org.
	 * Calls processWeather on succesfull response.
	 */
	updateWeather: function() {
		var url = this.config.apiBase + this.config.apiVersion + "/" + this.config.weatherEndpoint + this.getParams();
		var self = this;
		var retry = true;

		var weatherRequest = new XMLHttpRequest();
		weatherRequest.open("GET", url, true);
		weatherRequest.onreadystatechange = function() {
			if (this.readyState === 4) {
				if (this.status === 200) {
					self.processWeather(JSON.parse(this.response));
				} else if (this.status === 401) {
					self.config.appid = "";
					self.updateDom(self.config.animationSpeed);

					Log.error(self.name + ": Incorrect APPID.");
					retry = false;
				} else {
					Log.error(self.name + ": Could not load weather.");
				}

				if (retry) {
					self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay);
				}
			}
		};
		weatherRequest.send();
	},

	/* getParams(compliments)
	 * Generates an url with api parameters based on the config.
	 *
	 * return String - URL params.
	 */
	getParams: function() {
		var params = "?";
		params += "q=" + this.config.location;
		params += "&units=" + this.config.units;
		params += "&lang=" + this.config.lang;
		params += "&APPID=" + this.config.appid;

		return params;
	},

	/* processWeather(data)
	 * Uses the received data to set the various values.
	 *
	 * argument data object - Weather information received form openweather.org.
	 */
	processWeather: function(data) {
		this.temperature = this.roundValue(data.main.temp);
		this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
		if (this.showDirection) {
			this.windDirection = this.deg2Cardinal(data.wind.deg);
		} else {}
		this.weatherType = this.config.iconTable[data.weather[0].icon];

		var now = new Date();
		var sunrise = new Date(data.sys.sunrise * 1000);
		var sunset = new Date(data.sys.sunset * 1000);

		// The moment().format('h') method has a bug on the Raspberry Pi.
		// So we need to generate the timestring manually.
		// See issue: https://github.com/MichMich/MagicMirror/issues/181
		var sunriseSunsetDateObject = (sunrise < now && sunset > now) ? sunset : sunrise;
		var timeString = moment(sunriseSunsetDateObject).format('HH:mm');
		if (this.config.timeFormat !== 24) {
			var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
			if (this.config.showPeriod) {
				if (this.config.showPeriodUpper) {
					timeString = hours + moment(sunriseSunsetDateObject).format(':mm A');
				} else {
					timeString = hours + moment(sunriseSunsetDateObject).format(':mm a');
				}
			} else {
    				timeString = hours + moment(sunriseSunsetDateObject).format(':mm');
			}
		}

		this.sunriseSunsetTime = timeString;
		this.sunriseSunsetIcon = (sunrise < now && sunset > now) ? "wi-sunset" : "wi-sunrise";

		this.loaded = true;
		this.updateDom(this.config.animationSpeed);
	},

	/* scheduleUpdate()
	 * Schedule next update.
	 *
	 * argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
	 */
	scheduleUpdate: function(delay) {
		var nextLoad = this.config.updateInterval;
		if (typeof delay !== "undefined" && delay >= 0) {
			nextLoad = delay;
		}

		var self = this;
		setTimeout(function() {
			self.updateWeather();
		}, nextLoad);
	},

	/* ms2Beaufort(ms)
	 * Converts m2 to beaufort (windspeed).
	 *
	 * argument ms number - Windspeed in m/s.
	 *
	 * return number - Windspeed in beaufort.
	 */
	ms2Beaufort: function(ms) {
		var kmh = ms * 60 * 60 / 1000;
		var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
		for (var beaufort in speeds) {
			var speed = speeds[beaufort];
			if (speed > kmh) {
				return beaufort;
			}
		}
		return 12;
	},

	/* function(temperature)
	 * Rounds a temperature to 1 decimal.
	 *
	 * argument temperature number - Temperature.
	 *
	 * return number - Rounded Temperature.
	 */

	deg2Cardinal: function(deg) {
                if (deg>11.25 && deg<33.75){
                        return "NNE";
                }else if (deg>33.75 && deg<56.25){
                        return "ENE";
                }else if (deg>56.25 && deg<78.75){
                        return "E";
                }else if (deg>78.75 && deg<101.25){
                        return "ESE";
                }else if (deg>101.25 && deg<123.75){
                        return "ESE";
                }else if (deg>123.75 && deg<146.25){
                        return "SE";
                }else if (deg>146.25 && deg<168.75){
                        return "SSE";
                }else if (deg>168.75 && deg<191.25){
                        return "S";
                }else if (deg>191.25 && deg<213.75){
                        return "SSW";
                }else if (deg>213.75 && deg<236.25){
                        return "SW";
                }else if (deg>236.25 && deg<258.75){
                        return "WSW";
                }else if (deg>258.75 && deg<281.25){
                        return "W";
                }else if (deg>281.25 && deg<303.75){
                        return "WNW";
                }else if (deg>303.75 && deg<326.25){
                        return "NW";
                }else if (deg>326.25 && deg<348.75){
                        return "NNW";
                }else{
                         return "N";
                }
	}

	roundValue: function(temperature) {
		return parseFloat(temperature).toFixed(1);
	}
});
<|endoftext|>


<commit_before>// todomvc
var Espresso    = window.Espresso
var Model       = Espresso.Model
var Collection  = Espresso.Collection
var Controller  = Espresso.Controller
var List        = Espresso.List
var extend      = Espresso.extend
var ENTER_KEY   = 13;
var ESC_KEY     = 27;

var ToDoStore = extend(Collection, {
    init: function() {
        this.clearCompleted = this.clearCompleted.bind(this);
        this.add = this.add.bind(this);
        this.reset(JSON.parse(localStorage.getItem('todo')));
        this.addListener('change', this.save.bind(this));
    },
    add: function(txt) {
        this.push({ done: false, id: this.count(), text: txt })
    },
    toggle: function(id, done) {
        // var index = this.findIndex({ id: id });
        // var item = this.get(index);
        // this.set(index, { done: done, id: item.id, text: item.text })
        this.set({ id: id, done: done })
    },
    toggleAll: function(done) {
        this.forEach(function(v, i) {
            if (v.done !== done) {
                this.set(i, { done: done, text: v.text, id: v.id })
            }
        }.bind(this))
    },
    clearCompleted: function() {
        this.set(this.active());
    },
    completed: function() {
        return this.filter(function(v) { return v.done });
    },
    active: function() {
        return this.filter(function(v) { return !v.done });
    },
    all: function() {
        return this.toArray();
    },
    save: function() {
        localStorage.setItem('todo', JSON.stringify(this.toArray()));
    }
});

var ToDoItem = extend(Controller, {
    init: function() {
        this.listenTo(window, 'click', function(e) {
            if (this.model.editing && e.target !== this.ref.input) {
                this.set({ editing: false });
            }
        });
    },
    edit: function() {
        this.ref.input.focus();
        this.set({ editing: true });
    },
    destroy: function() {
        store.remove({ id: this.model.id });
    },
    key: function(e) {
        if (e.which === ENTER_KEY) this.set({ editing: false, text: e.target.value });
        else if (e.which === ESC_KEY) {
            this.set({ editing: false });
            e.target.value = this.model.text;
        }
    },
    toggle: function(e) {
        store.toggle(this.model.id, e.target.checked);
    },
    render: function() {
        return {
            view: { 'class': { editing: this.model.editing, completed: this.model.done } },
            label: { ondblclick: this.edit, text: this.model.text },
            destroy: { onclick: this.destroy },
            input: { value: this.model.text, onkeydown: this.key },
            toggle: { onclick: this.toggle, checked: this.model.done }
        }
    }
});

var App = extend(Controller, {
    init: function() {
        this.model.filter = window.location.hash.replace('#/', '') || 'all';
        if (this.model.filter === 'active') this.filter({ target: this.ref.active });
        if (this.model.filter === 'completed') this.filter({ target: this.ref.completed });

        this.list = new List(ToDoItem);
        this.listenTo(store, 'change', this.render);
    },
    filter: function(e) {
        if (e === undefined) return
        if (e.target.nodeName === 'A') {
            document.querySelector('a.selected').classList.remove('selected');
            e.target.classList.add('selected')
            this.model.set({ filter: e.target.innerHTML.toLowerCase() })
        }
    },
    addItem: function(e) {
        if (e.which !== ENTER_KEY) return;
        store.add(this.ref.newItem.value)
        this.ref.newItem.value = '';
    },
    toggleAll: function(e) {
        store.toggleAll(e.target.checked);
    },
    clearText: function() {
        var x = store.completed().length;
        return 'Clear completed (' + x + ')'
    },
    render: function() {
        return {
            list: this.list.set(store[this.model.filter]()),
            filters: { onclick: this.filter },
            footer: { display: store.count() > 0 },
            toggleAll: { onclick: this.toggleAll },
            newItem: { onkeypress: this.addItem },
            count: { html: ''+store.active().length+' items left' },
            clear: { text: this.clearText(), onclick: store.clearCompleted, display: store.completed().length > 0 }
        }
    }
});

var store = new ToDoStore([
  // { id: 0, text: 'write todomvc', done: false }
]);

window.app = new App({ view: document.getElementById('todoapp') });
<commit_msg>todomvc<commit_after>// todomvc
var Espresso    = window.Espresso
var Model       = Espresso.Model
var Collection  = Espresso.Collection
var Controller  = Espresso.Controller
var List        = Espresso.List
var extend      = Espresso.extend
var ENTER_KEY   = 13;
var ESC_KEY     = 27;

var ToDoStore = extend(Collection, {
    init: function() {
        this.clearCompleted = this.clearCompleted.bind(this);
        this.add = this.add.bind(this);
        this.reset(JSON.parse(localStorage.getItem('todo')));
        this.addListener('change', this.save.bind(this));
    },
    add: function(txt) {
        this.push({ done: false, id: this.count(), text: txt })
    },
    toggle: function(id, done) {
        this.set({ id: id, done: done })
    },
    toggleAll: function(done) {
        this.forEach(function(v, i) {
            if (v.done !== done) {
                this.set(i, { done: done, text: v.text, id: v.id })
            }
        }.bind(this))
    },
    clearCompleted: function() {
        this.set(this.active());
    },
    completed: function() {
        return this.filter(function(v) { return v.done });
    },
    active: function() {
        return this.filter(function(v) { return !v.done });
    },
    all: function() {
        return this.toArray();
    },
    save: function() {
        localStorage.setItem('todo', JSON.stringify(this.toArray()));
    }
});

var ToDoItem = extend(Controller, {
    init: function() {
        this.listenTo(window, 'click', function(e) {
            if (this.model.editing && e.target !== this.ref.input) {
                this.set({ editing: false });
            }
        });
    },
    edit: function() {
        this.ref.input.focus();
        this.set({ editing: true });
    },
    destroy: function() {
        store.remove({ id: this.model.id });
    },
    key: function(e) {
        if (e.which === ENTER_KEY) this.set({ editing: false, text: e.target.value });
        else if (e.which === ESC_KEY) {
            this.set({ editing: false });
            e.target.value = this.model.text;
        }
    },
    toggle: function(e) {
        store.toggle(this.model.id, e.target.checked);
    },
    render: function() {
        return {
            view: { 'class': { editing: this.model.editing, completed: this.model.done } },
            label: { ondblclick: this.edit, text: this.model.text },
            destroy: { onclick: this.destroy },
            input: { value: this.model.text, onkeydown: this.key },
            toggle: { onclick: this.toggle, checked: this.model.done }
        }
    }
});

var App = extend(Controller, {
    init: function() {
        this.model.filter = window.location.hash.replace('#/', '') || 'all';
        if (this.model.filter === 'active') this.filter({ target: this.ref.active });
        if (this.model.filter === 'completed') this.filter({ target: this.ref.completed });

        this.list = new List(ToDoItem);
        this.listenTo(store, 'change', this.render);
    },
    filter: function(e) {
        if (e === undefined) return
        if (e.target.nodeName === 'A') {
            document.querySelector('a.selected').classList.remove('selected');
            e.target.classList.add('selected')
            this.model.set({ filter: e.target.innerHTML.toLowerCase() })
        }
    },
    addItem: function(e) {
        if (e.which !== ENTER_KEY) return;
        store.add(this.ref.newItem.value)
        this.ref.newItem.value = '';
    },
    toggleAll: function(e) {
        store.toggleAll(e.target.checked);
    },
    clearText: function() {
        var x = store.completed().length;
        return 'Clear completed (' + x + ')'
    },
    render: function() {
        return {
            list: this.list.set(store[this.model.filter]()),
            filters: { onclick: this.filter },
            footer: { display: store.count() > 0 },
            toggleAll: { onclick: this.toggleAll },
            newItem: { onkeypress: this.addItem },
            count: { html: ''+store.active().length+' items left' },
            clear: { text: this.clearText(), onclick: store.clearCompleted, display: store.completed().length > 0 }
        }
    }
});

var store = new ToDoStore([
  // { id: 0, text: 'write todomvc', done: false }
]);

window.app = new App({ view: document.getElementById('todoapp') });
<|endoftext|>


<commit_before>'use strict';

var gulp = require('gulp');

gulp.task('deploy', ['build'], function(done) {
  gulp.src('./dist/**/*.*')
    .pipe(gulp.dest('./../robeasthope.github.io'));
});
<commit_msg>Update deploy task<commit_after>'use strict';

var gulp = require('gulp');

gulp.task('deploy', ['build'], function(done) {
  gulp.src('./dist/**/*.*')
    .pipe(gulp.dest('./../'));
});
<|endoftext|>


<commit_before>import { select } from "d3-selection";
import { max as d3max, extent as d3extent } from "d3-array";
import { scaleSqrt } from "d3-scale";

import {
  forceSimulation,
  forceLink,
  forceManyBody,
  forceCenter,
  forceCollide,
  forceX
} from "d3-force";

import { Tooltip } from "./tooltip";

import "./style/style-network.scss";

const NODE_MODULES = /.*(?:\/|\\\\)?node_modules(?:\/|\\\\)([^/\\]+)(?:\/|\\\\).+/;

function color({ id }) {
  const match = id.match(NODE_MODULES);
  if (match) {
    return "#599e59";
  } else {
    return "#487ea4";
  }
}

const WIDTH = window.chartParameters.width || 1500;
const HEIGHT = window.chartParameters.height || 1000;

const chartNode = document.querySelector("main");
const { nodes: origNodes, links: origLinks } = window.nodesData;

const tooltip = new Tooltip(select(chartNode), {
  getNodeUid: d => d.uid,
  getNodePath: d => d.id,
  getNodeSize: d => d.size,
  nodes: origNodes,
  links: origLinks
});

const nodes = Object.entries(origNodes).map(([uid, node]) => ({
  uid,
  ...node
}));
const nodesCache = new Map(nodes.map(d => [d.uid, d]));
const links = origLinks.map(({ source, target }) => ({
  source: nodesCache.get(source),
  target: nodesCache.get(target),
  value: 1
}));

const maxLines = d3max(nodes, d => d.size);
const size = scaleSqrt()
  .domain([1, maxLines])
  .range([5, 30]);

const svg = select(chartNode)
  .append("svg")
  .attr("viewBox", [0, 0, WIDTH, HEIGHT]);

const simulation = forceSimulation()
  .force(
    "link",
    forceLink()
      .id(d => d.id)
      .strength(1)
      .distance(50)
      .iterations(10)
  )
  .force(
    "collide",
    forceCollide().radius(d => size(d.size) + 1)
  )
  .force("forceX", forceX(HEIGHT / 2).strength(0.05))
  .force("charge", forceManyBody().strength(-100))
  .force("center", forceCenter(WIDTH / 2, HEIGHT / 2));

simulation.nodes(nodes);
simulation.force("link").links(links);
simulation.stop();

for (let i = 0; i < 300; i++) simulation.tick();

const xExtent = d3extent(nodes, d => d.x);
const yExtent = d3extent(nodes, d => d.y);

const xRange = xExtent[1] - xExtent[0];
const yRange = yExtent[1] - yExtent[0];

if (yRange > xRange) {
  nodes.forEach(d => {
    const y = parseFloat(d.y);
    d.y = parseFloat(d.x);
    d.x = y;
  });
}

//TODO by idea i can move it to the center if it is required

svg
  .append("g")
  .attr("stroke", "#999")
  .attr("stroke-opacity", 0.6)
  .selectAll("line")
  .data(links)
  .join("line")
  .attr("stroke-width", 1)
  // set correct positions
  .attr("x1", d => d.source.x)
  .attr("y1", d => d.source.y)
  .attr("x2", d => d.target.x)
  .attr("y2", d => d.target.y);

svg
  .append("g")
  .attr("stroke", "#fff")
  .attr("stroke-width", 1.5)
  .selectAll("circle")
  .data(nodes)
  .join("circle")
  .attr("r", d => size(d.size))
  .attr("fill", d => (d.size === 0 ? "#ccc" : color(d)))
  .attr("cx", d => d.x)
  .attr("cy", d => d.y)
  .on("mouseover", tooltip.onMouseOver)
  .on("mousemove", tooltip.onMouseMove)
  .on("mouseleave", tooltip.onMouseLeave);

tooltip.buildCache(svg.selectAll("circle"));
<commit_msg>Center network graph<commit_after>import { select } from "d3-selection";
import { max as d3max, extent as d3extent } from "d3-array";
import { scaleSqrt } from "d3-scale";

import {
  forceSimulation,
  forceLink,
  forceManyBody,
  forceCenter,
  forceCollide,
  forceX
} from "d3-force";

import { Tooltip } from "./tooltip";

import "./style/style-network.scss";

const NODE_MODULES = /.*(?:\/|\\\\)?node_modules(?:\/|\\\\)([^/\\]+)(?:\/|\\\\).+/;

function color({ id }) {
  const match = id.match(NODE_MODULES);
  if (match) {
    return "#599e59";
  } else {
    return "#487ea4";
  }
}

const WIDTH = window.chartParameters.width || 1500;
const HEIGHT = window.chartParameters.height || 1000;

const chartNode = document.querySelector("main");
const { nodes: origNodes, links: origLinks } = window.nodesData;

const tooltip = new Tooltip(select(chartNode), {
  getNodeUid: d => d.uid,
  getNodePath: d => d.id,
  getNodeSize: d => d.size,
  nodes: origNodes,
  links: origLinks
});

const nodes = Object.entries(origNodes).map(([uid, node]) => ({
  uid,
  ...node
}));
const nodesCache = new Map(nodes.map(d => [d.uid, d]));
const links = origLinks.map(({ source, target }) => ({
  source: nodesCache.get(source),
  target: nodesCache.get(target),
  value: 1
}));

const maxLines = d3max(nodes, d => d.size);
const size = scaleSqrt()
  .domain([1, maxLines])
  .range([5, 30]);

const svg = select(chartNode)
  .append("svg")
  .attr("viewBox", [0, 0, WIDTH, HEIGHT]);

const simulation = forceSimulation()
  .force(
    "link",
    forceLink()
      .id(d => d.id)
      .strength(1)
      .distance(50)
      .iterations(10)
  )
  .force(
    "collide",
    forceCollide().radius(d => size(d.size) + 1)
  )
  .force("forceX", forceX(HEIGHT / 2).strength(0.05))
  .force("charge", forceManyBody().strength(-100))
  .force("center", forceCenter(WIDTH / 2, HEIGHT / 2));

simulation.nodes(nodes);
simulation.force("link").links(links);
simulation.stop();

for (let i = 0; i < 300; i++) simulation.tick();

let xExtent = d3extent(nodes, d => d.x);
let yExtent = d3extent(nodes, d => d.y);

const xRange = xExtent[1] - xExtent[0];
const yRange = yExtent[1] - yExtent[0];

//rotate
if (yRange > xRange) {
  nodes.forEach(d => {
    const y = parseFloat(d.y);
    d.y = parseFloat(d.x);
    d.x = y;
  });

  const t = yExtent;
  yExtent = xExtent;
  xExtent = t;
}

//center
const xCenter = (xExtent[1] - xExtent[0]) / 2 + xExtent[0];
const yCenter = (yExtent[1] - yExtent[0]) / 2 + yExtent[0];

const svgXCenter = WIDTH / 2;
const svgYCenter = HEIGHT / 2;

const xCenterDiff = svgXCenter - xCenter;
const yCenterDiff = svgYCenter - yCenter;

nodes.forEach(d => {
  d.y += yCenterDiff;
  d.x += xCenterDiff;
});

svg
  .append("g")
  .attr("stroke", "#999")
  .attr("stroke-opacity", 0.6)
  .selectAll("line")
  .data(links)
  .join("line")
  .attr("stroke-width", 1)
  // set correct positions
  .attr("x1", d => d.source.x)
  .attr("y1", d => d.source.y)
  .attr("x2", d => d.target.x)
  .attr("y2", d => d.target.y);

svg
  .append("g")
  .attr("stroke", "#fff")
  .attr("stroke-width", 1.5)
  .selectAll("circle")
  .data(nodes)
  .join("circle")
  .attr("r", d => size(d.size))
  .attr("fill", d => (d.size === 0 ? "#ccc" : color(d)))
  .attr("cx", d => d.x)
  .attr("cy", d => d.y)
  .on("mouseover", tooltip.onMouseOver)
  .on("mousemove", tooltip.onMouseMove)
  .on("mouseleave", tooltip.onMouseLeave);

tooltip.buildCache(svg.selectAll("circle"));
<|endoftext|>


<commit_before>// Full spec-compliant TodoMVC with localStorage persistence
// and hash-based routing in ~150 lines.

// localStorage persistence
var STORAGE_KEY = 'todos-vuejs-2.0'
var todoStorage = {
  fetch: function () {
    var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
    todos.forEach(function (todo, index) {
      todo.id = index
    })
    todoStorage.uid = todos.length
    return todos
  },
  save: function (todos) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
  }
}

// visibility filters
var filters = {
  all: function (todos) {
    return todos
  },
  active: function (todos) {
    return todos.filter(function (todo) {
      return !todo.completed
    })
  },
  completed: function (todos) {
    return todos.filter(function (todo) {
      return todo.completed
    })
  }
}

// app Vue instance
var app = new Vue({
  // app initial state
  data: {
    todos: todoStorage.fetch(),
    newTodo: '',
    editedTodo: null,
    visibility: 'all'
  },

  // watch todos change for localStorage persistence
  watch: {
    todos: {
      handler: function (todos) {
        todoStorage.save(todos)
      },
      deep: true
    }
  },

  // computed properties
  // https://vuejs.org/guide/computed.html
  computed: {
    filteredTodos: function () {
      return filters[this.visibility](this.todos)
    },
    remaining: function () {
      return filters.active(this.todos).length
    },
    allDone: {
      get: function () {
        return this.remaining === 0
      },
      set: function (value) {
        this.todos.forEach(function (todo) {
          todo.completed = value
        })
      }
    }
  },

  filters: {
    pluralize: function (n) {
      return n === 1 ? 'item' : 'items'
    }
  },

  // methods that implement data logic.
  // note there's no DOM manipulation here at all.
  methods: {
    addTodo: function () {
      var value = this.newTodo && this.newTodo.trim()
      if (!value) {
        return
      }
      this.todos.push({
        id: todoStorage.uid++,
        title: value,
        completed: false
      })
      this.newTodo = ''
    },

    removeTodo: function (todo) {
      this.todos.splice(this.todos.indexOf(todo), 1)
    },

    editTodo: function (todo) {
      this.beforeEditCache = todo.title
      this.editedTodo = todo
    },

    doneEdit: function (todo) {
      if (!this.editedTodo) {
        return
      }
      this.editedTodo = null
      todo.title = todo.title.trim()
      if (!todo.title) {
        this.removeTodo(todo)
      }
    },

    cancelEdit: function (todo) {
      this.editedTodo = null
      todo.title = this.beforeEditCache
    },

    removeCompleted: function () {
      this.todos = filters.active(this.todos)
    }
  },

  // a custom directive to wait for the DOM to be updated
  // before focusing on the input field.
  // https://vuejs.org/guide/custom-directive.html
  directives: {
    'todo-focus': function (el, value) {
      if (value) {
        el.focus()
      }
    }
  }
})

// handle routing
function onHashChange () {
  var visibility = window.location.hash.replace(/#\/?/, '')
  if (filters[visibility]) {
    app.visibility = visibility
  } else {
    window.location.hash = ''
    app.visibility = 'all'
  }
}

window.addEventListener('hashchange', onHashChange)
onHashChange()

// mount
app.$mount('.todoapp')
<commit_msg>chore: check binding.value instead of just binding object (#5065)<commit_after>// Full spec-compliant TodoMVC with localStorage persistence
// and hash-based routing in ~150 lines.

// localStorage persistence
var STORAGE_KEY = 'todos-vuejs-2.0'
var todoStorage = {
  fetch: function () {
    var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
    todos.forEach(function (todo, index) {
      todo.id = index
    })
    todoStorage.uid = todos.length
    return todos
  },
  save: function (todos) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
  }
}

// visibility filters
var filters = {
  all: function (todos) {
    return todos
  },
  active: function (todos) {
    return todos.filter(function (todo) {
      return !todo.completed
    })
  },
  completed: function (todos) {
    return todos.filter(function (todo) {
      return todo.completed
    })
  }
}

// app Vue instance
var app = new Vue({
  // app initial state
  data: {
    todos: todoStorage.fetch(),
    newTodo: '',
    editedTodo: null,
    visibility: 'all'
  },

  // watch todos change for localStorage persistence
  watch: {
    todos: {
      handler: function (todos) {
        todoStorage.save(todos)
      },
      deep: true
    }
  },

  // computed properties
  // https://vuejs.org/guide/computed.html
  computed: {
    filteredTodos: function () {
      return filters[this.visibility](this.todos)
    },
    remaining: function () {
      return filters.active(this.todos).length
    },
    allDone: {
      get: function () {
        return this.remaining === 0
      },
      set: function (value) {
        this.todos.forEach(function (todo) {
          todo.completed = value
        })
      }
    }
  },

  filters: {
    pluralize: function (n) {
      return n === 1 ? 'item' : 'items'
    }
  },

  // methods that implement data logic.
  // note there's no DOM manipulation here at all.
  methods: {
    addTodo: function () {
      var value = this.newTodo && this.newTodo.trim()
      if (!value) {
        return
      }
      this.todos.push({
        id: todoStorage.uid++,
        title: value,
        completed: false
      })
      this.newTodo = ''
    },

    removeTodo: function (todo) {
      this.todos.splice(this.todos.indexOf(todo), 1)
    },

    editTodo: function (todo) {
      this.beforeEditCache = todo.title
      this.editedTodo = todo
    },

    doneEdit: function (todo) {
      if (!this.editedTodo) {
        return
      }
      this.editedTodo = null
      todo.title = todo.title.trim()
      if (!todo.title) {
        this.removeTodo(todo)
      }
    },

    cancelEdit: function (todo) {
      this.editedTodo = null
      todo.title = this.beforeEditCache
    },

    removeCompleted: function () {
      this.todos = filters.active(this.todos)
    }
  },

  // a custom directive to wait for the DOM to be updated
  // before focusing on the input field.
  // https://vuejs.org/guide/custom-directive.html
  directives: {
    'todo-focus': function (el, binding) {
      if (binding.value) {
        el.focus()
      }
    }
  }
})

// handle routing
function onHashChange () {
  var visibility = window.location.hash.replace(/#\/?/, '')
  if (filters[visibility]) {
    app.visibility = visibility
  } else {
    window.location.hash = ''
    app.visibility = 'all'
  }
}

window.addEventListener('hashchange', onHashChange)
onHashChange()

// mount
app.$mount('.todoapp')
<|endoftext|>


<commit_before>// @flow

import React from 'react'
import cx from 'classnames'
import {connect} from 'react-redux'
import {Link} from '@reach/router'
import {List, Map} from 'immutable'
import {countCredits} from '@gob/examine-student'
import {semesterName} from '@gob/school-st-olaf-college'
import {DropTarget} from 'react-dnd'
import * as theme from '../../theme'
import {FlatButton} from '../../components/button'
import {Icon} from '../../components/icon'
import {InlineList, InlineListItem} from '../../components/list'
import {close, search} from '../../icons/ionicons'
import {
	IDENT_COURSE,
	Student,
	Schedule,
	type WarningType,
} from '@gob/object-student'
import type {Course as CourseType} from '@gob/types'
import {getOnlyCourse} from '../../helpers/get-courses'
import {
	changeStudent,
	type ChangeStudentFunc,
} from '../../redux/students/actions/change'
import {CourseList} from './course-list'
import styled from 'styled-components'

const Container = styled.div`
	${theme.card};
	flex: 1 0;
	min-width: 16em;
	margin: var(--semester-spacing);

	&.can-drop {
		cursor: copy;
		box-shadow: 0 0 4px var(--gray-500);
		z-index: 10;
	}
`

const TitleButton = styled(FlatButton)`
	padding: var(--block-edge-padding) var(--semester-side-padding);
	min-height: 0;
	font-size: 0.9em;

	border: 0;
	border-radius: 0;
	transition: 0.15s;

	& + & {
		margin-left: 0.1em;
	}
`

const RemoveSemesterButton = styled(TitleButton)`
	&:hover {
		color: var(--red-500);
		border-color: var(--red-500);
		background-color: var(--red-50);
	}
`

const Header = styled.header`
	border-bottom: ${theme.materialDivider};

	font-size: 0.85em;

	display: flex;
	flex-flow: row nowrap;
	align-items: stretch;
	border-top-right-radius: 2px;
	border-top-left-radius: 2px;
	color: var(--gray-500);

	overflow: hidden;
`

const InfoList = styled(InlineList)`
	font-size: 0.8em;
`

const InfoItem = styled(InlineListItem)`
	font-variant-numeric: oldstyle-nums;

	& + &::before {
		content: ' – ';
		padding-left: 0.25em;
	}
`

const Title = styled(Link)`
	${theme.linkUndecorated};
	flex: 1;
	display: flex;
	flex-direction: column;
	padding: var(--block-edge-padding) var(--semester-side-padding);

	&:hover {
		text-decoration: underline;
	}
`

const TitleText = styled.h1`
	${theme.headingNeutral};
	display: inline-block;
	color: black;
`

type DnDProps = {
	canDrop?: boolean,
	connectDropTarget: Function,
	isOver: boolean,
}

type ReduxProps = {
	changeStudent: ChangeStudentFunc,
}

type ReactProps = {
	schedule: Schedule,
	semester: number,
	student: Student,
	year: number,
}

type Props = ReduxProps & DnDProps & ReactProps

type State = {
	loading: boolean,
	checking: boolean,
	courses: List<CourseType>,
	warnings: Map<string, List<?WarningType>>,
	hasConflict: boolean,
	credits: number,
}

class Semester extends React.Component<Props, State> {
	state = {
		loading: true,
		checking: true,
		courses: List(),
		warnings: Map(),
		hasConflict: false,
		credits: 0,
	}

	componentDidMount() {
		this.prepare()
	}

	prepare = async () => {
		this.setState(() => ({loading: true, checking: true}))

		let {schedule} = this.props
		let courses = await schedule.getOnlyCourses(getOnlyCourse)
		let credits = countCredits([...courses])

		this.setState(() => ({courses, credits, loading: false}))

		let {warnings, hasConflict} = await schedule.validate(getOnlyCourse)

		this.setState(() => ({warnings, hasConflict, checking: false}))
	}

	removeSemester = () => {
		const {student, semester, year} = this.props
		let s = student.destroySchedulesForTerm({year, semester})
		this.props.changeStudent(s)
	}

	render() {
		let props = this.props
		let {student, semester, year, canDrop} = props

		let schedule = this.props.schedule
		let {courses, credits, warnings, hasConflict} = this.state

		let {recommendedCredits} = schedule
		let creditsPerCourse = 1
		let recommendedSlots = creditsPerCourse * recommendedCredits

		const infoBar = []
		if (courses.size) {
			// prettier-ignore
			infoBar.push(`${courses.size} ${courses.size === 1 ? 'course' : 'courses'}`)

			// prettier-ignore
			infoBar.push(`${credits} ${credits === 1 ? 'credit' : 'credits'}`)
		}

		const className = cx('semester', {
			invalid: hasConflict,
			'can-drop': canDrop,
		})

		let name = semesterName(semester)

		return (
			<Container
				className={className}
				ref={ref => props.connectDropTarget(ref)}
			>
				<Header>
					<Title
						to={`./term/${year}${semester}`}
						title={`Details for ${name}`}
					>
						<TitleText>{name}</TitleText>
						<InfoList>
							{infoBar.map(item => (
								<InfoItem key={item}>{item}</InfoItem>
							))}
						</InfoList>
					</Title>

					<TitleButton
						as={Link}
						to={`./search?term=${year}${semester}`}
						title="Search for courses"
					>
						<Icon>{search}</Icon> Course
					</TitleButton>

					<RemoveSemesterButton
						onClick={this.removeSemester}
						title={`Remove ${year} ${name}`}
					>
						<Icon>{close}</Icon>
					</RemoveSemesterButton>
				</Header>

				{schedule && (
					<CourseList
						courses={[...courses]}
						usedSlots={credits / creditsPerCourse}
						maxSlots={recommendedSlots / creditsPerCourse}
						warnings={warnings}
						scheduleId={schedule.id}
						studentId={student.id}
					/>
				)}
			</Container>
		)
	}
}

// Implements the drag source contract.
const semesterTarget = {
	drop(props: ReactProps & ReduxProps, monitor) {
		let {clbid, fromScheduleId, isFromSchedule} = monitor.getItem()
		let {student, schedule} = props

		if (isFromSchedule) {
			let s = student.moveCourseToSchedule({
				from: fromScheduleId,
				to: schedule.id,
				clbid,
			})
			props.changeStudent(s)
		} else {
			let s = student.addCourseToSchedule(schedule.id, clbid)
			props.changeStudent(s)
		}
	},
	canDrop(props: ReactProps, monitor) {
		let item = monitor.getItem()
		let hasClbid = props.schedule.clbids.includes(item.clbid)
		return !hasClbid
	},
}

// Specifies the props to inject into your component.
function collect(connect, monitor) {
	return {
		connectDropTarget: connect.dropTarget(),
		isOver: monitor.isOver(),
		canDrop: monitor.canDrop(),
	}
}

const droppable = DropTarget(IDENT_COURSE, semesterTarget, collect)(Semester)

const connected = connect(
	undefined,
	{changeStudent},
)(droppable)

export default connected
<commit_msg>fix Semester to fetch courses when they change<commit_after>// @flow

import React from 'react'
import cx from 'classnames'
import {connect} from 'react-redux'
import {Link} from '@reach/router'
import {List, Map} from 'immutable'
import {countCredits} from '@gob/examine-student'
import {semesterName} from '@gob/school-st-olaf-college'
import {DropTarget} from 'react-dnd'
import * as theme from '../../theme'
import {FlatButton} from '../../components/button'
import {Icon} from '../../components/icon'
import {InlineList, InlineListItem} from '../../components/list'
import {close, search} from '../../icons/ionicons'
import {
	IDENT_COURSE,
	Student,
	Schedule,
	type WarningType,
} from '@gob/object-student'
import type {Course as CourseType} from '@gob/types'
import {getOnlyCourse} from '../../helpers/get-courses'
import {
	changeStudent,
	type ChangeStudentFunc,
} from '../../redux/students/actions/change'
import {CourseList} from './course-list'
import styled from 'styled-components'

const Container = styled.div`
	${theme.card};
	flex: 1 0;
	min-width: 16em;
	margin: var(--semester-spacing);

	&.can-drop {
		cursor: copy;
		box-shadow: 0 0 4px var(--gray-500);
		z-index: 10;
	}

	&.loading {
	}
`

const TitleButton = styled(FlatButton)`
	padding: var(--block-edge-padding) var(--semester-side-padding);
	min-height: 0;
	font-size: 0.9em;

	border: 0;
	border-radius: 0;
	transition: 0.15s;

	& + & {
		margin-left: 0.1em;
	}
`

const RemoveSemesterButton = styled(TitleButton)`
	&:hover {
		color: var(--red-500);
		border-color: var(--red-500);
		background-color: var(--red-50);
	}
`

const Header = styled.header`
	border-bottom: ${theme.materialDivider};

	font-size: 0.85em;

	display: flex;
	flex-flow: row nowrap;
	align-items: stretch;
	border-top-right-radius: 2px;
	border-top-left-radius: 2px;
	color: var(--gray-500);

	overflow: hidden;
`

const InfoList = styled(InlineList)`
	font-size: 0.8em;
`

const InfoItem = styled(InlineListItem)`
	font-variant-numeric: oldstyle-nums;

	& + &::before {
		content: ' – ';
		padding-left: 0.25em;
	}
`

const Title = styled(Link)`
	${theme.linkUndecorated};
	flex: 1;
	display: flex;
	flex-direction: column;
	padding: var(--block-edge-padding) var(--semester-side-padding);

	&:hover {
		text-decoration: underline;
	}
`

const TitleText = styled.h1`
	${theme.headingNeutral};
	display: inline-block;
	color: black;
`

type DnDProps = {
	canDrop?: boolean,
	connectDropTarget: Function,
	isOver: boolean,
}

type ReduxProps = {
	changeStudent: ChangeStudentFunc,
}

type ReactProps = {
	schedule: Schedule,
	semester: number,
	student: Student,
	year: number,
}

type Props = ReduxProps & DnDProps & ReactProps

type State = {
	loading: boolean,
	checking: boolean,
	courses: List<CourseType>,
	warnings: Map<string, List<?WarningType>>,
	hasConflict: boolean,
	credits: number,
}

class Semester extends React.Component<Props, State> {
	state = {
		loading: true,
		checking: true,
		courses: List(),
		warnings: Map(),
		hasConflict: false,
		credits: 0,
	}

	componentDidMount() {
		this.prepare(this.props)
	}

	componentDidUpdate(prevProps: Props) {
		if (this.props.schedule !== prevProps.schedule) {
			this.prepare(this.props)
		}
	}

	prepare = async props => {
		this.setState(() => ({loading: true, checking: true}))

		let {schedule} = props
		let courses = await schedule.getOnlyCourses(getOnlyCourse)
		let credits = countCredits([...courses])

		this.setState(() => ({courses, credits, loading: false}))

		let {warnings, hasConflict} = await schedule.validate(getOnlyCourse)

		this.setState(() => ({warnings, hasConflict, checking: false}))
	}

	removeSemester = () => {
		const {student, semester, year} = this.props
		let s = student.destroySchedulesForTerm({year, semester})
		this.props.changeStudent(s)
	}

	render() {
		let props = this.props
		let {student, semester, year, canDrop} = props

		let schedule = this.props.schedule
		let {courses, credits, warnings, hasConflict, loading} = this.state

		let {recommendedCredits} = schedule
		let creditsPerCourse = 1
		let recommendedSlots = creditsPerCourse * recommendedCredits

		const infoBar = []
		if (courses.size) {
			// prettier-ignore
			infoBar.push(`${courses.size} ${courses.size === 1 ? 'course' : 'courses'}`)

			// prettier-ignore
			infoBar.push(`${credits} ${credits === 1 ? 'credit' : 'credits'}`)
		}

		if (loading) {
			infoBar.unshift('Loading…')
		}

		const className = cx('semester', {
			invalid: hasConflict,
			'can-drop': canDrop,
			loading: loading,
		})

		let name = semesterName(semester)

		return (
			<Container
				className={className}
				ref={ref => props.connectDropTarget(ref)}
			>
				<Header>
					<Title
						to={`./term/${year}${semester}`}
						title={`Details for ${name}`}
					>
						<TitleText>{name}</TitleText>
						<InfoList>
							{infoBar.map(item => (
								<InfoItem key={item}>{item}</InfoItem>
							))}
						</InfoList>
					</Title>

					<TitleButton
						as={Link}
						to={`./search?term=${year}${semester}`}
						title="Search for courses"
					>
						<Icon>{search}</Icon> Course
					</TitleButton>

					<RemoveSemesterButton
						onClick={this.removeSemester}
						title={`Remove ${year} ${name}`}
					>
						<Icon>{close}</Icon>
					</RemoveSemesterButton>
				</Header>

				{schedule && (
					<CourseList
						courses={[...courses]}
						usedSlots={credits / creditsPerCourse}
						maxSlots={recommendedSlots / creditsPerCourse}
						warnings={warnings}
						scheduleId={schedule.id}
						studentId={student.id}
					/>
				)}
			</Container>
		)
	}
}

// Implements the drag source contract.
const semesterTarget = {
	drop(props: ReactProps & ReduxProps, monitor) {
		let {clbid, fromScheduleId, isFromSchedule} = monitor.getItem()
		let {student, schedule} = props

		if (isFromSchedule) {
			let s = student.moveCourseToSchedule({
				from: fromScheduleId,
				to: schedule.id,
				clbid,
			})
			props.changeStudent(s)
		} else {
			let s = student.addCourseToSchedule(schedule.id, clbid)
			props.changeStudent(s)
		}
	},
	canDrop(props: ReactProps, monitor) {
		let item = monitor.getItem()
		let hasClbid = props.schedule.clbids.includes(item.clbid)
		return !hasClbid
	},
}

// Specifies the props to inject into your component.
function collect(connect, monitor) {
	return {
		connectDropTarget: connect.dropTarget(),
		isOver: monitor.isOver(),
		canDrop: monitor.canDrop(),
	}
}

const droppable = DropTarget(IDENT_COURSE, semesterTarget, collect)(Semester)

const connected = connect(
	undefined,
	{changeStudent},
)(droppable)

export default connected
<|endoftext|>


<commit_before>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

const gulp = require('gulp');
const filter = require('gulp-filter');
const es = require('event-stream');
const gulptslint = require('gulp-tslint');
const gulpeslint = require('gulp-eslint');
const tsfmt = require('typescript-formatter');
const tslint = require('tslint');
const VinylFile = require('vinyl');
const vfs = require('vinyl-fs');
const path = require('path');
const fs = require('fs');
const pall = require('p-all');

/**
 * Hygiene works by creating cascading subsets of all our files and
 * passing them through a sequence of checks. Here are the current subsets,
 * named according to the checks performed on them. Each subset contains
 * the following one, as described in mathematical notation:
 *
 * all ⊃ eol ⊇ indentation ⊃ copyright ⊃ typescript
 */

const all = [
	'*',
	'build/**/*',
	'extensions/**/*',
	'scripts/**/*',
	'src/**/*',
	'test/**/*',
	'!**/node_modules/**'
];

const indentationFilter = [
	'**',

	// except specific files
	'!ThirdPartyNotices.txt',
	'!LICENSE.txt',
	'!src/vs/nls.js',
	'!src/vs/css.js',
	'!src/vs/loader.js',
	'!src/vs/base/common/marked/marked.js',
	'!src/vs/base/common/winjs.base.js',
	'!src/vs/base/node/terminateProcess.sh',
	'!test/assert.js',

	// except specific folders
	'!test/smoke/out/**',
	'!extensions/vscode-api-tests/testWorkspace/**',
	'!extensions/vscode-api-tests/testWorkspace2/**',
	'!build/monaco/**',
	'!build/win32/**',

	// except multiple specific files
	'!**/package.json',
	'!**/yarn.lock',
	'!**/yarn-error.log',

	// except multiple specific folders
	'!**/octicons/**',
	'!**/fixtures/**',
	'!**/lib/**',
	'!extensions/**/out/**',
	'!extensions/**/snippets/**',
	'!extensions/**/syntaxes/**',
	'!extensions/**/themes/**',
	'!extensions/**/colorize-fixtures/**',

	// except specific file types
	'!src/vs/*/**/*.d.ts',
	'!src/typings/**/*.d.ts',
	'!extensions/**/*.d.ts',
	'!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe}',
	'!build/{lib,tslintRules}/**/*.js',
	'!build/**/*.sh',
	'!build/tfs/**/*.js',
	'!**/Dockerfile',
	'!extensions/markdown-language-features/media/*.js'
];

const copyrightFilter = [
	'**',
	'!**/*.desktop',
	'!**/*.json',
	'!**/*.html',
	'!**/*.template',
	'!**/*.md',
	'!**/*.bat',
	'!**/*.cmd',
	'!**/*.xml',
	'!**/*.sh',
	'!**/*.txt',
	'!**/*.xpm',
	'!**/*.opts',
	'!**/*.disabled',
	'!**/*.code-workspace',
	'!build/**/*.init',
	'!resources/linux/snap/snapcraft.yaml',
	'!resources/win32/bin/code.js',
	'!extensions/markdown-language-features/media/tomorrow.css',
	'!extensions/html-language-features/server/src/modes/typescript/*',
	'!extensions/*/server/bin/*'
];

const eslintFilter = [
	'src/**/*.js',
	'build/gulpfile.*.js',
	'!src/vs/loader.js',
	'!src/vs/css.js',
	'!src/vs/nls.js',
	'!src/vs/css.build.js',
	'!src/vs/nls.build.js',
	'!src/**/winjs.base.js',
	'!src/**/marked.js',
	'!**/test/**'
];

const tslintFilter = [
	'src/**/*.ts',
	'test/**/*.ts',
	'extensions/**/*.ts',
	'!**/fixtures/**',
	'!**/typings/**',
	'!**/node_modules/**',
	'!extensions/typescript/test/colorize-fixtures/**',
	'!extensions/vscode-api-tests/testWorkspace/**',
	'!extensions/vscode-api-tests/testWorkspace2/**',
	'!extensions/**/*.test.ts',
	'!extensions/html-language-features/server/lib/jquery.d.ts'
];

const copyrightHeaderLines = [
	'/*---------------------------------------------------------------------------------------------',
	' *  Copyright (c) Microsoft Corporation. All rights reserved.',
	' *  Licensed under the MIT License. See License.txt in the project root for license information.',
	' *--------------------------------------------------------------------------------------------*/'
];

gulp.task('eslint', () => {
	return vfs.src(all, { base: '.', follow: true, allowEmpty: true })
		.pipe(filter(eslintFilter))
		.pipe(gulpeslint('src/.eslintrc'))
		.pipe(gulpeslint.formatEach('compact'))
		.pipe(gulpeslint.failAfterError());
});

gulp.task('tslint', () => {
	const options = { emitError: true };

	return vfs.src(all, { base: '.', follow: true, allowEmpty: true })
		.pipe(filter(tslintFilter))
		.pipe(gulptslint.default({ rulesDirectory: 'build/lib/tslint' }))
		.pipe(gulptslint.default.report(options));
});

function hygiene(some) {
	let errorCount = 0;

	const indentation = es.through(function (file) {
		const lines = file.contents.toString('utf8').split(/\r\n|\r|\n/);
		file.__lines = lines;

		lines
			.forEach((line, i) => {
				if (/^\s*$/.test(line)) {
					// empty or whitespace lines are OK
				} else if (/^[\t]*[^\s]/.test(line)) {
					// good indent
				} else if (/^[\t]* \*/.test(line)) {
					// block comment using an extra space
				} else {
					console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation');
					errorCount++;
				}
			});

		this.emit('data', file);
	});

	const copyrights = es.through(function (file) {
		const lines = file.__lines;

		for (let i = 0; i < copyrightHeaderLines.length; i++) {
			if (lines[i] !== copyrightHeaderLines[i]) {
				console.error(file.relative + ': Missing or bad copyright statement');
				errorCount++;
				break;
			}
		}

		this.emit('data', file);
	});

	const formatting = es.map(function (file, cb) {
		tsfmt.processString(file.path, file.contents.toString('utf8'), {
			verify: false,
			tsfmt: true,
			// verbose: true,
			// keep checkJS happy
			editorconfig: undefined,
			replace: undefined,
			tsconfig: undefined,
			tsconfigFile: undefined,
			tslint: undefined,
			tslintFile: undefined,
			tsfmtFile: undefined,
			vscode: undefined,
			vscodeFile: undefined
		}).then(result => {
			let original = result.src.replace(/\r\n/gm, '\n');
			let formatted = result.dest.replace(/\r\n/gm, '\n');

			if (original !== formatted) {
				console.error('File not formatted:', file.relative);
				errorCount++;
			}
			cb(null, file);

		}, err => {
			cb(err);
		});
	});

	const tslintConfiguration = tslint.Configuration.findConfiguration('tslint.json', '.');
	const tslintOptions = { fix: false, formatter: 'json' };
	const tsLinter = new tslint.Linter(tslintOptions);

	const tsl = es.through(function (file) {
		const contents = file.contents.toString('utf8');
		tsLinter.lint(file.relative, contents, tslintConfiguration.results);
		this.emit('data', file);
	});

	let input;

	if (Array.isArray(some) || typeof some === 'string' || !some) {
		input = vfs.src(some || all, { base: '.', follow: true, allowEmpty: true });
	} else {
		input = some;
	}

	const result = input
		.pipe(filter(f => !f.stat.isDirectory()))
		.pipe(filter(indentationFilter))
		.pipe(indentation)
		.pipe(filter(copyrightFilter))
		.pipe(copyrights);

	const typescript = result
		.pipe(filter(tslintFilter))
		.pipe(formatting)
		.pipe(tsl);

	const javascript = result
		.pipe(filter(eslintFilter))
		.pipe(gulpeslint('src/.eslintrc'))
		.pipe(gulpeslint.formatEach('compact'))
		.pipe(gulpeslint.failAfterError());

	let count = 0;
	return es.merge(typescript, javascript)
		.pipe(es.through(function (data) {
			count++;
			if (process.env['TRAVIS'] && count % 10 === 0) {
				process.stdout.write('.');
			}
			this.emit('data', data);
		}, function () {
			process.stdout.write('\n');

			const tslintResult = tsLinter.getResult();
			if (tslintResult.failures.length > 0) {
				for (const failure of tslintResult.failures) {
					const name = failure.getFileName();
					const position = failure.getStartPosition();
					const line = position.getLineAndCharacter().line;
					const character = position.getLineAndCharacter().character;

					console.error(`${name}:${line + 1}:${character + 1}:${failure.getFailure()}`);
				}
				errorCount += tslintResult.failures.length;
			}

			if (errorCount > 0) {
				this.emit('error', 'Hygiene failed with ' + errorCount + ' errors. Check \'build/gulpfile.hygiene.js\'.');
			} else {
				this.emit('end');
			}
		}));
}

function createGitIndexVinyls(paths) {
	const cp = require('child_process');
	const repositoryPath = process.cwd();

	const fns = paths.map(relativePath => () => new Promise((c, e) => {
		const fullPath = path.join(repositoryPath, relativePath);

		fs.stat(fullPath, (err, stat) => {
			if (err && err.code === 'ENOENT') { // ignore deletions
				return c(null);
			} else if (err) {
				return e(err);
			}

			cp.exec(`git show :${relativePath}`, { maxBuffer: 2000 * 1024, encoding: 'buffer' }, (err, out) => {
				if (err) {
					return e(err);
				}

				c(new VinylFile({
					path: fullPath,
					base: repositoryPath,
					contents: out,
					stat
				}));
			});
		});
	}));

	return pall(fns, { concurrency: 4 })
		.then(r => r.filter(p => !!p));
}

gulp.task('hygiene', () => hygiene());

// this allows us to run hygiene as a git pre-commit hook
if (require.main === module) {
	const cp = require('child_process');

	process.on('unhandledRejection', (reason, p) => {
		console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
		process.exit(1);
	});

	if (process.argv.length > 2) {
		hygiene(process.argv.slice(2)).on('error', err => {
			console.error();
			console.error(err);
			process.exit(1);
		});
	} else {
		cp.exec('git diff --cached --name-only', { maxBuffer: 2000 * 1024 }, (err, out) => {
			if (err) {
				console.error();
				console.error(err);
				process.exit(1);
				return;
			}

			const some = out
				.split(/\r?\n/)
				.filter(l => !!l);

			if (some.length > 0) {
				console.log('Reading git index versions...');

				createGitIndexVinyls(some)
					.then(vinyls => new Promise((c, e) => hygiene(es.readArray(vinyls))
						.on('end', () => c())
						.on('error', e)))
					.catch(err => {
						console.error();
						console.error(err);
						process.exit(1);
					});
			}
		});
	}
}
<commit_msg>ignore js error for webpack<commit_after>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

const gulp = require('gulp');
const filter = require('gulp-filter');
const es = require('event-stream');
const gulptslint = require('gulp-tslint');
const gulpeslint = require('gulp-eslint');
const tsfmt = require('typescript-formatter');
const tslint = require('tslint');
const VinylFile = require('vinyl');
const vfs = require('vinyl-fs');
const path = require('path');
const fs = require('fs');
const pall = require('p-all');

/**
 * Hygiene works by creating cascading subsets of all our files and
 * passing them through a sequence of checks. Here are the current subsets,
 * named according to the checks performed on them. Each subset contains
 * the following one, as described in mathematical notation:
 *
 * all ⊃ eol ⊇ indentation ⊃ copyright ⊃ typescript
 */

const all = [
	'*',
	'build/**/*',
	'extensions/**/*',
	'scripts/**/*',
	'src/**/*',
	'test/**/*',
	'!**/node_modules/**'
];

const indentationFilter = [
	'**',

	// except specific files
	'!ThirdPartyNotices.txt',
	'!LICENSE.txt',
	'!src/vs/nls.js',
	'!src/vs/css.js',
	'!src/vs/loader.js',
	'!src/vs/base/common/marked/marked.js',
	'!src/vs/base/common/winjs.base.js',
	'!src/vs/base/node/terminateProcess.sh',
	'!test/assert.js',

	// except specific folders
	'!test/smoke/out/**',
	'!extensions/vscode-api-tests/testWorkspace/**',
	'!extensions/vscode-api-tests/testWorkspace2/**',
	'!build/monaco/**',
	'!build/win32/**',

	// except multiple specific files
	'!**/package.json',
	'!**/yarn.lock',
	'!**/yarn-error.log',

	// except multiple specific folders
	'!**/octicons/**',
	'!**/fixtures/**',
	'!**/lib/**',
	'!extensions/**/out/**',
	'!extensions/**/snippets/**',
	'!extensions/**/syntaxes/**',
	'!extensions/**/themes/**',
	'!extensions/**/colorize-fixtures/**',

	// except specific file types
	'!src/vs/*/**/*.d.ts',
	'!src/typings/**/*.d.ts',
	'!extensions/**/*.d.ts',
	'!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe}',
	'!build/{lib,tslintRules}/**/*.js',
	'!build/**/*.sh',
	'!build/tfs/**/*.js',
	'!**/Dockerfile',
	'!extensions/markdown-language-features/media/*.js',
	'!extensions/git-extended/media/*.js'
];

const copyrightFilter = [
	'**',
	'!**/*.desktop',
	'!**/*.json',
	'!**/*.html',
	'!**/*.template',
	'!**/*.md',
	'!**/*.bat',
	'!**/*.cmd',
	'!**/*.xml',
	'!**/*.sh',
	'!**/*.txt',
	'!**/*.xpm',
	'!**/*.opts',
	'!**/*.disabled',
	'!**/*.code-workspace',
	'!build/**/*.init',
	'!resources/linux/snap/snapcraft.yaml',
	'!resources/win32/bin/code.js',
	'!extensions/markdown-language-features/media/tomorrow.css',
	'!extensions/html-language-features/server/src/modes/typescript/*',
	'!extensions/*/server/bin/*'
];

const eslintFilter = [
	'src/**/*.js',
	'build/gulpfile.*.js',
	'!src/vs/loader.js',
	'!src/vs/css.js',
	'!src/vs/nls.js',
	'!src/vs/css.build.js',
	'!src/vs/nls.build.js',
	'!src/**/winjs.base.js',
	'!src/**/marked.js',
	'!**/test/**'
];

const tslintFilter = [
	'src/**/*.ts',
	'test/**/*.ts',
	'extensions/**/*.ts',
	'!**/fixtures/**',
	'!**/typings/**',
	'!**/node_modules/**',
	'!extensions/typescript/test/colorize-fixtures/**',
	'!extensions/vscode-api-tests/testWorkspace/**',
	'!extensions/vscode-api-tests/testWorkspace2/**',
	'!extensions/**/*.test.ts',
	'!extensions/html-language-features/server/lib/jquery.d.ts'
];

const copyrightHeaderLines = [
	'/*---------------------------------------------------------------------------------------------',
	' *  Copyright (c) Microsoft Corporation. All rights reserved.',
	' *  Licensed under the MIT License. See License.txt in the project root for license information.',
	' *--------------------------------------------------------------------------------------------*/'
];

gulp.task('eslint', () => {
	return vfs.src(all, { base: '.', follow: true, allowEmpty: true })
		.pipe(filter(eslintFilter))
		.pipe(gulpeslint('src/.eslintrc'))
		.pipe(gulpeslint.formatEach('compact'))
		.pipe(gulpeslint.failAfterError());
});

gulp.task('tslint', () => {
	const options = { emitError: true };

	return vfs.src(all, { base: '.', follow: true, allowEmpty: true })
		.pipe(filter(tslintFilter))
		.pipe(gulptslint.default({ rulesDirectory: 'build/lib/tslint' }))
		.pipe(gulptslint.default.report(options));
});

function hygiene(some) {
	let errorCount = 0;

	const indentation = es.through(function (file) {
		const lines = file.contents.toString('utf8').split(/\r\n|\r|\n/);
		file.__lines = lines;

		lines
			.forEach((line, i) => {
				if (/^\s*$/.test(line)) {
					// empty or whitespace lines are OK
				} else if (/^[\t]*[^\s]/.test(line)) {
					// good indent
				} else if (/^[\t]* \*/.test(line)) {
					// block comment using an extra space
				} else {
					console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation');
					errorCount++;
				}
			});

		this.emit('data', file);
	});

	const copyrights = es.through(function (file) {
		const lines = file.__lines;

		for (let i = 0; i < copyrightHeaderLines.length; i++) {
			if (lines[i] !== copyrightHeaderLines[i]) {
				console.error(file.relative + ': Missing or bad copyright statement');
				errorCount++;
				break;
			}
		}

		this.emit('data', file);
	});

	const formatting = es.map(function (file, cb) {
		tsfmt.processString(file.path, file.contents.toString('utf8'), {
			verify: false,
			tsfmt: true,
			// verbose: true,
			// keep checkJS happy
			editorconfig: undefined,
			replace: undefined,
			tsconfig: undefined,
			tsconfigFile: undefined,
			tslint: undefined,
			tslintFile: undefined,
			tsfmtFile: undefined,
			vscode: undefined,
			vscodeFile: undefined
		}).then(result => {
			let original = result.src.replace(/\r\n/gm, '\n');
			let formatted = result.dest.replace(/\r\n/gm, '\n');

			if (original !== formatted) {
				console.error('File not formatted:', file.relative);
				errorCount++;
			}
			cb(null, file);

		}, err => {
			cb(err);
		});
	});

	const tslintConfiguration = tslint.Configuration.findConfiguration('tslint.json', '.');
	const tslintOptions = { fix: false, formatter: 'json' };
	const tsLinter = new tslint.Linter(tslintOptions);

	const tsl = es.through(function (file) {
		const contents = file.contents.toString('utf8');
		tsLinter.lint(file.relative, contents, tslintConfiguration.results);
		this.emit('data', file);
	});

	let input;

	if (Array.isArray(some) || typeof some === 'string' || !some) {
		input = vfs.src(some || all, { base: '.', follow: true, allowEmpty: true });
	} else {
		input = some;
	}

	const result = input
		.pipe(filter(f => !f.stat.isDirectory()))
		.pipe(filter(indentationFilter))
		.pipe(indentation)
		.pipe(filter(copyrightFilter))
		.pipe(copyrights);

	const typescript = result
		.pipe(filter(tslintFilter))
		.pipe(formatting)
		.pipe(tsl);

	const javascript = result
		.pipe(filter(eslintFilter))
		.pipe(gulpeslint('src/.eslintrc'))
		.pipe(gulpeslint.formatEach('compact'))
		.pipe(gulpeslint.failAfterError());

	let count = 0;
	return es.merge(typescript, javascript)
		.pipe(es.through(function (data) {
			count++;
			if (process.env['TRAVIS'] && count % 10 === 0) {
				process.stdout.write('.');
			}
			this.emit('data', data);
		}, function () {
			process.stdout.write('\n');

			const tslintResult = tsLinter.getResult();
			if (tslintResult.failures.length > 0) {
				for (const failure of tslintResult.failures) {
					const name = failure.getFileName();
					const position = failure.getStartPosition();
					const line = position.getLineAndCharacter().line;
					const character = position.getLineAndCharacter().character;

					console.error(`${name}:${line + 1}:${character + 1}:${failure.getFailure()}`);
				}
				errorCount += tslintResult.failures.length;
			}

			if (errorCount > 0) {
				this.emit('error', 'Hygiene failed with ' + errorCount + ' errors. Check \'build/gulpfile.hygiene.js\'.');
			} else {
				this.emit('end');
			}
		}));
}

function createGitIndexVinyls(paths) {
	const cp = require('child_process');
	const repositoryPath = process.cwd();

	const fns = paths.map(relativePath => () => new Promise((c, e) => {
		const fullPath = path.join(repositoryPath, relativePath);

		fs.stat(fullPath, (err, stat) => {
			if (err && err.code === 'ENOENT') { // ignore deletions
				return c(null);
			} else if (err) {
				return e(err);
			}

			cp.exec(`git show :${relativePath}`, { maxBuffer: 2000 * 1024, encoding: 'buffer' }, (err, out) => {
				if (err) {
					return e(err);
				}

				c(new VinylFile({
					path: fullPath,
					base: repositoryPath,
					contents: out,
					stat
				}));
			});
		});
	}));

	return pall(fns, { concurrency: 4 })
		.then(r => r.filter(p => !!p));
}

gulp.task('hygiene', () => hygiene());

// this allows us to run hygiene as a git pre-commit hook
if (require.main === module) {
	const cp = require('child_process');

	process.on('unhandledRejection', (reason, p) => {
		console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
		process.exit(1);
	});

	if (process.argv.length > 2) {
		hygiene(process.argv.slice(2)).on('error', err => {
			console.error();
			console.error(err);
			process.exit(1);
		});
	} else {
		cp.exec('git diff --cached --name-only', { maxBuffer: 2000 * 1024 }, (err, out) => {
			if (err) {
				console.error();
				console.error(err);
				process.exit(1);
				return;
			}

			const some = out
				.split(/\r?\n/)
				.filter(l => !!l);

			if (some.length > 0) {
				console.log('Reading git index versions...');

				createGitIndexVinyls(some)
					.then(vinyls => new Promise((c, e) => hygiene(es.readArray(vinyls))
						.on('end', () => c())
						.on('error', e)))
					.catch(err => {
						console.error();
						console.error(err);
						process.exit(1);
					});
			}
		});
	}
}
<|endoftext|>


<commit_before>import * as generators from '../common/generators';

import * as covers from '../dsymbols/covers';
import * as DS from '../dsymbols/delaney';
import * as DS2D from '../dsymbols/delaney2d';
import * as props from '../dsymbols/properties';

import { rationals } from '../arithmetic/types';
const Q = rationals;

const _loopless = (ds, i, j, D) => DS.orbit2(ds, i, j, D)
  .every(E => ds.s(i, E) != E && ds.s(j, E) != E);

const _orbits = ds => {
  const result = [];

  for (const [i, j] of [[0, 1], [1, 2]]) {
    for (const D of DS.orbitReps2(ds, i, j)) {
      result.push([i, D, DS.r(ds, i, j, D), _loopless(ds, i, j, D)]);
    }
  }

  return result;
};

const _openOrbits = ds =>
  _orbits(ds).filter(([i, D, r, loopless]) => !ds.v(i, i+1, D));

const _withMinimalBranchings = ds => {
  const branchings = (i, j) =>
    DS.orbitReps2(ds, i, j)
    .filter(D => !ds.v(i, j, D))
    .map(D => [D, Math.ceil(3 / DS.r(ds, i, j, D))]);

  return DS.withBranchings(
    DS.withBranchings(ds, 0, branchings(0, 1)),
    1,
    branchings(1, 2)
  );
};

const _compareMapped = (ds, m) => {
  for (const D of ds.elements()) {
    for (const [i, j] of [[0, 1], [1, 2]]) {
      const d = ds.v(i, j, D) - ds.v(i, j, m[D]);
      if (d != 0) return d;
    }
  }
  return 0;
};

const _isCanonical = (ds, maps) => maps.every(m => _compareMapped(ds, m) >= 0);

const _newCurvature = (curv, loopless, v, vOld) =>
  Q.plus(
    curv,
    Q.times(loopless ? 2 : 1, Q.minus(Q.div(1, v), Q.div(1, vOld)))
  );

const isMinimallyHyperbolic = ds => {
  const curv = DS2D.curvature(ds);
  if (Q.ge(curv, 0))
    return false;

  for (const [i, D, r, loopless] of _orbits(ds)) {
    const v = ds.v(i, i+1, D);

    if (v && v > Math.ceil(3 / r)) {
      const newCurv = _newCurvature(curv, loopless, v-1, v);
      if (Q.lt(newCurv, 0))
        return false;
    }
  }

  return true;
};

const _goodResult = ds => {
  if (Q.le(DS2D.curvature(ds), 0))
    return true;

  if (!DS2D.isSpherical(ds))
    return false;

  const forbidden = [
    '55', '66', '77', '*55', '*66', '*77', '2*5', '2*6', '2*7'
  ];
  return forbidden.indexOf(DS2D.orbifoldSymbol(ds)) < 0;
};

const branchings = ds => {
  const unused = _openOrbits(ds);
  const maps = props.automorphisms(ds);
  const ds0 = _withMinimalBranchings(ds);

  return generators.backtracker({
    root: [ds0, DS2D.curvature(ds0), unused],

    extract([ds, curv, unused]) {
      if (unused.length == 0 && _isCanonical(ds, maps) && _goodResult(ds))
        return ds;
    },

    children([ds, curv, unused]) {
      if (unused.length) {
        if (Q.lt(curv, 0)) {
          return [[ds, curv, []]];
        }
        else {
          const [i, D, r, loopless] = unused[0];
          const v0 = ds.v(i, i+1, D);
          const out = [];

          for (let v = v0; v <= 7; ++v) {
            const newCurv = _newCurvature(curv, loopless, v, v0);
            const newDs = DS.withBranchings(ds, i, [[D, v]]);

            if (Q.ge(newCurv, 0) || isMinimallyHyperbolic(newDs))
              out.push([ newDs, newCurv, unused.slice(1) ]);

            if (Q.lt(newCurv, 0))
              break;
          }

          return out;
        }
      }
    }
  });
}

if (require.main == module) {
  const maxDsSize = parseInt(process.argv[2]);

  covers.covers(DS.parse('<1.1:1:1,1,1:0,0>'), maxDsSize)
    .flatMap(ds => generators.results(branchings(ds)))
    .forEach(ds => console.log(`${ds}`));
}
<commit_msg>scripts/daniel: add mode for specifying a D-set on the command line<commit_after>import * as generators from '../common/generators';

import * as covers from '../dsymbols/covers';
import * as DS from '../dsymbols/delaney';
import * as DS2D from '../dsymbols/delaney2d';
import * as props from '../dsymbols/properties';

import { rationals } from '../arithmetic/types';
const Q = rationals;

const _loopless = (ds, i, j, D) => DS.orbit2(ds, i, j, D)
  .every(E => ds.s(i, E) != E && ds.s(j, E) != E);

const _orbits = ds => {
  const result = [];

  for (const [i, j] of [[0, 1], [1, 2]]) {
    for (const D of DS.orbitReps2(ds, i, j)) {
      result.push([i, D, DS.r(ds, i, j, D), _loopless(ds, i, j, D)]);
    }
  }

  return result;
};

const _openOrbits = ds =>
  _orbits(ds).filter(([i, D, r, loopless]) => !ds.v(i, i+1, D));

const _withMinimalBranchings = ds => {
  const branchings = (i, j) =>
    DS.orbitReps2(ds, i, j)
    .filter(D => !ds.v(i, j, D))
    .map(D => [D, Math.ceil(3 / DS.r(ds, i, j, D))]);

  return DS.withBranchings(
    DS.withBranchings(ds, 0, branchings(0, 1)),
    1,
    branchings(1, 2)
  );
};

const _compareMapped = (ds, m) => {
  for (const D of ds.elements()) {
    for (const [i, j] of [[0, 1], [1, 2]]) {
      const d = ds.v(i, j, D) - ds.v(i, j, m[D]);
      if (d != 0) return d;
    }
  }
  return 0;
};

const _isCanonical = (ds, maps) => maps.every(m => _compareMapped(ds, m) >= 0);

const _newCurvature = (curv, loopless, v, vOld) =>
  Q.plus(
    curv,
    Q.times(loopless ? 2 : 1, Q.minus(Q.div(1, v), Q.div(1, vOld)))
  );

const isMinimallyHyperbolic = ds => {
  const curv = DS2D.curvature(ds);
  if (Q.ge(curv, 0))
    return false;

  for (const [i, D, r, loopless] of _orbits(ds)) {
    const v = ds.v(i, i+1, D);

    if (v && v > Math.ceil(3 / r)) {
      const newCurv = _newCurvature(curv, loopless, v-1, v);
      if (Q.lt(newCurv, 0))
        return false;
    }
  }

  return true;
};

const _goodResult = ds => {
  if (Q.le(DS2D.curvature(ds), 0))
    return true;

  if (!DS2D.isSpherical(ds))
    return false;

  const forbidden = [
    '55', '66', '77', '*55', '*66', '*77', '2*5', '2*6', '2*7'
  ];
  return forbidden.indexOf(DS2D.orbifoldSymbol(ds)) < 0;
};

const branchings = ds => {
  const unused = _openOrbits(ds);
  const maps = props.automorphisms(ds);
  const ds0 = _withMinimalBranchings(ds);

  return generators.backtracker({
    root: [ds0, DS2D.curvature(ds0), unused],

    extract([ds, curv, unused]) {
      if (unused.length == 0 && _isCanonical(ds, maps) && _goodResult(ds))
        return ds;
    },

    children([ds, curv, unused]) {
      if (unused.length) {
        if (Q.lt(curv, 0)) {
          return [[ds, curv, []]];
        }
        else {
          const [i, D, r, loopless] = unused[0];
          const v0 = ds.v(i, i+1, D);
          const out = [];

          for (let v = v0; v <= 7; ++v) {
            const newCurv = _newCurvature(curv, loopless, v, v0);
            const newDs = DS.withBranchings(ds, i, [[D, v]]);

            if (Q.ge(newCurv, 0) || isMinimallyHyperbolic(newDs))
              out.push([ newDs, newCurv, unused.slice(1) ]);

            if (Q.lt(newCurv, 0))
              break;
          }

          return out;
        }
      }
    }
  });
}

if (require.main == module) {
  const arg = process.argv[2];

  if (Number.isInteger(parseInt(arg))) {
    covers.covers(DS.parse('<1.1:1:1,1,1:0,0>'), parseInt(arg))
      .flatMap(ds => generators.results(branchings(ds)))
      .forEach(ds => console.log(`${ds}`));
  }
  else {
    for (const ds of generators.results(branchings(DS.parseSymbols(arg)[0])))
      console.log(`${ds}`);
  }
}
<|endoftext|>


<commit_before>(function(root) {
  root.LJCanvas = {};

  function Canvas(el) {
    this.canvas = document.querySelector(el);
    this.context = this.canvas.context;
  }

}(window))
;
<commit_msg>change canvas<commit_after>(function(root) {
  root.LJCanvas = {};

  function Canvas(el) {
    this.canvas = document.querySelector(el);
    this.ctx = null;
    this.halfWidth = null;
    this.halfHeight = null;

    this._init();
  }

  Canvas.prototype.drawArc = function(start, end, radius) {
    var x = end.x;
    var y = end.y;
    var hw = this.halfWidth;
    var hh = this.halfHeight;

    var ctx = this.ctx;

    ctx.beginPath();

    if (start) {
      ctx.moveTo(start.x + hw, start.y + hh);
    }
    var color = LJColor.getDirectionalColor(x, y);

    ctx.arcTo(x + hw, y + hh, radius, 0, 0);
    ctx.lineWidth = 5;
    ctx.closePath();
    ctx.strokeStyle = color;
    ctx.shadowBlur = 30;
    ctx.shadowColor = color;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;
    ctx.stroke();
  };

  Canvas.prototype._init = function() {
    if (!('getContext' in this.canvas)) {
      alert('Your browser doesnt support this.')
    }

    this.ctx = this.canvas.getContext('2d');

    // Check if we should scale for retina screens
    this._scale();

    this.halfWidth = this.canvas.width / 4;
    this.halfHeight = this.canvas.height / 4;

  };

  Canvas.prototype._scale = function() {
    var scalingRatio = window.devicePixelRatio;

    // Wer are on a standard res screen and dont need to scale.
    if (scalingRatio === 1) {
      return;
    }

    var oldWidth = this.canvas.width;
    var oldHeight = this.canvas.height;

    this.canvas.width = oldWidth * scalingRatio;
    this.canvas.height = oldHeight * scalingRatio;

    this.canvas.style.width = oldWidth + 'px';
    this.canvas.style.height = oldHeight + 'px';

    this.ctx.scale(scalingRatio, scalingRatio);
  };

  Canvas.prototype.height = function() {
    return this.canvas.height;
  };

  Canvas.prototype.width = function() {
    return this.canvas.width;
  };

  Canvas.prototype.constructor = Canvas;

  return root.LJCanvas = Canvas;
}(window))
;
<|endoftext|>


<commit_before>import { argv } from 'yargs'
import config from '../config'
import webpackConfig from './webpack.config'
const { paths } = config

module.exports = (karmaConfig) => {
  karmaConfig.set({
    basePath: process.cwd(),
    browsers: ['PhantomJS'],
    singleRun: !argv.watch,
    reporters: ['mocha'],
    files: [
      require.resolve('babel-polyfill/browser'),
      './test/tests.bundle.js',
    ],
    frameworks: ['mocha'],
    preprocessors: {
      '**/*.bundle.js': ['webpack'],
    },
    client: {
      mocha: {
        reporter: 'html',   // change Karma's debug.html to mocha web reporter
        ui: 'bdd',
      },
    },
    webpack: {
      devtool: 'cheap-module-source-map',
      module: {
        ...webpackConfig.module,
        loaders: [
          {
            test: /sinon\.js$/,
            loader: 'imports?define=>false,require=>false',
          },
          ...webpackConfig.module.loaders,
        ],
      },
      resolve: {
        ...webpackConfig.resolve,
        alias: {
          ...webpackConfig.resolve.alias,
          jquery: `${paths.test('mocks')}/SemanticjQuery-mock.js`,
          sinon: 'sinon/pkg/sinon',
          // These are internal deps specific to React 0.13 required() by enzyme
          // They shouldn't be requiring these at all, issues and fix proposed
          // https://github.com/airbnb/enzyme/issues/285
          'react/lib/ExecutionEnvironment': 'empty/object',
          'react/lib/ReactContext': 'empty/object',
        },
      },
    },
    webpackServer: {
      progress: false,
      stats: config.compiler_stats,
      debug: true,
      noInfo: false,
      quiet: false,
    },
  })
}
<commit_msg>chore(karma): add compiler globals<commit_after>import { argv } from 'yargs'
import webpack from 'webpack'

import config from '../config'
import webpackConfig from './webpack.config'

const { paths } = config

module.exports = (karmaConfig) => {
  karmaConfig.set({
    basePath: process.cwd(),
    browsers: ['PhantomJS'],
    singleRun: !argv.watch,
    reporters: ['mocha'],
    files: [
      require.resolve('babel-polyfill/browser'),
      './test/tests.bundle.js',
    ],
    frameworks: ['mocha'],
    preprocessors: {
      '**/*.bundle.js': ['webpack'],
    },
    client: {
      mocha: {
        reporter: 'html',   // change Karma's debug.html to mocha web reporter
        ui: 'bdd',
      },
    },
    webpack: {
      devtool: 'cheap-module-source-map',
      module: {
        ...webpackConfig.module,
        loaders: [
          {
            test: /sinon\.js$/,
            loader: 'imports?define=>false,require=>false',
          },
          ...webpackConfig.module.loaders,
        ],
      },
      plugins: [
        new webpack.DefinePlugin(config.compiler_globals),
      ],
      resolve: {
        ...webpackConfig.resolve,
        alias: {
          ...webpackConfig.resolve.alias,
          jquery: `${paths.test('mocks')}/SemanticjQuery-mock.js`,
          sinon: 'sinon/pkg/sinon',
          // These are internal deps specific to React 0.13 required() by enzyme
          // They shouldn't be requiring these at all, issues and fix proposed
          // https://github.com/airbnb/enzyme/issues/285
          'react/lib/ExecutionEnvironment': 'empty/object',
          'react/lib/ReactContext': 'empty/object',
        },
      },
    },
    webpackServer: {
      progress: false,
      stats: config.compiler_stats,
      debug: true,
      noInfo: false,
      quiet: false,
    },
  })
}
<|endoftext|>