<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 David Heidrich, BowlingX <me@bowlingx.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/*!
 * FlexCss.Dropdown
 * Licensed under the MIT License (MIT)
 * Copyright (c) 2015 David Heidrich, BowlingX <me@bowlingx.com>
 */

import Util from './util/Util';
import Settings from './util/Settings';
import Event from './util/Event';
import Widget from './Widget';
import FixedWindow from './lib/FixedWindow';

/**
 * @type {string}
 */
const ATTR_CC = 'data-collision-container';
/**
 * @type {string}
 */
const ATTR_DARKENER = 'data-darkener-container';
/**
 * @type {string}
 */
const ATTR_ENABLE_EVENT_BUBBLE = 'data-enable-event-bubble';
/**
 * @type {string}
 */
const DARKENER_INIT = 'init';
/**
 * @type {string}
 */
const ATTR_DATA_TARGET = 'data-target';
/**
 * @type {string}
 */
const ATTR_CLOSE_DROPDOWN = 'data-close-dropdown';
/**
 * @type {string}
 */
const ATTR_NAME = 'data-dropdown';
/**
 * @type {string}
 */
const STATE_LOADING = 'loading';

const CLS_DARKENER_DROPDOWN = 'darkener-dropdown';
/**
 * @type {string}
 */
const CLS_OPEN = 'open';

/**
 * @type {string}
 */
const CLS_DROPDOWN = 'dropdown';

/**
 * @type {string}
 */
export const EVENT_DROPDOWN_CLOSED = 'flexcss.dropdown.closed';

/**
 * A Dropdown
 */
class Dropdown {
    constructor(DelegateContainer, Darkener) {
        const doc = global.document;

        /**
         * Container Element
         * @type {HTMLElement}
         */
        this.container = DelegateContainer instanceof HTMLElement ?
            DelegateContainer : doc.getElementById(DelegateContainer);

        this.currentOpen = null;
        this.currentTarget = null;
        /**
         * @type {HTMLElement}
         */
        this.darkener = Darkener instanceof HTMLElement ? Darkener : document.getElementById(Darkener);

        this.destroyOnClose = false;

        if (!this.darkener || !this.container) {
            throw new Error('required elements not found (darkener and container element)');
        }
    }

    /**
     * Method that handles delegation events for dropdowns
     * @param e
     * @returns {boolean}
     * @private
     */
    _delegateFunction(e) {
        const currentOpen = this.currentOpen;
        const target = Util.closestCallback(e.target, n => n instanceof HTMLElement && n.hasAttribute(ATTR_NAME));
        const targetIsCurrent = target === this.currentTarget;

        if (currentOpen && !Util.isPartOfNode(e.target, currentOpen) || targetIsCurrent) {
            this.close();
            if (targetIsCurrent) {
                e.preventDefault();
            }
            return targetIsCurrent ? false : this._delegateFunction(e);
        }

        if (target && !currentOpen) {

            if (!target.getAttribute(ATTR_ENABLE_EVENT_BUBBLE)) {
                e.preventDefault();
                e.stopImmediatePropagation();
            }

            if (target.isLoading) {
                return false;
            }
            this.createDropdown(target);
        } else {
            if (currentOpen) {
                if (e.target.hasAttribute(ATTR_CLOSE_DROPDOWN)) {
                    e.preventDefault();
                    this.close();
                }
                if (!Util.isPartOfNode(e.target, currentOpen)) {
                    this.close();
                }
            }
        }
    }

    /**
     * Register Events for this dropdown container
     * @returns {Dropdown}
     */
    registerEvents() {
        FixedWindow.getInstance().addScreenConstraint(Dropdown, (width) => width < Settings.get().smallBreakpoint);
        this.container.addEventListener(Settings.getTabEvent(), this._delegateFunction.bind(this), true);
        return this;
    }

    /**
     * Destroys this instance, unbinds events
     * @returns {Dropdown}
     */
    destroy() {
        this.container.removeEventListener(Settings.getTabEvent(), this._delegateFunction.bind(this), true);
        return this;
    }

    /**
     * Destroys instance on close of dropdown
     * @param v
     * @returns {Dropdown}
     */
    setDestroyOnClose(v) {
        this.destroyOnClose = v;
        return this;
    }

    /**
     * Will add the right class to container for specific darkener id
     * @param instance
     * @param show
     */
    toggleDarkenerToggler(instance, show) {
        const cls = `toggle-${(instance.id || CLS_DARKENER_DROPDOWN)}`;
        const classList = this.container.classList;
        if (show) {
            classList.add(cls);
        } else {
            classList.remove(cls);
        }
    }

    /**
     * Closes Dropdown on current instance
     * @return {Boolean|Promise}
     */
    close() {
        const currentOpen = this.currentOpen;
        if (!currentOpen) {
            return false;
        }
        const darkenerInstance = currentOpen.flexDarkenerInstance || this.darkener;
        const thisCurrentOpen = currentOpen;

        const future = new Promise((resolve) => {
            if (window.getComputedStyle(currentOpen).position === 'fixed') {
                Util.addEventOnce(Settings.getTransitionEvent(), currentOpen, function scheduler(e) {
                    if (e.target !== currentOpen) {
                        return Util.addEventOnce(Settings.getTransitionEvent(), currentOpen, scheduler.bind(this));
                    }
                    requestAnimationFrame(() => {
                        Event.dispatchAndFire(thisCurrentOpen, EVENT_DROPDOWN_CLOSED);
                        // if a new dropdown has been opened in the meantime, do not remove darkener
                        if (this.currentOpen !== null) {
                            return false;
                        }
                        this.toggleDarkenerToggler(darkenerInstance, false);
                        resolve(true);
                    });
                }.bind(this));
            } else {
                resolve(true);
                Event.dispatchAndFire(thisCurrentOpen, EVENT_DROPDOWN_CLOSED);
            }
        });

        FixedWindow.getInstance().close();
        currentOpen.classList.remove(CLS_OPEN);

        if (currentOpen.flexDarkenerInstance) {
            currentOpen.flexDarkenerInstance.classList.remove(DARKENER_INIT);
        } else {
            this.darkener.classList.remove(DARKENER_INIT);
        }

        this.currentOpen = null;
        this.currentTarget = null;

        if (this.destroyOnClose) {
            this.destroy();
        }

        return future;
    }

    /**
     * Creates a dropdown on given target and opens it
     * @param {HTMLElement} target target where this dropdown is placed
     * @param {FlexCss.Widget} [thisWidget] if given will use widget instead of widget instance
     * @return {FlexCss.Dropdown}
     */
    createDropdown(target, thisWidget) {
        const doc = global.document;

        if (!target) {
            throw new Error('Dropdown target not found');
        }

        const widget = thisWidget || Widget.findWidget(target);
        let future;
        const data = target.getAttribute(ATTR_NAME);
        const dropdownContainerElement = doc.getElementById(data);
        const async = !dropdownContainerElement && Widget.isWidget(widget) ? widget.getAsync() : false;

        if (async) {
            target.classList.add(STATE_LOADING);
            target.isLoading = true;
            future = async.then((r) => {
                if (r instanceof HTMLElement) {
                    if (r.id) {
                        target.setAttribute(ATTR_NAME, r.id);
                    }
                    return r;
                }
                // Create container Element:
                const element = doc.createElement('div');
                element.className = CLS_DROPDOWN;
                element.innerHTML = r;
                element.id = Util.guid();
                // Cache target for later use:
                target.setAttribute(ATTR_NAME, element.id);
                this.container.appendChild(element);
                return element;
            }).then((r) => {
                target.isLoading = false;
                target.classList.remove(STATE_LOADING);
                return r;
            });
        } else {
            if (!dropdownContainerElement) {
                throw new Error(`Could not found Dropdown container with ID "${data}"`);
            }
            future = new Promise((r) => {
                r(dropdownContainerElement);
            });
        }

        future.then((dropdownContent) => {
            if (this.currentOpen) {
                this.close();
            }
            // Skip one frame to show animation
            target.dropdownContent = dropdownContent;
            const isAbsolute = global.getComputedStyle(dropdownContent).position === 'absolute';
            dropdownContent.hfWidgetInstance = this;

            if (!target.flexCollisionContainer) {
                const collisionC = target.getAttribute(ATTR_CC);
                target.flexCollisionContainer = collisionC ?
                doc.getElementById(collisionC) || document.documentElement : document.documentElement;
            }

            dropdownContent.classList.toggle(CLS_OPEN);
            if (dropdownContent.classList.contains(CLS_OPEN)) {
                this.currentOpen = dropdownContent;
                this.currentTarget = target;
            }
            FixedWindow.getInstance().open(this, dropdownContent);
            if (isAbsolute) {
                // Check collision:
                let selfTarget = target.getAttribute(ATTR_DATA_TARGET);
                selfTarget = selfTarget ? doc.getElementById(selfTarget) : target;
                Util.setupPositionNearby(selfTarget, dropdownContent, target.flexCollisionContainer);
            } else {
                // optionally get custom darkener container for target
                const d = target.getAttribute(ATTR_DARKENER);
                if (d) {
                    dropdownContent.flexDarkenerInstance = doc.getElementById(d);
                    (dropdownContent.flexDarkenerInstance || this.darkener).classList.toggle(DARKENER_INIT);
                } else {
                    this.darkener.classList.toggle(DARKENER_INIT);
                }
                this.toggleDarkenerToggler(dropdownContent.flexDarkenerInstance || this.darkener, true);

                dropdownContent.style.left = '0';
                dropdownContent.style.top = 'auto';
            }
        });
    }

}

export default Dropdown;
<commit_msg>fix(dropdown): make event bubbeling in dropdown configurable<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 David Heidrich, BowlingX <me@bowlingx.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/*!
 * FlexCss.Dropdown
 * Licensed under the MIT License (MIT)
 * Copyright (c) 2015 David Heidrich, BowlingX <me@bowlingx.com>
 */

import Util from './util/Util';
import Settings from './util/Settings';
import Event from './util/Event';
import Widget from './Widget';
import FixedWindow from './lib/FixedWindow';

/**
 * @type {string}
 */
const ATTR_CC = 'data-collision-container';
/**
 * @type {string}
 */
const ATTR_DARKENER = 'data-darkener-container';
/**
 * @type {string}
 */
const ATTR_ENABLE_EVENT_BUBBLE = 'data-enable-event-bubble';
/**
 * @type {string}
 */
const DARKENER_INIT = 'init';
/**
 * @type {string}
 */
const ATTR_DATA_TARGET = 'data-target';
/**
 * @type {string}
 */
const ATTR_CLOSE_DROPDOWN = 'data-close-dropdown';
/**
 * @type {string}
 */
const ATTR_NAME = 'data-dropdown';
/**
 * @type {string}
 */
const STATE_LOADING = 'loading';

const CLS_DARKENER_DROPDOWN = 'darkener-dropdown';
/**
 * @type {string}
 */
const CLS_OPEN = 'open';

/**
 * @type {string}
 */
const CLS_DROPDOWN = 'dropdown';

/**
 * @type {string}
 */
export const EVENT_DROPDOWN_CLOSED = 'flexcss.dropdown.closed';

/**
 * A Dropdown
 */
class Dropdown {
    constructor(DelegateContainer, Darkener) {
        const doc = global.document;

        /**
         * Container Element
         * @type {HTMLElement}
         */
        this.container = DelegateContainer instanceof HTMLElement ?
            DelegateContainer : doc.getElementById(DelegateContainer);

        this.currentOpen = null;
        this.currentTarget = null;
        /**
         * @type {HTMLElement}
         */
        this.darkener = Darkener instanceof HTMLElement ? Darkener : document.getElementById(Darkener);

        this.destroyOnClose = false;

        if (!this.darkener || !this.container) {
            throw new Error('required elements not found (darkener and container element)');
        }
    }

    /**
     * Method that handles delegation events for dropdowns
     * @param e
     * @returns {boolean}
     * @private
     */
    _delegateFunction(e) {
        const currentOpen = this.currentOpen;
        const target = Util.closestCallback(e.target, n => n instanceof HTMLElement && n.hasAttribute(ATTR_NAME));
        const targetIsCurrent = target === this.currentTarget;

        if (currentOpen && !Util.isPartOfNode(e.target, currentOpen) || targetIsCurrent) {
            this.close();
            if (targetIsCurrent) {
                e.preventDefault();
            }
            return targetIsCurrent ? false : this._delegateFunction(e);
        }

        if (target && !currentOpen) {

            if (!target.getAttribute(ATTR_ENABLE_EVENT_BUBBLE)) {
                e.preventDefault();
                e.stopImmediatePropagation();
            }

            if (target.isLoading) {
                return false;
            }
            this.createDropdown(target);
        } else {
            if (currentOpen) {
                if (e.target.hasAttribute(ATTR_CLOSE_DROPDOWN)) {
                    if (!e.target.getAttribute(ATTR_ENABLE_EVENT_BUBBLE)) {
                        e.preventDefault();
                    }
                    this.close();
                }
                if (!Util.isPartOfNode(e.target, currentOpen)) {
                    this.close();
                }
            }
        }
    }

    /**
     * Register Events for this dropdown container
     * @returns {Dropdown}
     */
    registerEvents() {
        FixedWindow.getInstance().addScreenConstraint(Dropdown, (width) => width < Settings.get().smallBreakpoint);
        this.container.addEventListener(Settings.getTabEvent(), this._delegateFunction.bind(this), true);
        return this;
    }

    /**
     * Destroys this instance, unbinds events
     * @returns {Dropdown}
     */
    destroy() {
        this.container.removeEventListener(Settings.getTabEvent(), this._delegateFunction.bind(this), true);
        return this;
    }

    /**
     * Destroys instance on close of dropdown
     * @param v
     * @returns {Dropdown}
     */
    setDestroyOnClose(v) {
        this.destroyOnClose = v;
        return this;
    }

    /**
     * Will add the right class to container for specific darkener id
     * @param instance
     * @param show
     */
    toggleDarkenerToggler(instance, show) {
        const cls = `toggle-${(instance.id || CLS_DARKENER_DROPDOWN)}`;
        const classList = this.container.classList;
        if (show) {
            classList.add(cls);
        } else {
            classList.remove(cls);
        }
    }

    /**
     * Closes Dropdown on current instance
     * @return {Boolean|Promise}
     */
    close() {
        const currentOpen = this.currentOpen;
        if (!currentOpen) {
            return false;
        }
        const darkenerInstance = currentOpen.flexDarkenerInstance || this.darkener;
        const thisCurrentOpen = currentOpen;

        const future = new Promise((resolve) => {
            if (window.getComputedStyle(currentOpen).position === 'fixed') {
                Util.addEventOnce(Settings.getTransitionEvent(), currentOpen, function scheduler(e) {
                    if (e.target !== currentOpen) {
                        return Util.addEventOnce(Settings.getTransitionEvent(), currentOpen, scheduler.bind(this));
                    }
                    requestAnimationFrame(() => {
                        Event.dispatchAndFire(thisCurrentOpen, EVENT_DROPDOWN_CLOSED);
                        // if a new dropdown has been opened in the meantime, do not remove darkener
                        if (this.currentOpen !== null) {
                            return false;
                        }
                        this.toggleDarkenerToggler(darkenerInstance, false);
                        resolve(true);
                    });
                }.bind(this));
            } else {
                resolve(true);
                Event.dispatchAndFire(thisCurrentOpen, EVENT_DROPDOWN_CLOSED);
            }
        });

        FixedWindow.getInstance().close();
        currentOpen.classList.remove(CLS_OPEN);

        if (currentOpen.flexDarkenerInstance) {
            currentOpen.flexDarkenerInstance.classList.remove(DARKENER_INIT);
        } else {
            this.darkener.classList.remove(DARKENER_INIT);
        }

        this.currentOpen = null;
        this.currentTarget = null;

        if (this.destroyOnClose) {
            this.destroy();
        }

        return future;
    }

    /**
     * Creates a dropdown on given target and opens it
     * @param {HTMLElement} target target where this dropdown is placed
     * @param {FlexCss.Widget} [thisWidget] if given will use widget instead of widget instance
     * @return {FlexCss.Dropdown}
     */
    createDropdown(target, thisWidget) {
        const doc = global.document;

        if (!target) {
            throw new Error('Dropdown target not found');
        }

        const widget = thisWidget || Widget.findWidget(target);
        let future;
        const data = target.getAttribute(ATTR_NAME);
        const dropdownContainerElement = doc.getElementById(data);
        const async = !dropdownContainerElement && Widget.isWidget(widget) ? widget.getAsync() : false;

        if (async) {
            target.classList.add(STATE_LOADING);
            target.isLoading = true;
            future = async.then((r) => {
                if (r instanceof HTMLElement) {
                    if (r.id) {
                        target.setAttribute(ATTR_NAME, r.id);
                    }
                    return r;
                }
                // Create container Element:
                const element = doc.createElement('div');
                element.className = CLS_DROPDOWN;
                element.innerHTML = r;
                element.id = Util.guid();
                // Cache target for later use:
                target.setAttribute(ATTR_NAME, element.id);
                this.container.appendChild(element);
                return element;
            }).then((r) => {
                target.isLoading = false;
                target.classList.remove(STATE_LOADING);
                return r;
            });
        } else {
            if (!dropdownContainerElement) {
                throw new Error(`Could not found Dropdown container with ID "${data}"`);
            }
            future = new Promise((r) => {
                r(dropdownContainerElement);
            });
        }

        future.then((dropdownContent) => {
            if (this.currentOpen) {
                this.close();
            }
            // Skip one frame to show animation
            target.dropdownContent = dropdownContent;
            const isAbsolute = global.getComputedStyle(dropdownContent).position === 'absolute';
            dropdownContent.hfWidgetInstance = this;

            if (!target.flexCollisionContainer) {
                const collisionC = target.getAttribute(ATTR_CC);
                target.flexCollisionContainer = collisionC ?
                doc.getElementById(collisionC) || document.documentElement : document.documentElement;
            }

            dropdownContent.classList.toggle(CLS_OPEN);
            if (dropdownContent.classList.contains(CLS_OPEN)) {
                this.currentOpen = dropdownContent;
                this.currentTarget = target;
            }
            FixedWindow.getInstance().open(this, dropdownContent);
            if (isAbsolute) {
                // Check collision:
                let selfTarget = target.getAttribute(ATTR_DATA_TARGET);
                selfTarget = selfTarget ? doc.getElementById(selfTarget) : target;
                Util.setupPositionNearby(selfTarget, dropdownContent, target.flexCollisionContainer);
            } else {
                // optionally get custom darkener container for target
                const d = target.getAttribute(ATTR_DARKENER);
                if (d) {
                    dropdownContent.flexDarkenerInstance = doc.getElementById(d);
                    (dropdownContent.flexDarkenerInstance || this.darkener).classList.toggle(DARKENER_INIT);
                } else {
                    this.darkener.classList.toggle(DARKENER_INIT);
                }
                this.toggleDarkenerToggler(dropdownContent.flexDarkenerInstance || this.darkener, true);

                dropdownContent.style.left = '0';
                dropdownContent.style.top = 'auto';
            }
        });
    }

}

export default Dropdown;
<|endoftext|>


<commit_before>import Promise from 'bluebird'
import Immutable from 'immutable'
import {flatten, forEach, omit} from 'lodash'
import {v4 as uuid} from 'node-uuid'

import {version as currentVersionString} from '../../package.json'

import {randomChar, countCredits} from 'sto-helpers'

import Schedule from './schedule'
import Study from './study'

import checkStudentGraduatability from '../helpers/checkStudentGraduatability'

const StudentRecord = Immutable.Record({
	id: null,
	name: null,
	version: currentVersionString,

	creditsNeeded: 35,

	matriculation: 1894,
	graduation: 1898,

	dateLastModified: null,
	dateCreated: null,

	studies: Immutable.OrderedMap(),
	schedules: Immutable.OrderedMap(),
	overrides: Immutable.OrderedMap(),
	fabrications: Immutable.OrderedMap(),

	settings: Immutable.Map(),
})

class Student extends StudentRecord {
	constructor(encodedStudent={}) {
		// const startTime = present()
		// Don't pass the list params into the StudentRecord constructor; it creates them as JS objects,
		// instead of our custom Studies, Schedules, and such.
		const toRemove = ['studies', 'schedules', 'overrides', 'fabrications', 'settings']
		const filtered = omit(encodedStudent, toRemove)
		const immutableStudent = Immutable.fromJS(filtered)

		super(immutableStudent)

		// Then add them manually.
		if (encodedStudent) {
			return this.withMutations((student) => {
				student = student.set('id', encodedStudent.id || uuid())
				student = student.set('name', encodedStudent.name || 'Student ' + randomChar())
				student = student.set('dateCreated', encodedStudent.dateCreated || new Date())
				student = student.set('dateLastModified', encodedStudent.dateLastModified || new Date())

				forEach((encodedStudent.studies || []), study => {
					student = student.addArea(study)
				})

				forEach((encodedStudent.schedules || []), schedule => {
					student = student.addSchedule(schedule)
				})

				forEach((encodedStudent.overrides || []), (val, key) => {
					student = student.addOverride({[key]: val})
				})

				forEach((encodedStudent.fabrications || []), fabrication => {
					student = student.addFabrication(fabrication)
				})

				forEach((encodedStudent.settings || {}), (value, key) => {
					student = student.changeSetting(key, value)
				})

				student = student.set('version', currentVersionString)

				// console.log('it took', present() - startTime, 'ms to make a student')

				return student
			})
		}
	}

	changeName(newName) {
		return this.set('name', newName)
	}

	changeCreditsNeeded(newCreditsNeeded) {
		return this.set('creditsNeeded', newCreditsNeeded)
	}

	changeMatriculation(newMatriculation) {
		return this.set('matriculation', newMatriculation)
	}

	changeGraduation(newGraduation) {
		return this.set('graduation', newGraduation)
	}

	changeSetting(key, value) {
		return this.setIn(['settings', key], value)
	}

	get graduatability() {
		return checkStudentGraduatability(this)
	}

	// schedule methods

	get schedulesByYear() {
		return this.schedules.groupBy(sched => sched.year)
	}

	get activeSchedules() {
		return this.schedules.filter(sched => sched.active).sortBy(sched => sched.semester)
	}

	addSchedule(newSchedule) {
		const sched = new Schedule(newSchedule)
		return this.setIn(['schedules', sched.id], sched)
	}

	destroySchedule(scheduleId) {
		// console.log(`removing schedule ${scheduleId}`)

		const deadSched = this.getIn(['schedules', scheduleId])
		const scheduleIsNoMore = this.set('schedules', this.schedules.delete(scheduleId))

		if (deadSched && deadSched.active) {
			const otherSchedKey = scheduleIsNoMore.findKey((sched) =>
				sched.year === deadSched.year &&
				sched.semester === deadSched.semester &&
				sched.id !== deadSched.id)

			if (otherSchedKey) {
				return scheduleIsNoMore.setIn(['schedules', otherSchedKey, 'active'], true)
			}
		}
		return scheduleIsNoMore
	}

	destroyMultipleSchedules(ids) {
		return this.withMutations((student) => {
			// console.groupCollapsed('destroyMultipleSchedules')
			Immutable.Seq(ids).forEach((id) => {
				// console.log('destroyMultipleSchedules', id)
				student = student.destroySchedule(id)
			})
			// console.groupEnd('destroyMultipleSchedules')
			return student
		})
	}

	moveCourse(fromScheduleId, toScheduleId, clbid) {
		// console.log(`moving course ${clbid} from schedule ${fromScheduleId} to schedule ${toScheduleId}`)
		return this.withMutations((student) => {
			student = student.setIn(['schedules', fromScheduleId], student.getIn(['schedules', fromScheduleId]).removeCourse(clbid))
			student = student.setIn(['schedules', toScheduleId], student.getIn(['schedules', toScheduleId]).addCourse(clbid))
		})
	}

	// area-of-study methods

	get areasByType() {
		return this.studies.groupBy(study => study.type || 'Unknown')
	}

	addArea(areaOfStudy) {
		const study = new Study(areaOfStudy)
		return this.setIn(['studies', study.id], study)
	}

	removeArea(id) {
		return this.set('studies', this.studies.delete(id))
	}

	removeMultipleAreas(ids) {
		return this.withMutations((student) => {
			Immutable.Seq(ids).forEach((id) => {
				student = student.removeArea(id)
			})
			return student
		})
	}

	// override methods

	addOverride(override) {
		return this.withMutations(student => {
			forEach(override, (val, key) => {
				student.setIn(['overrides', key], val)
			})
		})
	}

	removeOverride(thingOverriden) {
		return this.set('overrides', this.overrides.delete(thingOverriden))
	}

	// fabrication methods

	addFabrication(fabrication) {
		return this.setIn(['fabrications', fabrication.id], fabrication)
	}

	removeFabrication(fabricationId) {
		return this.set('fabrications', this.fabrications.delete(fabricationId))
	}

	// getters

	get courses() {
		const allCourses = this.activeSchedules
			.map((schedule) => schedule.courses)
			.toArray()
		const scheduleCoursePromises = Promise.all(allCourses)
		return scheduleCoursePromises.then((scheduleCourses) => flatten(scheduleCourses))
	}

	get creditCount() {
		return this.courses.then(countCredits)
	}

	// helpers

	data() {
		return Promise.props({
			courses: this.courses,
			fabrications: this.fabrications.toList().toJS(),
			overrides: this.overrides.toList().toJS(),
			studies: this.studies.toList().toJS(),
			creditsNeeded: this.creditsNeeded,
			graduation: this.graduation,
			matriculation: this.matriculation,
		})
	}

	encode() {
		return encodeURIComponent(JSON.stringify(this))
	}

	save() {
		const oldVersion = localStorage.getItem(this.id)
		const stringified = JSON.stringify(this)
		if (oldVersion !== stringified) {
			console.log(`saving student ${this.name} (${this.id})`)
			const student = this.set('dateLastModified', new Date())
			localStorage.setItem(student.id, stringified)
		}
	}

	toJSON() {
		return {
			id: this.id,
			name: this.name,
			version: this.version,
			creditsNeeded: this.creditsNeeded,
			matriculation: this.matriculation,
			graduation: this.graduation,
			studies: this.studies.toList(),
			schedules: this.schedules.toList(),
			overrides: this.overrides.toMap(),
			fabrications: this.fabrications.toList(),
			settings: this.settings.toMap(),
		}
	}
}

export default Student
<commit_msg>add a debug: call to Student<commit_after>import Promise from 'bluebird'
import Immutable from 'immutable'
import {flatten, forEach, omit} from 'lodash'
import {v4 as uuid} from 'node-uuid'

import {version as currentVersionString} from '../../package.json'

import {randomChar, countCredits} from 'sto-helpers'

import Schedule from './schedule'
import Study from './study'

import checkStudentGraduatability from '../helpers/checkStudentGraduatability'

import present from 'present'
import debug from 'debug'
const changelog = debug('gobbldygook:model:student:changes')

const StudentRecord = Immutable.Record({
	id: null,
	name: null,
	version: currentVersionString,

	creditsNeeded: 35,

	matriculation: 1894,
	graduation: 1898,

	dateLastModified: null,
	dateCreated: null,

	studies: Immutable.OrderedMap(),
	schedules: Immutable.OrderedMap(),
	overrides: Immutable.OrderedMap(),
	fabrications: Immutable.OrderedMap(),

	settings: Immutable.Map(),
})

class Student extends StudentRecord {
	constructor(encodedStudent={}) {
		// Don't pass the list params into the StudentRecord constructor; it creates them as JS objects,
		// instead of our custom Studies, Schedules, and such.
		const startTime = present()

		const toRemove = ['studies', 'schedules', 'overrides', 'fabrications', 'settings']
		const filtered = omit(encodedStudent, toRemove)
		const immutableStudent = Immutable.fromJS(filtered)

		super(immutableStudent)

		// Then add them manually.
		if (encodedStudent) {
			return this.withMutations((student) => {
				student = student.set('id', encodedStudent.id || uuid())
				student = student.set('name', encodedStudent.name || 'Student ' + randomChar())
				student = student.set('dateCreated', encodedStudent.dateCreated || new Date())
				student = student.set('dateLastModified', encodedStudent.dateLastModified || new Date())

				forEach((encodedStudent.studies || []), study => {
					student = student.addArea(study)
				})

				forEach((encodedStudent.schedules || []), schedule => {
					student = student.addSchedule(schedule)
				})

				forEach((encodedStudent.overrides || []), (val, key) => {
					student = student.addOverride({[key]: val})
				})

				forEach((encodedStudent.fabrications || []), fabrication => {
					student = student.addFabrication(fabrication)
				})

				forEach((encodedStudent.settings || {}), (value, key) => {
					student = student.changeSetting(key, value)
				})

				student = student.set('version', currentVersionString)

				changelog(`it took ${present() - startTime} ms to make a student`)

				return student
			})
		}
	}

	changeName(newName) {
		return this.set('name', newName)
	}

	changeCreditsNeeded(newCreditsNeeded) {
		return this.set('creditsNeeded', newCreditsNeeded)
	}

	changeMatriculation(newMatriculation) {
		return this.set('matriculation', newMatriculation)
	}

	changeGraduation(newGraduation) {
		return this.set('graduation', newGraduation)
	}

	changeSetting(key, value) {
		return this.setIn(['settings', key], value)
	}

	get graduatability() {
		return checkStudentGraduatability(this)
	}

	// schedule methods

	get schedulesByYear() {
		return this.schedules.groupBy(sched => sched.year)
	}

	get activeSchedules() {
		return this.schedules.filter(sched => sched.active).sortBy(sched => sched.semester)
	}

	addSchedule(newSchedule) {
		const sched = new Schedule(newSchedule)
		return this.setIn(['schedules', sched.id], sched)
	}

	destroySchedule(scheduleId) {
		changelog(`removing schedule ${scheduleId}`)

		const deadSched = this.getIn(['schedules', scheduleId])
		const scheduleIsNoMore = this.set('schedules', this.schedules.delete(scheduleId))

		if (deadSched && deadSched.active) {
			const otherSchedKey = scheduleIsNoMore.findKey((sched) =>
				sched.year === deadSched.year &&
				sched.semester === deadSched.semester &&
				sched.id !== deadSched.id)

			if (otherSchedKey) {
				return scheduleIsNoMore.setIn(['schedules', otherSchedKey, 'active'], true)
			}
		}
		return scheduleIsNoMore
	}

	destroyMultipleSchedules(ids) {
		return this.withMutations((student) => {
			changelog('destroyMultipleSchedules', ids)
			// console.groupCollapsed('destroyMultipleSchedules')
			Immutable.Seq(ids).forEach((id) => {
				// console.log('destroyMultipleSchedules', id)
				student = student.destroySchedule(id)
			})
			// console.groupEnd('destroyMultipleSchedules')
			return student
		})
	}

	moveCourse(fromScheduleId, toScheduleId, clbid) {
		changelog(`moving course ${clbid} from schedule ${fromScheduleId} to schedule ${toScheduleId}`)
		return this.withMutations((student) => {
			student = student.setIn(['schedules', fromScheduleId], student.getIn(['schedules', fromScheduleId]).removeCourse(clbid))
			student = student.setIn(['schedules', toScheduleId], student.getIn(['schedules', toScheduleId]).addCourse(clbid))
		})
	}

	// area-of-study methods

	get areasByType() {
		return this.studies.groupBy(study => study.type || 'Unknown')
	}

	addArea(areaOfStudy) {
		const study = new Study(areaOfStudy)
		return this.setIn(['studies', study.id], study)
	}

	removeArea(id) {
		return this.set('studies', this.studies.delete(id))
	}

	removeMultipleAreas(ids) {
		return this.withMutations((student) => {
			Immutable.Seq(ids).forEach((id) => {
				student = student.removeArea(id)
			})
			return student
		})
	}

	// override methods

	addOverride(override) {
		return this.withMutations(student => {
			forEach(override, (val, key) => {
				student.setIn(['overrides', key], val)
			})
		})
	}

	removeOverride(thingOverriden) {
		return this.set('overrides', this.overrides.delete(thingOverriden))
	}

	// fabrication methods

	addFabrication(fabrication) {
		return this.setIn(['fabrications', fabrication.id], fabrication)
	}

	removeFabrication(fabricationId) {
		return this.set('fabrications', this.fabrications.delete(fabricationId))
	}

	// getters

	get courses() {
		const allCourses = this.activeSchedules
			.map((schedule) => schedule.courses)
			.toArray()
		const scheduleCoursePromises = Promise.all(allCourses)
		return scheduleCoursePromises.then((scheduleCourses) => flatten(scheduleCourses))
	}

	get creditCount() {
		return this.courses.then(countCredits)
	}

	// helpers

	data() {
		return Promise.props({
			courses: this.courses,
			fabrications: this.fabrications.toList().toJS(),
			overrides: this.overrides.toList().toJS(),
			studies: this.studies.toList().toJS(),
			creditsNeeded: this.creditsNeeded,
			graduation: this.graduation,
			matriculation: this.matriculation,
		})
	}

	encode() {
		return encodeURIComponent(JSON.stringify(this))
	}

	save() {
		const oldVersion = localStorage.getItem(this.id)
		const stringified = JSON.stringify(this)
		if (oldVersion !== stringified) {
			console.log(`saving student ${this.name} (${this.id})`)
			const student = this.set('dateLastModified', new Date())
			localStorage.setItem(student.id, stringified)
		}
	}

	toJSON() {
		return {
			id: this.id,
			name: this.name,
			version: this.version,
			creditsNeeded: this.creditsNeeded,
			matriculation: this.matriculation,
			graduation: this.graduation,
			studies: this.studies.toList(),
			schedules: this.schedules.toList(),
			overrides: this.overrides.toMap(),
			fabrications: this.fabrications.toList(),
			settings: this.settings.toMap(),
		}
	}
}

export default Student
<|endoftext|>


<commit_before>/**
* This file is part of taolin project (http://taolin.fbk.eu)
* Copyright (C) 2008, 2009 FBK Foundation, (http://www.fbk.eu)
* Authors: SoNet Group (see AUTHORS.txt)
*
* Taolin 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 version 3 of the License.
*
* Taolin 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 Taolin. If not, see <http://www.gnu.org/licenses/>.
*
*/

/**
  * Ext.ux.fbk.sonet.FeedReader Extension Class
  *
  * @author  Marco Frassoni and Davide Setti
  * @class Ext.ux.fbk.sonet.FeedReader
  *
With this widget a user can display a external rss resource

  *

    This is a example of the json

*/

FeedReader = function(conf, panel_conf){
    Ext.apply(this, panel_conf);

    var feedId = this.getId();

    var nItems = 5;
    if (conf.items){
        nItems = parseInt(conf.items, 10);
    }
    this.autoExpand = conf.autoExpand;

    var store = new Ext.data.Store({
        proxy: new Ext.data.HttpProxy({
            url: 'wrappers/getrss'
        }),
        reader: new Ext.data.JsonReader({
            root: 'items',
            totalProperty: 'totalCount'
        }, [{
            name: 'title'
        }, {
            name: 'url'
        }, {
            name: 'description'
        }, {
            name: 'date'
        }]),
        baseParams: {
            limit: nItems
            ,url: conf.url
        }
        ,parent: this
        ,listeners: {
            load: function(store, records, options) {
                var widget = this.parent
                    ,data = store.reader.jsonData
                    ,id = widget.getId()
                    ,logo = $('#'+id+'_logo');

                if (data.title)
                    widget.setPortletTitle(data.title);

                //expand rows if autoExpand is true
                if (widget.autoExpand){
                    widget.expandAll();
                }

                if (data.image_url){
                    var width = data.image_width
                        ,height = data.image_height
                        ,min_height = 30
                        ,max_height = 50;

                    if (height > max_height){
                        width *= max_height/height;
                        height = max_height;
                    } else if (height < min_height) {
                        logo.css('padding', ((min_height - height)/2)+'px 0');
                    }

                    $('#'+id+'_logo')
                        .attr('src', data.image_url)
                        .style({
                            width: width
                            ,height: height
                        });
                }
                else {
                    logo.remove();
                }
            }
        }
    });

    this.expander = new Ext.grid.RowExpander({
        tpl: new Ext.XTemplate(
            '
Summary: {description}
',
            '
more...',
            'Email to |
'
            ,{
                compiled: true
            }
        )
        ,listeners: {
            expand: function(){
                //don't open links in this tab!
                $('#'+ this.parent.getId() +' .description a').attr('target', '_blank');
            }
        }
        ,parent: this
     });

    this.grid = new Ext.grid.GridPanel({
        autoHeight: true,
        //autoWidth: true,
        cls: 'feedreader-grid',
        viewConfig: {
            forceFit:true,
            scrollOffset:0
        },
        hideHeaders: true,
        bbar: new Ext.PagingToolbar({
            store: store,
            beforePageText: '',
            pageSize: nItems,
            displayInfo: true,
            displayMsg: '{0} - {1} of {2}',
            emptyMsg: 'No news'
        }),
        store: store,
        parent: this,
        cm: new Ext.grid.ColumnModel([
            this.expander,
            {
                id:'title',
                renderer: function(value, p, record) {
                    htmlForNewItems="";
                    if(record.get('date')){
                        /** if current date on client is between
                         *  date of post minus -365
                         *  and
                         *  date of post plus 5 (there are some feeds with wrong dates (for example year is 2015 and we are in 2008, need to decide what to do for those feeds)
                         *  (would be better to check the date on server for reasons of timezones and users modifying its time but for now this is not needed)
                         */
                        var date = Date.parseDate(record.get('date'),'d/m/Y');
                        var startWindowDate = date.add(Date.DAY, -365);
                        var endWindowDate = date.add(Date.DAY, +5);
                        if(new Date().between(startWindowDate,endWindowDate)) {
                            //TODO: add css to this and remove ! Maybe an icon! In famfamfam there is a "new" icon!!!
                            //htmlForNewItems="NEW!";
                            p.css = "new-rss-item"
                        }
                    }
                    value = String.format('{0} {1} {2}',
                        record.get('title'),
                        ""+record.get('date')+"",
                        htmlForNewItems
                    );
                    return value;
                }
            }
        ]),
        plugins: this.expander,
        listeners: {
            cellclick:function(grid, rowIndex, columnIndex, e) {
                /* if columnIndex > 0 the click is on the title
                 * if column 0 is hidden, we're in full screen and we want
                 * to disable the expander
                 */
                if (columnIndex && !grid.getColumnModel().isHidden(0)) {
                    grid.parent.expander.toggleRow(rowIndex);
                }
            }
        },
        sendTo: function() {
            var text =  Ext.util.Format.stripTags(this.getSelectionModel().getSelected().data.description.replace(/\\n/g,"
"));
            var prefix = "Hi all!\nI think that you may be interested in:\n\n";
            var logparams = {
                source: "feedreader widget",
                widget_id: this.parent.portlet_id
            };
            new SendToWindow(prefix+text, null, logparams);
        }
    });

    this.expandAll = function() {
        var store = this.grid.store;
        for(var i = store.data.length-1; i>=0; i--)
            this.expander.expandRow(i);
    };

    this.collapseAll = function() {
        var store = this.grid.store;
        for(var i = store.data.length-1; i>=0; i--)
            this.expander.collapseRow(i);
    }

    FeedReader.superclass.constructor.call(this, {
        items: [
            {
                html: ''
            }
            ,this.grid
        ]
        ,listeners:{
            bodyresize: function(panel, panelWidth, panelHeight){
                panel.grid.setWidth(panelWidth);
            }
            ,fullscreen: function(t){
                t.grid.getColumnModel().setHidden(0, true);
                t.expandAll();
            }
            ,downsize: function(t){
                t.grid.getColumnModel().setHidden(0, false);
                t.collapseAll();
            }
        }
    });

    store.load({
        params: {
            start: 0
        }
    });
};

Ext.extend(FeedReader, Ext.Panel);
<commit_msg>Bug fixing<commit_after>/**
* This file is part of taolin project (http://taolin.fbk.eu)
* Copyright (C) 2008, 2009 FBK Foundation, (http://www.fbk.eu)
* Authors: SoNet Group (see AUTHORS.txt)
*
* Taolin 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 version 3 of the License.
*
* Taolin 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 Taolin. If not, see <http://www.gnu.org/licenses/>.
*
*/

/**
  * Ext.ux.fbk.sonet.FeedReader Extension Class
  *
  * @author  Marco Frassoni and Davide Setti
  * @class Ext.ux.fbk.sonet.FeedReader
  *
With this widget a user can display a external rss resource

  *

    This is a example of the json

*/

FeedReader = function(conf, panel_conf){
    Ext.apply(this, panel_conf);

    var feedId = this.getId();

    var nItems = 5;
    if (conf.items){
        nItems = parseInt(conf.items, 10);
    }
    this.autoExpand = conf.autoExpand;

    var store = new Ext.data.Store({
        proxy: new Ext.data.HttpProxy({
            url: 'wrappers/getrss'
        }),
        reader: new Ext.data.JsonReader({
            root: 'items',
            totalProperty: 'totalCount'
        }, [{
            name: 'title'
        }, {
            name: 'url'
        }, {
            name: 'description'
        }, {
            name: 'date'
        }]),
        baseParams: {
            limit: nItems
            ,url: conf.url
        }
        ,parent: this
        ,listeners: {
            load: function(store, records, options) {
                var widget = this.parent
                    ,data = store.reader.jsonData
                    ,id = widget.getId()
                    ,logo = $('#'+id+'_logo');

                if (data.title)
                    widget.setPortletTitle(data.title);

                //expand rows if autoExpand is true
                if (widget.autoExpand){
                    widget.expandAll();
                }

                if (data.image_url){
                    var width = data.image_width
                        ,height = data.image_height
                        ,min_height = 30
                        ,max_height = 50;

                    if (height > max_height){
                        width *= max_height/height;
                        height = max_height;
                    } else if (height < min_height) {
                        logo.css('padding', ((min_height - height)/2)+'px 0');
                    }

                    logo
                        .attr('src', data.image_url)
                        .css({
                            width: width
                            ,height: height
                        });
                }
                else {
                    logo.remove();
                }
            }
        }
    });

    this.expander = new Ext.grid.RowExpander({
        tpl: new Ext.XTemplate(
            '
Summary: {description}
',
            '
more...',
            'Email to |
'
            ,{
                compiled: true
            }
        )
        ,listeners: {
            expand: function(){
                //don't open links in this tab!
                $('#'+ this.parent.getId() +' .description a').attr('target', '_blank');
            }
        }
        ,parent: this
     });

    this.grid = new Ext.grid.GridPanel({
        autoHeight: true,
        //autoWidth: true,
        cls: 'feedreader-grid',
        viewConfig: {
            forceFit:true,
            scrollOffset:0
        },
        hideHeaders: true,
        bbar: new Ext.PagingToolbar({
            store: store,
            beforePageText: '',
            pageSize: nItems,
            displayInfo: true,
            displayMsg: '{0} - {1} of {2}',
            emptyMsg: 'No news'
        }),
        store: store,
        parent: this,
        cm: new Ext.grid.ColumnModel([
            this.expander,
            {
                id:'title',
                renderer: function(value, p, record) {
                    htmlForNewItems="";
                    if(record.get('date')){
                        /** if current date on client is between
                         *  date of post minus -365
                         *  and
                         *  date of post plus 5 (there are some feeds with wrong dates (for example year is 2015 and we are in 2008, need to decide what to do for those feeds)
                         *  (would be better to check the date on server for reasons of timezones and users modifying its time but for now this is not needed)
                         */
                        var date = Date.parseDate(record.get('date'),'d/m/Y');
                        var startWindowDate = date.add(Date.DAY, -365);
                        var endWindowDate = date.add(Date.DAY, +5);
                        if(new Date().between(startWindowDate,endWindowDate)) {
                            //TODO: add css to this and remove ! Maybe an icon! In famfamfam there is a "new" icon!!!
                            //htmlForNewItems="NEW!";
                            p.css = "new-rss-item"
                        }
                    }
                    value = String.format('{0} {1} {2}',
                        record.get('title'),
                        ""+record.get('date')+"",
                        htmlForNewItems
                    );
                    return value;
                }
            }
        ]),
        plugins: this.expander,
        listeners: {
            cellclick:function(grid, rowIndex, columnIndex, e) {
                /* if columnIndex > 0 the click is on the title
                 * if column 0 is hidden, we're in full screen and we want
                 * to disable the expander
                 */
                if (columnIndex && !grid.getColumnModel().isHidden(0)) {
                    grid.parent.expander.toggleRow(rowIndex);
                }
            }
        },
        sendTo: function() {
            var text =  Ext.util.Format.stripTags(this.getSelectionModel().getSelected().data.description.replace(/\\n/g,"
"));
            var prefix = "Hi all!\nI think that you may be interested in:\n\n";
            var logparams = {
                source: "feedreader widget",
                widget_id: this.parent.portlet_id
            };
            new SendToWindow(prefix+text, null, logparams);
        }
    });

    this.expandAll = function() {
        var store = this.grid.store;
        for(var i = store.data.length-1; i>=0; i--)
            this.expander.expandRow(i);
    };

    this.collapseAll = function() {
        var store = this.grid.store;
        for(var i = store.data.length-1; i>=0; i--)
            this.expander.collapseRow(i);
    }

    FeedReader.superclass.constructor.call(this, {
        items: [
            {
                html: ''
            }
            ,this.grid
        ]
        ,listeners:{
            bodyresize: function(panel, panelWidth, panelHeight){
                panel.grid.setWidth(panelWidth);
            }
            ,fullscreen: function(t){
                t.grid.getColumnModel().setHidden(0, true);
                t.expandAll();
            }
            ,downsize: function(t){
                t.grid.getColumnModel().setHidden(0, false);
                t.collapseAll();
            }
        }
    });

    store.load({
        params: {
            start: 0
        }
    });
};

Ext.extend(FeedReader, Ext.Panel);
<|endoftext|>


<commit_before>function KingPowerful(session){
	this.session=session;
	this.canRepeat = true;
	this.player1 = null;
	this.player2 = null;

	this.trigger = function(){
		this.player1 = null;
		this.player2 = null;
		/*
			two ways I can do this:
			Either I can select a player at random, and if they are in a quadrant, return true.
			OR, pass a random number test, then  filter players down to only those who are in a quadrant, and return true.

			First one is probably easiest, but hardest to modify rate of triggering.
			Stop picking the laziest way to do things, dunkass.
		*/
		if(Math.seededRandom() > 0.5){
			findRandomQuadrantedAvailablePlayer();
			findQuardrantMate();
		}
		return false;
	}

	this.findRandomQuadrantedAvailablePlayer(){
		//set this.player1 to be a random quadranted player.
	}

	this.findQuardrantMate(){
		//set this.player2 to be one of player1's quadrant mates. first diamond, then heart, then spade, then clubs.
	}

	this.renderContent = function(div){
		if(this.player1.aspect != "Time") removeFromArray(this.player1, this.session.availablePlayers);
		if(this.player2.aspect != "Time") removeFromArray(this.player2, this.session.availablePlayers);

		/*
				Since this dialogue has no "purpose", no information that HAS to be conveyed, can treat it as more dynamic.
				Go for bullshit elder scrolls pairs.

				Greeting (based on quadrant, not generic greetings)
				Greeting

				chatPair1, chatPair2, chatPair3

				where chatPair is a call and response about one of several topics, each of which have multiple random call/response things it can deploy

				Have you heard about Kvatch?
				No.

				I have been to the Imperial City recently.
				I've heard others say the same.

				I have nothing more to say to you.
				Good day.

				<3<3<3 elder scrolls. They are such ASSHOLES to each other.

				Chat pairs can be generated from: interests in common, quadrants, and relationship value (quardrant and value being separate allows impending breakups to be foreshadowed.)
				ALSO, flipOutReason (but only for diamonds. so maybe that's just a quadrant thing.)

		*/

	}

	this.content = function(){
		return "NEVER RUN IN 1.0 YOU DUNKASS."
	}

}
<commit_msg>planning quadrantDialog<commit_after>function KingPowerful(session){
	this.session=session;
	this.canRepeat = true;
	this.player1 = null;
	this.player2 = null;

	this.trigger = function(){
		this.player1 = null;
		this.player2 = null;
		/*
			two ways I can do this:
			Either I can select a player at random, and if they are in a quadrant, return true.
			OR, pass a random number test, then  filter players down to only those who are in a quadrant, and return true.

			First one is probably easiest, but hardest to modify rate of triggering.
			Stop picking the laziest way to do things, dunkass.
		*/
		if(Math.seededRandom() > 0.5){
			findRandomQuadrantedAvailablePlayer();
			findQuardrantMate();
		}
		return false;
	}

	this.findRandomQuadrantedAvailablePlayer(){
		//set this.player1 to be a random quadranted player.
	}

	this.findQuardrantMate(){
		//set this.player2 to be one of player1's quadrant mates. first diamond, then heart, then spade, then clubs.
	}

	this.getDiscussSymbol(){
		//TODO, turn which quadrant player1 and player2 are in into a png to pass.  Create pngs for diamonds and clubs.
	}

	this.getChat =function(){
		var trait = whatDoPlayersHaveInCommon(this.player1, this.player2);
		if(trait != "nice"){
			if(Math.seededRandom() > 0.5){
				return this.chatAboutInterests(trait);
			}else{
				if(Math.seededRandom() > 0.5){
					return this.chatAboutQuadrant();
				}else{
					return this.chatAboutRelationshipValue();
				}
			}
		}else{  //no option to chat about interests.
			if(Math.seededRandom() > 0.5){
				return this.chatAboutQuadrant();
			}else{
				return this.chatAboutRelationshipValue();
			}
		}
	}

	this.chat = function(div){
		var canvasHTML = "<br><canvas id='canvas" + (div.attr("id")) +"' width='" +canvasWidth + "' height="+canvasHeight + "'>  </canvas>";
		div.append(canvasHTML);
		var chatText = this.getChat();
		drawChat(document.getElementById("canvas"+ (div.attr("id"))), this.player1, this.player2, chatText, repeatTime,this.getDiscussSymbol());
	}

	this.renderContent = function(div){
		if(this.player1.aspect != "Time") removeFromArray(this.player1, this.session.availablePlayers);
		if(this.player2.aspect != "Time") removeFromArray(this.player2, this.session.availablePlayers);

		/*
				Since this dialogue has no "purpose", no information that HAS to be conveyed, can treat it as more dynamic.
				Go for bullshit elder scrolls pairs.

				Greeting (based on quadrant, not generic greetings)
				Greeting

				chatPair1, chatPair2, chatPair3

				where chatPair is a call and response about one of several topics, each of which have multiple random call/response things it can deploy

				Have you heard about Kvatch?
				No.

				I have been to the Imperial City recently.
				I've heard others say the same.

				I have nothing more to say to you.
				Good day.

				<3<3<3 elder scrolls. They are such ASSHOLES to each other.

				Chat pairs can be generated from: interests in common, quadrants, and relationship value (quardrant and value being separate allows impending breakups to be foreshadowed.)
				ALSO, flipOutReason (but only for diamonds. so maybe that's just a quadrant thing.)

		*/
		this.chat(div);

	}

	this.content = function(){
		return "NEVER RUN IN 1.0 YOU DUNKASS."
	}

}
<|endoftext|>


<commit_before>/*
 * Chat Window Class
 */

/*
 * Ext JS Library 2.0.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 *
 * http://extjs.com/license
 */

/**
  * Ext.ux.fbk.sonet.ChatWindow Extension Class
  *
  * @author  Marco Frassoni and Davide Setti and Yakalope
  * @class Ext.ux.fbk.sonet.ChatWindow
  *
This window chat

  *

    This is a example of the json

*/

ChatWindow = Ext.extend(Ext.Window, {

    /* Construction Time Variables */

    width: 250,
    height: 250,
    iconCls: "chatwindowicon",
    //constrain: true,
    border:false,
    collapsible:true,
    draggable:false,
    resizable:false,
    animCollapse:false,
    constrain:true,
    closeAction: 'hide',
    layout:'anchor',
    cls:'comment',

    /* Runtime Variables */

    focus: function() {
        /* focus the area where the user write instead of the entire window*/
        jabberui.focused = this.getId();
        Ext.getCmp('msgarea'+this.getId()).focus('', 5);
    },

    sendHandler: function(key,event) {
        var msgArea = this.items.last();
        var chatMessage = msgArea.getValue();
        if (chatMessage !== '' && chatMessage !== '\n') {
            this.addMsg(this.user, chatMessage);
            msgArea.setValue('');
            //Send Message to Jabber Connection
            jabber.sendMsg(this.getId(), chatMessage);
        }
        msgArea.focus();
        event.stopEvent();
    },
    initComponent: function() {
        Ext.apply(this, {
            items: [{
                id:'msgarea'+this.getId(),
                layout:'fit',
                height:50,
                split:true,
                hideBorders:false,
                border:false,
                xtype:'textarea',
                hideLabel:true,
                maxLength:4000,
                maxLengthText:'The maximum length text for this field is 4000',
                style:'overflow: auto;',
                anchor:'100%',
                listeners: {
                    focus: function(t) {
                        Ext.getCmp(t.getId().substring(7)).setActive(true);
                    },
                    blur: function(t) {
                        Ext.getCmp(t.getId().substring(7)).setActive();
                    }
                }
            }],
            keys: [{
                key:13,
                fn:this.sendHandler,
                scope:this
            },{
                key:Ext.EventObject.TAB,
                fn:jabberui.focusNext,
                scope:jabberui
            }
            ]
        });
        ChatWindow.superclass.initComponent.apply(this, arguments);
        //jabberui.addChatToList(this.getId());
    },
    render: function() {
        /*
         * Set up chat, chatStore, and dataview
         * objects for use in the window
         */
        var chatId = 'chat' + this.getId();
        var chatStoreId = 'chatStore' + this.getId();
        var chat =  {
            id:chatId,
            msgs:[],
            update: function(msg, scope) {
                this.msgs.push(msg);
                scope.chatStore.load();
            },
            clear: function(scope) {
                this.msgs = new Array();
                scope.chatStore.load();
            }
        };
        var chatStore = new Ext.data.Store({
            id:chatStoreId,
            proxy: new Ext.data.MemoryProxy(chat),
            reader: new Ext.data.JsonReader({
                root:'msgs'},
            [
               {name: 'username'},
               {name: 'time'},
               {name: 'msg'}
            ]),
            listeners: {
                loadexception: function (o, responce, e, exception) {
                    Ext.MessageBox.alert('Error', exception);
                }
            }
        });

        var dataview = new Ext.DataView({
            id:'chatview' + this.getId()
            ,store:chatStore
            ,tpl: new Ext.XTemplate(
            '<tpl for=".">',
            '
{username} ({time}): {msg}
',
            '</tpl>')
            ,itemSelector:'div.msg'
        });

        var panel = new Ext.Panel({
            id:'chatpanel' + this.getId()
            ,items: dataview
            ,anchor:'100% -50'
            ,autoScroll: true
        });

        this.chat = chat;
        this.chatStore = chatStore;
        this.items.insert(0, panel.id, panel);

        ChatWindow.superclass.render.apply(this, arguments);
    },
    addMsg:function(userName, msg, timestamp) {
        var chat = Ext.getCmp(this.getId()).chat;

        msg = Ext.util.Format.htmlEncode(msg).urlize().smilize().replace(/\n/g,"
");
        if (userName !== this.user) {
            userName = userName.toString().split('@')[0];
        }
        if (!timestamp) {
            timestamp = new Date();
        }

        var sTime; //string containg formatted time
        //show day and month only if day != today
        if (timestamp.format('jmY') == (new Date()).format('jmY')){
            sTime = timestamp.format('G:i');
        } else {
            sTime = timestamp.format('G:i, M j');
        }

        var lMsg = {
            username: userName,
            time: sTime,
            msg: msg
        };
        chat.update(lMsg, this);
        var panel = Ext.getCmp('chatpanel'+this.getId());
        panel.body.scroll('down', 5000, true);

        // play sounds
        var tm = msg.trim();
        if ((soundManager.enabled) && (tm.split(' ', 1)[0] == '/play')){
            var i = tm.indexOf(' ');
            var sound = tm.substring(i).trim();

            if (sound == 'trombone') {
                soundManager.play('trombone', 'sound/sad_trombone.mp3');
            }
            else if (sound == 'moo') {
                soundManager.play('moo', 'sound/cow.mp3');
            }
            else if (sound == 'cheer') {
                soundManager.play('cheer', 'sound/cheer.mp3');
            }
            else if (sound == 'fart') {
                soundManager.play('fart', 'sound/fartus_tubartus.mp3');
            }
        }

        /* now do show signals if the message comes from another user */
        if (userName == this.user) return;

        // check also for document.hasFocus existence: WebKit has not yet implemented it
        if ((!this.isActive) || (typeof document.hasFocus == 'undefined') || (!document.hasFocus())) {
            this.visualBeep = true;
        }
    },
    listeners: {
        beforehide: function(t) {
            jabberui.removeChat(t.getId());
            jabberui.refreshChats();
        },
        beforeshow: function(t){
            jabberui.addChat(t.getId());
            jabberui.refreshChats();
        },
        collapse: function(panel){
            jabberui.refreshChats();
        },
        expand: function(panel){
            jabberui.refreshChats();
        },
        activate: function(t){
            t.isActive = true;
            t.visualBeep = false;
        },
        deactivate: function(t){
            t.isActive = false;
        }
    }
 });

 Ext.reg('chatwindow', ChatWindow);
<commit_msg>Re-changing class name<commit_after>/*
 * Chat Window Class
 */

/*
 * Ext JS Library 2.0.2
 * Copyright(c) 2006-2008, Ext JS, LLC.
 * licensing@extjs.com
 *
 * http://extjs.com/license
 */

/**
  * Ext.ux.fbk.sonet.ChatWindow Extension Class
  *
  * @author  Marco Frassoni and Davide Setti and Yakalope
  * @class Ext.ux.fbk.sonet.ChatWindow
  *
This window chat

  *

    This is a example of the json

*/

ChatWindow = Ext.extend(Ext.Window, {

    /* Construction Time Variables */

    width: 250,
    height: 250,
    iconCls: "chatwindowicon",
    //constrain: true,
    border:false,
    collapsible:true,
    draggable:false,
    resizable:false,
    animCollapse:false,
    constrain:true,
    closeAction: 'hide',
    layout:'anchor',
    cls:'comment-icon',

    /* Runtime Variables */

    focus: function() {
        /* focus the area where the user write instead of the entire window*/
        jabberui.focused = this.getId();
        Ext.getCmp('msgarea'+this.getId()).focus('', 5);
    },

    sendHandler: function(key,event) {
        var msgArea = this.items.last();
        var chatMessage = msgArea.getValue();
        if (chatMessage !== '' && chatMessage !== '\n') {
            this.addMsg(this.user, chatMessage);
            msgArea.setValue('');
            //Send Message to Jabber Connection
            jabber.sendMsg(this.getId(), chatMessage);
        }
        msgArea.focus();
        event.stopEvent();
    },
    initComponent: function() {
        Ext.apply(this, {
            items: [{
                id:'msgarea'+this.getId(),
                layout:'fit',
                height:50,
                split:true,
                hideBorders:false,
                border:false,
                xtype:'textarea',
                hideLabel:true,
                maxLength:4000,
                maxLengthText:'The maximum length text for this field is 4000',
                style:'overflow: auto;',
                anchor:'100%',
                listeners: {
                    focus: function(t) {
                        Ext.getCmp(t.getId().substring(7)).setActive(true);
                    },
                    blur: function(t) {
                        Ext.getCmp(t.getId().substring(7)).setActive();
                    }
                }
            }],
            keys: [{
                key:13,
                fn:this.sendHandler,
                scope:this
            },{
                key:Ext.EventObject.TAB,
                fn:jabberui.focusNext,
                scope:jabberui
            }
            ]
        });
        ChatWindow.superclass.initComponent.apply(this, arguments);
        //jabberui.addChatToList(this.getId());
    },
    render: function() {
        /*
         * Set up chat, chatStore, and dataview
         * objects for use in the window
         */
        var chatId = 'chat' + this.getId();
        var chatStoreId = 'chatStore' + this.getId();
        var chat =  {
            id:chatId,
            msgs:[],
            update: function(msg, scope) {
                this.msgs.push(msg);
                scope.chatStore.load();
            },
            clear: function(scope) {
                this.msgs = new Array();
                scope.chatStore.load();
            }
        };
        var chatStore = new Ext.data.Store({
            id:chatStoreId,
            proxy: new Ext.data.MemoryProxy(chat),
            reader: new Ext.data.JsonReader({
                root:'msgs'},
            [
               {name: 'username'},
               {name: 'time'},
               {name: 'msg'}
            ]),
            listeners: {
                loadexception: function (o, responce, e, exception) {
                    Ext.MessageBox.alert('Error', exception);
                }
            }
        });

        var dataview = new Ext.DataView({
            id:'chatview' + this.getId()
            ,store:chatStore
            ,tpl: new Ext.XTemplate(
            '<tpl for=".">',
            '
{username} ({time}): {msg}
',
            '</tpl>')
            ,itemSelector:'div.msg'
        });

        var panel = new Ext.Panel({
            id:'chatpanel' + this.getId()
            ,items: dataview
            ,anchor:'100% -50'
            ,autoScroll: true
        });

        this.chat = chat;
        this.chatStore = chatStore;
        this.items.insert(0, panel.id, panel);

        ChatWindow.superclass.render.apply(this, arguments);
    },
    addMsg:function(userName, msg, timestamp) {
        var chat = Ext.getCmp(this.getId()).chat;

        msg = Ext.util.Format.htmlEncode(msg).urlize().smilize().replace(/\n/g,"
");
        if (userName !== this.user) {
            userName = userName.toString().split('@')[0];
        }
        if (!timestamp) {
            timestamp = new Date();
        }

        var sTime; //string containg formatted time
        //show day and month only if day != today
        if (timestamp.format('jmY') == (new Date()).format('jmY')){
            sTime = timestamp.format('G:i');
        } else {
            sTime = timestamp.format('G:i, M j');
        }

        var lMsg = {
            username: userName,
            time: sTime,
            msg: msg
        };
        chat.update(lMsg, this);
        var panel = Ext.getCmp('chatpanel'+this.getId());
        panel.body.scroll('down', 5000, true);

        // play sounds
        var tm = msg.trim();
        if ((soundManager.enabled) && (tm.split(' ', 1)[0] == '/play')){
            var i = tm.indexOf(' ');
            var sound = tm.substring(i).trim();

            if (sound == 'trombone') {
                soundManager.play('trombone', 'sound/sad_trombone.mp3');
            }
            else if (sound == 'moo') {
                soundManager.play('moo', 'sound/cow.mp3');
            }
            else if (sound == 'cheer') {
                soundManager.play('cheer', 'sound/cheer.mp3');
            }
            else if (sound == 'fart') {
                soundManager.play('fart', 'sound/fartus_tubartus.mp3');
            }
        }

        /* now do show signals if the message comes from another user */
        if (userName == this.user) return;

        // check also for document.hasFocus existence: WebKit has not yet implemented it
        if ((!this.isActive) || (typeof document.hasFocus == 'undefined') || (!document.hasFocus())) {
            this.visualBeep = true;
        }
    },
    listeners: {
        beforehide: function(t) {
            jabberui.removeChat(t.getId());
            jabberui.refreshChats();
        },
        beforeshow: function(t){
            jabberui.addChat(t.getId());
            jabberui.refreshChats();
        },
        collapse: function(panel){
            jabberui.refreshChats();
        },
        expand: function(panel){
            jabberui.refreshChats();
        },
        activate: function(t){
            t.isActive = true;
            t.visualBeep = false;
        },
        deactivate: function(t){
            t.isActive = false;
        }
    }
 });

 Ext.reg('chatwindow', ChatWindow);
<|endoftext|>


<commit_before>(function () {
  "use strict"
  var root = this,
      $ = root.jQuery;

  if (typeof GOVUK === 'undefined') { root.GOVUK = {}; }

  var SelectionButtons = function (elms, opts) {
    this.selectedClass = 'selected';
    this.focusedClass = 'focused';
    if (opts !== undefined) {
      $.each(opts, function (optionName, optionObj) {
        this[optionName] = optionObj;
      }.bind(this));
    }
    this.setInitialState($(elms));
    this.setEventTypes(elms);
    this.bindEvents(elms);
  };
  SelectionButtons.prototype.setEventTypes = function (elms) {
    var selector,
        $elms;

    if (typeof elms === 'string') {
      selector = elms;
      $elms = $(selector);
      this.bindEvents = function ($elms) {
        this.bindDocumentLevelEvents(selector);
      }.bind(this);
    } else { // elms is a jQuery object
      $elms = elms;
      this.bindEvents = function ($elms) {
        this.bindElementLevelEvents($elms);
      }.bind(this);
    }
  };
  SelectionButtons.prototype.setInitialState = function ($elms) {
    $elms.each(function (idx, elm) {
      var $elm = $(elm);

      if ($elm.is(':checked')) {
        this.markSelected($elm);
      }
    }.bind(this));
  };
  SelectionButtons.prototype.markFocused = function ($elm, state) {
    if (state === 'focused') {
      $elm.parent('label').addClass(this.focusedClass);
    } else {
      $elm.parent('label').removeClass(this.focusedClass);
    }
  };
  SelectionButtons.prototype.markSelected = (function () {
    var markSelected = {
      'radio' : function ($elm) {
        var radioName = $elm.attr('name'),
            $radiosInGroup = $($elm[0].form).find('input[name="' + radioName + '"]');

        $radiosInGroup.parent('label').removeClass(this.selectedClass);
        $elm.parent('label').addClass(this.selectedClass);
      },
      'checkbox' : function ($elm) {
        if ($elm.is(':checked')) {
          $elm.parent('label').addClass(this.selectedClass);
        } else {
          $elm.parent('label').removeClass(this.selectedClass);
        }
      }
    };

    return function ($elm) {
      var inputType = $elm.attr('type');

      markSelected[inputType].call(this, $elm);
    };
  }());
  SelectionButtons.prototype.bindElementLevelEvents = function ($elms) {
    $elms
      .on('click', function (e) {
        this.markSelected($(e.target));
      }.bind(this))
      .on('focus blur', function (e) {
        var state = (e.type === 'focus') ? 'focused' : 'blurred';

        this.markFocused($(e.target), state);
      }.bind(this));
  };
  SelectionButtons.eventSelectors = [],
  SelectionButtons.prototype.bindDocumentLevelEvents = function (selector) {
    if ($.inArray(selector, SelectionButtons.eventSelectors) === -1) {
      $(document)
        .on('click', selector, function (e) {
          this.markSelected($(e.target));
        }.bind(this))
        .on('focus blur', selector, function (e) {
          var state = (e.type === 'focusin') ? 'focused' : 'blurred';

          this.markFocused($(e.target), state);
        }.bind(this));
      SelectionButtons.eventSelectors.push(selector);
    }
  };

  var selectionButtons = function (elms, opts) {
    new SelectionButtons(elms, opts);
  };
  selectionButtons.removeEventsFor = function (selector) {
    $(document)
      .off('click', selector)
      .off('focus blur', selector);
    SelectionButtons.eventSelectors = $.grep(SelectionButtons.eventSelectors, function (entry) {
      return entry !== selector;
    });
  };

  root.GOVUK.selectionButtons = selectionButtons;
}).call(this);
<commit_msg>Tidy up markSelected method<commit_after>(function () {
  "use strict"
  var root = this,
      $ = root.jQuery;

  if (typeof GOVUK === 'undefined') { root.GOVUK = {}; }

  var SelectionButtons = function (elms, opts) {
    this.selectedClass = 'selected';
    this.focusedClass = 'focused';
    if (opts !== undefined) {
      $.each(opts, function (optionName, optionObj) {
        this[optionName] = optionObj;
      }.bind(this));
    }
    this.setInitialState($(elms));
    this.setEventTypes(elms);
    this.bindEvents(elms);
  };
  SelectionButtons.prototype.setEventTypes = function (elms) {
    var selector,
        $elms;

    if (typeof elms === 'string') {
      selector = elms;
      $elms = $(selector);
      this.bindEvents = function ($elms) {
        this.bindDocumentLevelEvents(selector);
      }.bind(this);
    } else { // elms is a jQuery object
      $elms = elms;
      this.bindEvents = function ($elms) {
        this.bindElementLevelEvents($elms);
      }.bind(this);
    }
  };
  SelectionButtons.prototype.setInitialState = function ($elms) {
    $elms.each(function (idx, elm) {
      var $elm = $(elm);

      if ($elm.is(':checked')) {
        this.markSelected($elm);
      }
    }.bind(this));
  };
  SelectionButtons.prototype.markFocused = function ($elm, state) {
    if (state === 'focused') {
      $elm.parent('label').addClass(this.focusedClass);
    } else {
      $elm.parent('label').removeClass(this.focusedClass);
    }
  };
  SelectionButtons.prototype.markSelected = function ($elm) {
    var radioName;

    if ($elm.attr('type') === 'radio') {
      radioName = $elm.attr('name'),
      $($elm[0].form).find('input[name="' + radioName + '"]')
        .parent('label')
        .removeClass(this.selectedClass);
      $elm.parent('label').addClass(this.selectedClass);
    } else { // checkbox
      if ($elm.is(':checked')) {
        $elm.parent('label').addClass(this.selectedClass);
      } else {
        $elm.parent('label').removeClass(this.selectedClass);
      }
    }
  };
  SelectionButtons.prototype.bindElementLevelEvents = function ($elms) {
    $elms
      .on('click', function (e) {
        this.markSelected($(e.target));
      }.bind(this))
      .on('focus blur', function (e) {
        var state = (e.type === 'focus') ? 'focused' : 'blurred';

        this.markFocused($(e.target), state);
      }.bind(this));
  };
  SelectionButtons.eventSelectors = [];
  SelectionButtons.prototype.bindDocumentLevelEvents = function (selector) {
    if ($.inArray(selector, SelectionButtons.eventSelectors) === -1) {
      $(document)
        .on('click', selector, function (e) {
          this.markSelected($(e.target));
        }.bind(this))
        .on('focus blur', selector, function (e) {
          var state = (e.type === 'focusin') ? 'focused' : 'blurred';

          this.markFocused($(e.target), state);
        }.bind(this));
      SelectionButtons.eventSelectors.push(selector);
    }
  };

  var selectionButtons = function (elms, opts) {
    new SelectionButtons(elms, opts);
  };
  selectionButtons.removeEventsFor = function (selector) {
    $(document)
      .off('click', selector)
      .off('focus blur', selector);
    SelectionButtons.eventSelectors = $.grep(SelectionButtons.eventSelectors, function (entry) {
      return entry !== selector;
    });
  };

  root.GOVUK.selectionButtons = selectionButtons;
}).call(this);
<|endoftext|>


<commit_before>var requireL = require("root-require")("./src/require-local.js");
var console_log = requireL("logging").logger("console");

var _ = require('underscore-node');
var serverIpAddress = "";

// @TODO move to monkeypatch-array module
Array.prototype.flatten = function() {return _.flatten(this)};

var os = require('os');
var exec = require('child_process').exec;
var bus = require("hermes-bus");
var MAX_STDOUT_BUFFER = {maxBuffer: 500 * 1024};

var numSlaves = 1;
var dockerSlaveImageId = 'next-swarm-slave';
var dockerSlaveTimeout = 30;
var dockerSlaveOptions = "";
bus.on("registerCommandlineArguments", function (parser) {
	parser.addArgument(
		['-s', '--slaves'],
		{
			dest: 'numSlaves',
			metavar: 'NUM',
			help: 'Specify a how many slaves to start.'
		}
	);
	parser.addArgument(
		['--docker-slave-image-id'],
		{
			dest: 'dockerSlaveImageId',
			metavar: 'ID',
			help: 'The slave image id that docker uses to run a slave (default: \'next-swarm-slave\').'
		}
	);
	parser.addArgument(
		['--docker-slave-timeout'],
		{
			dest: 'dockerSlaveTimeout',
			metavar: 'SEC',
			help: 'The allowed maximum number of seconds between two consecutive heartbeats before next-swarm considers the slave lost.'
		}
	);
	parser.addArgument(
		['--docker-slave-options'],
		{
			dest: 'dockerSlaveOptions',
			metavar: 'OPTIONS',
			help: 'Additional options passed to docker when starting slaves.'
		}
	);
});

bus.on("commandlineArgumentsParsed", function (args) {
	numSlaves = args.numSlaves || numSlaves;
	dockerSlaveImageId = args.dockerSlaveImageId || dockerSlaveImageId;

	# Explicit loose equality check
	dockerSlaveTimeout = args.dockerSlaveTimeout != undefined ? args.dockerSlaveTimeout : dockerSlaveTimeout;
	dockerSlaveOptions = args.dockerSlaveOptions != undefined ? args.dockerSlaveOptions : dockerSlaveOptions;
});

with(requireL("tasks").statusTypes) {

var tasks = [];

bus.on("scheduleTasks", function(tasks2) {
	tasks = tasks2;
});

// maximum time since last heartbeat before we consider the slave lost
var getSlaveMaxTimeout = function() {
	return dockerSlaveTimeout*1000;
}

// interval at which we check if a slave was lost
var SLAVE_SANITY_CHECK_INTERVAL= 1000;
// slave looks like: {workerId: "docker container id", timeOfLastHeartBeat: 1447284644252, taskName: "name of task it was running"}
var monitoredSlaves = [];
/*
 * Every SLAVE_SANITY_CHECK_INTERVAL ms we check if slaves were lost,
 * when this happens:
 *
 * 1) restart the slave
 * 2) reschedule the job that the slave was supposed to run
 */
var isMonitoringSlaves = false;
var startMonitoringSlaves = function() {
	isMonitoringSlaves = true;
	setInterval(function() {
//		console_log("Checking slaves:", JSON.stringify(monitoredSlaves, null, 4));
		var now = Date.now();
		monitoredSlaves.forEach(function(slave) {

			if((now - slave.timeOfLastHeartBeat) > getSlaveMaxTimeout()) {
				console_log("slave " + slave.workerId + " seems lost, restarting");
				restartSlave(slave.workerId);
				var task = tasks.find((task) => task.name === slave.taskName);

				if(task && !task.completed) {
					if(task.numRecoveryRunsLeft > 0){
						task.status = SCHEDULED;
						task.workerId = "<unknown>";
						task.numRecoveryRunsLeft -= 1;
					} else {
						task.status = FAILED;
						task.completed = true;
						console_log("Task: "+ task.name +" reached max numOfRecoveryRuns!");
					}
				}
			}
		});
	}, SLAVE_SANITY_CHECK_INTERVAL);
};

bus.on("taskUpdated", function(task) {
	if(task.workerId !== "<unknown>") {
		if(task.completed) {
			unmonitorSlave(task.workerId);
		} else if(task.status === PICKED_UP) {
			if(task.numRecoveryRunsLeft == undefined) {
				task.defineProperty("numRecoveryRunsLeft", 3);
			}

			monitorSlave(task);
		}
	}
});

bus.on("heartbeatReceived", function(workerId) {
	console_log("Heartbeat received from", workerId);

	var monitoredSlave = findSlave(workerId);
	if(monitoredSlave) {
		monitoredSlave.timeOfLastHeartBeat = Date.now();
	}
});

function restartSlave(workerId) {
	console_log("Restarting slave", workerId);

	unmonitorSlave(workerId);
	var command = "docker stop --time=3 " + workerId;
	exec(command, MAX_STDOUT_BUFFER, function (error) {
		if (error) {
			console_log("FAILED to kill container", workerId, error);
		}
	});
	//For some reason this fails to start the docker container ????
	startSlave(serverIpAddress);
}

function findSlave(workerId) {
	return monitoredSlaves.find((slave) => slave.workerId === workerId);
}

function monitorSlave(taskThatSlaveIsRunning) {
	var monitoredSlave = findSlave(taskThatSlaveIsRunning.workerId);
	if(!monitoredSlave) {
		monitoredSlaves.push({
			workerId: taskThatSlaveIsRunning.workerId,
			timeOfLastHeartBeat: Date.now(),
			taskName: taskThatSlaveIsRunning.name,
		});

		if(!isMonitoringSlaves) {
			startMonitoringSlaves();
		}
	}
}

function unmonitorSlave(workerId) {
	var monitoredSlave = findSlave(workerId);
	if(monitoredSlave) {
		monitoredSlaves = _.without(monitoredSlaves, monitoredSlave);
	}
};

function startSlaves(serverAddress) {
	serverIpAddress = serverAddress;
	_.range(numSlaves).forEach(function() {
		startSlave(serverAddress);
	});
}

var SIGTERM = 137;
function startSlave(serverAddress){
	console_log("Booting-up a docker container...");
	var command = "docker run " + dockerSlaveOptions + " " + dockerSlaveImageId + " " + serverAddress;
	exec(command, MAX_STDOUT_BUFFER, function(error){
		if(error && error.code !== SIGTERM){
			console_log("Failed to boot-up a slave:", error);
			bus.triggerRequestStopApplication({value: 1});
		}
	});
}

} // with tasks.statusTypes

var localIpAddress = "127.0.0.1";

bus.on("applicationStarted", function() {
	var firstNonLocalNetworkConfig = _.values(os.networkInterfaces()).flatten().find((e) => e.address !== "127.0.0.1" && e.address !== "::1" );

	if(firstNonLocalNetworkConfig) {
		localIpAddress = firstNonLocalNetworkConfig.address;
	} else {
		console_log("Error: Unable to determine public ip address of this host!");
		console_log( "Exiting...");
		bus.triggerRequestStopApplication({value: 1});
	}
});

bus.on("connectServerStarted", function(connectServer) {
	startSlaves("http://"+localIpAddress+":"+connectServer.address().port);
});
<commit_msg>src/manage-slaves.js: # <-> //  (comment-style-in-programming-language-mixup)<commit_after>var requireL = require("root-require")("./src/require-local.js");
var console_log = requireL("logging").logger("console");

var _ = require('underscore-node');
var serverIpAddress = "";

// @TODO move to monkeypatch-array module
Array.prototype.flatten = function() {return _.flatten(this)};

var os = require('os');
var exec = require('child_process').exec;
var bus = require("hermes-bus");
var MAX_STDOUT_BUFFER = {maxBuffer: 500 * 1024};

var numSlaves = 1;
var dockerSlaveImageId = 'next-swarm-slave';
var dockerSlaveTimeout = 30;
var dockerSlaveOptions = "";
bus.on("registerCommandlineArguments", function (parser) {
	parser.addArgument(
		['-s', '--slaves'],
		{
			dest: 'numSlaves',
			metavar: 'NUM',
			help: 'Specify a how many slaves to start.'
		}
	);
	parser.addArgument(
		['--docker-slave-image-id'],
		{
			dest: 'dockerSlaveImageId',
			metavar: 'ID',
			help: 'The slave image id that docker uses to run a slave (default: \'next-swarm-slave\').'
		}
	);
	parser.addArgument(
		['--docker-slave-timeout'],
		{
			dest: 'dockerSlaveTimeout',
			metavar: 'SEC',
			help: 'The allowed maximum number of seconds between two consecutive heartbeats before next-swarm considers the slave lost.'
		}
	);
	parser.addArgument(
		['--docker-slave-options'],
		{
			dest: 'dockerSlaveOptions',
			metavar: 'OPTIONS',
			help: 'Additional options passed to docker when starting slaves.'
		}
	);
});

bus.on("commandlineArgumentsParsed", function (args) {
	numSlaves = args.numSlaves || numSlaves;
	dockerSlaveImageId = args.dockerSlaveImageId || dockerSlaveImageId;

	// Explicit loose equality check
	dockerSlaveTimeout = args.dockerSlaveTimeout != undefined ? args.dockerSlaveTimeout : dockerSlaveTimeout;
	dockerSlaveOptions = args.dockerSlaveOptions != undefined ? args.dockerSlaveOptions : dockerSlaveOptions;
});

with(requireL("tasks").statusTypes) {

var tasks = [];

bus.on("scheduleTasks", function(tasks2) {
	tasks = tasks2;
});

// maximum time since last heartbeat before we consider the slave lost
var getSlaveMaxTimeout = function() {
	return dockerSlaveTimeout*1000;
}

// interval at which we check if a slave was lost
var SLAVE_SANITY_CHECK_INTERVAL= 1000;
// slave looks like: {workerId: "docker container id", timeOfLastHeartBeat: 1447284644252, taskName: "name of task it was running"}
var monitoredSlaves = [];
/*
 * Every SLAVE_SANITY_CHECK_INTERVAL ms we check if slaves were lost,
 * when this happens:
 *
 * 1) restart the slave
 * 2) reschedule the job that the slave was supposed to run
 */
var isMonitoringSlaves = false;
var startMonitoringSlaves = function() {
	isMonitoringSlaves = true;
	setInterval(function() {
//		console_log("Checking slaves:", JSON.stringify(monitoredSlaves, null, 4));
		var now = Date.now();
		monitoredSlaves.forEach(function(slave) {

			if((now - slave.timeOfLastHeartBeat) > getSlaveMaxTimeout()) {
				console_log("slave " + slave.workerId + " seems lost, restarting");
				restartSlave(slave.workerId);
				var task = tasks.find((task) => task.name === slave.taskName);

				if(task && !task.completed) {
					if(task.numRecoveryRunsLeft > 0){
						task.status = SCHEDULED;
						task.workerId = "<unknown>";
						task.numRecoveryRunsLeft -= 1;
					} else {
						task.status = FAILED;
						task.completed = true;
						console_log("Task: "+ task.name +" reached max numOfRecoveryRuns!");
					}
				}
			}
		});
	}, SLAVE_SANITY_CHECK_INTERVAL);
};

bus.on("taskUpdated", function(task) {
	if(task.workerId !== "<unknown>") {
		if(task.completed) {
			unmonitorSlave(task.workerId);
		} else if(task.status === PICKED_UP) {
			if(task.numRecoveryRunsLeft == undefined) {
				task.defineProperty("numRecoveryRunsLeft", 3);
			}

			monitorSlave(task);
		}
	}
});

bus.on("heartbeatReceived", function(workerId) {
	console_log("Heartbeat received from", workerId);

	var monitoredSlave = findSlave(workerId);
	if(monitoredSlave) {
		monitoredSlave.timeOfLastHeartBeat = Date.now();
	}
});

function restartSlave(workerId) {
	console_log("Restarting slave", workerId);

	unmonitorSlave(workerId);
	var command = "docker stop --time=3 " + workerId;
	exec(command, MAX_STDOUT_BUFFER, function (error) {
		if (error) {
			console_log("FAILED to kill container", workerId, error);
		}
	});
	//For some reason this fails to start the docker container ????
	startSlave(serverIpAddress);
}

function findSlave(workerId) {
	return monitoredSlaves.find((slave) => slave.workerId === workerId);
}

function monitorSlave(taskThatSlaveIsRunning) {
	var monitoredSlave = findSlave(taskThatSlaveIsRunning.workerId);
	if(!monitoredSlave) {
		monitoredSlaves.push({
			workerId: taskThatSlaveIsRunning.workerId,
			timeOfLastHeartBeat: Date.now(),
			taskName: taskThatSlaveIsRunning.name,
		});

		if(!isMonitoringSlaves) {
			startMonitoringSlaves();
		}
	}
}

function unmonitorSlave(workerId) {
	var monitoredSlave = findSlave(workerId);
	if(monitoredSlave) {
		monitoredSlaves = _.without(monitoredSlaves, monitoredSlave);
	}
};

function startSlaves(serverAddress) {
	serverIpAddress = serverAddress;
	_.range(numSlaves).forEach(function() {
		startSlave(serverAddress);
	});
}

var SIGTERM = 137;
function startSlave(serverAddress){
	console_log("Booting-up a docker container...");
	var command = "docker run " + dockerSlaveOptions + " " + dockerSlaveImageId + " " + serverAddress;
	exec(command, MAX_STDOUT_BUFFER, function(error){
		if(error && error.code !== SIGTERM){
			console_log("Failed to boot-up a slave:", error);
			bus.triggerRequestStopApplication({value: 1});
		}
	});
}

} // with tasks.statusTypes

var localIpAddress = "127.0.0.1";

bus.on("applicationStarted", function() {
	var firstNonLocalNetworkConfig = _.values(os.networkInterfaces()).flatten().find((e) => e.address !== "127.0.0.1" && e.address !== "::1" );

	if(firstNonLocalNetworkConfig) {
		localIpAddress = firstNonLocalNetworkConfig.address;
	} else {
		console_log("Error: Unable to determine public ip address of this host!");
		console_log( "Exiting...");
		bus.triggerRequestStopApplication({value: 1});
	}
});

bus.on("connectServerStarted", function(connectServer) {
	startSlaves("http://"+localIpAddress+":"+connectServer.address().port);
});
<|endoftext|>