<commit_before>/* jshint esversion: 6 */

let BUTTON_CHECKED = 'goog-imageless-button-checked'
let BUTTON_CONTENT = 'goog-imageless-button-content'

function trigger(event_names, elem) {
    // event_names: space sep names of events
    // elem: jQuery element
    if (!event_names || event_names.length === 0) {
        throw (`Cannot trigger ${event_names}, element event_names`)
    }
    if (!elem || elem.length === 0) {
        throw (`Cannot trigger ${event_names}, element missing`)
    }
    for (let event_name of event_names.split(' ')) {
        let evt = document.createEvent("MouseEvents")
        // eek, there's gotta be a better way.
        // replace with https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent
        // https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
        evt.initMouseEvent(event_name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
        let dom_elem = elem.get(0)
        dom_elem.dispatchEvent(evt)
    }
    return elem
}

class Toolbar {
    get buttons() {
        return $('#topRightNavigation .goog-imageless-button')
    }
    get month_view() {
        return this.buttons.eq(2)
    }
    get custom_view() {
        return this.buttons.eq(3)
    }
    get prev_month() {
        return $('.navBack').eq(0)
    }
    get today() {
        return $('#todayButton\\:1,#todayButton\\:2').children().eq(0)
    }
    get next_month() {
        return $('#dp_0_next')
    }
    get is_custom_view_active() {
        return this.custom_view.is(`.${BUTTON_CHECKED}`)
    }

    poll_custom_button_visibility(wait_ms = 500) {
        if (
            this.custom_view.is(":visible") &&
            this.buttons.filter(`.${BUTTON_CHECKED}`).is(":visible")
        ) {
            $(document).trigger("custom_view_buttons_visible")
        } else {
            setTimeout((w) => this.poll_custom_button_visibility(w), wait_ms)
        }
    }

    inject_buttons() {
        toolbar.custom_view.after(
            function() {
                return $(this)
                    .clone()
                    .addClass('gcal-unlim-weeks-adjust-weeks')
                    .addClass('gcal-unlim-weeks-remove-weeks')
                    .find('.goog-imageless-button-content')
                        .text('-')
                    .end()
                    .click(() => unlimited_weeks.remove_week())

            }
        ).after(
            function() {
                return $(this)
                    .clone()
                    .addClass('gcal-unlim-weeks-adjust-weeks')
                    .addClass('gcal-unlim-weeks-add-weeks')
                    .find('.goog-imageless-button-content')
                        .text('+')
                    .end()
                    .click(() => unlimited_weeks.add_week())
            }
        )

        $('.gcal-unlim-weeks-adjust-weeks')
            .removeClass(BUTTON_CHECKED)
            // replicate button behavior
            .mousedown(function(){$(this).addClass('goog-imageless-button-focused')})
            .mouseup(function(){$(this).removeClass('goog-imageless-button-focused')})
            .mouseenter(function(){$(this).addClass('goog-imageless-button-hover')})
            .mouseleave(function(){$(this).removeClass('goog-imageless-button-hover')})

        if (this.is_custom_view_active) {
            unlimited_weeks.restore_weeks()
        } else {
            unlimited_weeks.load_num_weeks().then(
                function(num_weeks) {
                    unlimited_weeks.write_custom_button_label(num_weeks)
                })
        }

        this.custom_view.click(() => {
            unlimited_weeks.restore_weeks()
        })

        this.buttons.mousedown(() => {
            this.buttons.removeClass(BUTTON_CHECKED)
        })
    }
}

class BigCal {
    get first_day_num() {
        if (!$('#gridcontainer span[class^="ca-cdp"]').is(':visible')) {
            // no grid, must be in agenda mode
            trigger('mousedown mouseup', toolbar.month_view)
        }
        return parseInt(
            // take a fresh look, in case it's only just appeared
            $('#gridcontainer span[class^="ca-cdp"]')
            .attr('class')
            .split('ca-cdp')
            .slice(-1)[0]
        )
    }

    get num_weeks() {
        return $('.month-row').length
    }
}

class MiniCal {
    constructor(height = 6) {
        // in week rows
        this.height = height
    }

    get cells() {
        return $('.dp-cell[class*="dp-o"]')
    }
    nth(n) {
        return this.cells.eq(n)
    }
    get first() {
        return this.nth(0)
    }
    get last() {
        return this.nth(7 * this.height - 1)
    }
    extract_day_num(el) {
        // Google Calendar seems to label each day with a monotonic number
        // that skips in an unknown way
        return parseInt(el.eq(0).attr('id').split('_').slice(-1)[0])
    }
    get first_day_num() {
        return this.extract_day_num(this.first)
    }
    get last_day_num() {
        return this.extract_day_num(this.last)
    }
    get month_start_indexes() {
        // return the positions of the 1st of the current
        // and next months
        return this.cells.map((i, el) => {
            if ($(el).text() === '1') {
                return i
            } else {
                return null
            }
        })
    }
    get current_month_start_index() {
        return this.month_start_indexes[0]
    }
    get next_month_start_index() {
        return this.month_start_indexes[1]
    }
    // current month may start in either first or second row
    get current_month_starts_high() {
        return this.current_month_start_index < 7
    }
    // next month may start in either last or penultimate row
    get next_month_starts_low() {
        return this.next_month_start_index >= 7 * (this.height - 1)
    }
    get weeks_in_month() {
        // bools get cast to 0 or 1 here. each true is an extra week
        return 3 + this.current_month_starts_high + this.next_month_starts_low
    }
    cell_from_day_num(day_num) {
        return this.cells.filter(`[id$="${day_num}"]`)
    }
    month_backward() {
        trigger('mousedown mouseup', $('.dp-sb-prev'))
    }
    month_forward() {
        trigger('mousedown mouseup', $('.dp-sb-next'))
    }
    navigate_to_day_num(day_num) {
        let i = 0
        while (day_num < this.first_day_num || this.last_day_num < day_num) {
            if (++i > 10) {
                throw "Too many iterations"
            }
            if (day_num < this.first_day_num) {
                this.month_backward()
            } else if (this.last_day_num < day_num) {
                this.month_forward()
            } else {
                throw 'unknown condition'
            }
        }
        if (this.cell_from_day_num(day_num).length != 1) {
            throw "target not found on mini cal"
        }
    }
}

class UnlimitedWeeks {
    add_week() {
        $('.gcal-unlim-weeks-add-weeks').addClass(BUTTON_CHECKED)
        this.alter_weeks(+1)
    }

    remove_week() {
        $('.gcal-unlim-weeks-remove-weeks').addClass(BUTTON_CHECKED)
        this.alter_weeks(-1)
    }

    get can_persist() {
        return chrome && chrome.storage && chrome.storage.sync
    }

    save_num_weeks() {
        if (this.can_persist) {
            chrome.storage.sync.set({
                'num_weeks': big_cal.num_weeks
            })
        }
    }

    load_num_weeks() {
        // returns a promise
        if (!this.can_persist) {
            return new Promise((resolve) => resolve(big_cal.num_weeks))
        }
        return new Promise(function(resolve){
            chrome.storage.sync.get('num_weeks', function(data) {
                if (
                    $.isEmptyObject(data) ||
                    typeof data.num_weeks === 'number' ||
                    typeof data.num_weeks >= 2
                ) {
                    return resolve(data.num_weeks)
                } else {
                    return resolve(big_cal.num_weeks)
                }
            })
        })
    }

    restore_weeks() {
        this.load_num_weeks().then(
            num_weeks => this.display_weeks(num_weeks)
        )
    }

    allocate_weeks(weeks_left) {
        while (weeks_left > 0) {
            let weeks_in_month = mini_cal.weeks_in_month
            if (weeks_in_month > weeks_left) {
                break
            }
            weeks_left -= weeks_in_month
            mini_cal.month_forward()
        }
        if (weeks_left > 0) {
            return 7 * weeks_left
        }
        return 0
    }

    get_start_cell() {
        let index = mini_cal.current_month_start_index + 7
        return mini_cal.nth(index)
    }

    get_end_cell(days_remaining) {
        let index = days_remaining + mini_cal.current_month_start_index
        return mini_cal.nth(index)
    }

    move_weeks(start_day_num) {
        // move the calandar back to the date it started at

        // move active range forward, out the way
        mini_cal.month_forward()
        // we must click outside the active range, otherwise, we just select a single day
        trigger('mousedown mouseup', mini_cal.last)

        // now click the date we want, in the mini map
        mini_cal.navigate_to_day_num(start_day_num)
        trigger('mousedown mouseup', mini_cal.cell_from_day_num(start_day_num))
    }

    write_custom_button_label(num_weeks=null) {
        toolbar.custom_view
            .find(`.${BUTTON_CONTENT}`)
            .text(`${num_weeks || big_cal.num_weeks} weeks`)
    }

    alter_weeks(delta) {
        if (toolbar.is_custom_view_active) {
            return this.display_weeks(big_cal.num_weeks + delta)
        }
        let that = this
        this.load_num_weeks().then(function(num_weeks) {
            that.display_weeks(num_weeks + delta)
        })
    }

    display_weeks(weeks_left) {
        if (weeks_left < 2) {
            weeks_left = 2
        }
        let target_start_day_num = big_cal.first_day_num

        // move to custom view, click doesn't work here
        trigger('mousedown mouseup', toolbar.custom_view)

        // ensure start date in visible in mini cal
        mini_cal.navigate_to_day_num(target_start_day_num)

        // do a double manoeuvre: click next month during a click drag over the mini calendar.
        // this is how we reach more than one month
        trigger('mousedown', this.get_start_cell())
        let days_remaining = this.allocate_weeks(weeks_left)
        trigger('mousemove mouseup', this.get_end_cell(days_remaining))

        this.write_custom_button_label()

        this.move_weeks(target_start_day_num)

        // preserve number of weeks for next page (re)load
        this.save_num_weeks()
        toolbar.custom_view.addClass(BUTTON_CHECKED)

        $('.gcal-unlim-weeks-adjust-weeks').removeClass(BUTTON_CHECKED)
    }
}

let demo = false
let mini_cal = new MiniCal()
let big_cal = new BigCal()
let toolbar = new Toolbar()
let unlimited_weeks = new UnlimitedWeeks()

$(document)
    .on("custom_view_buttons_visible", function() {
        toolbar.inject_buttons()
        if (demo === true) {
            console.log('demo')
            setTimeout(unlimited_weeks.add_week, 1000)
            setTimeout(unlimited_weeks.add_week, 1500)
            setTimeout(unlimited_weeks.remove_week, 3000)
        }
    })

$(document).ready(
    function() {
        // triggers custom_view_buttons_visible event
        toolbar.poll_custom_button_visibility()
    })
<commit_msg>remove unused code, fixes #1<commit_after>/* jshint esversion: 6 */

let BUTTON_CHECKED = 'goog-imageless-button-checked'
let BUTTON_CONTENT = 'goog-imageless-button-content'

function trigger(event_names, elem) {
    // event_names: space sep names of events
    // elem: jQuery element
    if (!event_names || event_names.length === 0) {
        throw (`Cannot trigger ${event_names}, element event_names`)
    }
    if (!elem || elem.length === 0) {
        throw (`Cannot trigger ${event_names}, element missing`)
    }
    for (let event_name of event_names.split(' ')) {
        let evt = document.createEvent("MouseEvents")
        // eek, there's gotta be a better way.
        // replace with https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent
        // https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
        evt.initMouseEvent(event_name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
        let dom_elem = elem.get(0)
        dom_elem.dispatchEvent(evt)
    }
    return elem
}

class Toolbar {
    get buttons() {
        return $('#topRightNavigation .goog-imageless-button')
    }
    get month_view() {
        return this.buttons.eq(2)
    }
    get custom_view() {
        return this.buttons.eq(3)
    }
    get is_custom_view_active() {
        return this.custom_view.is(`.${BUTTON_CHECKED}`)
    }

    poll_custom_button_visibility(wait_ms = 500) {
        if (
            this.custom_view.is(":visible") &&
            this.buttons.filter(`.${BUTTON_CHECKED}`).is(":visible")
        ) {
            $(document).trigger("custom_view_buttons_visible")
        } else {
            setTimeout((w) => this.poll_custom_button_visibility(w), wait_ms)
        }
    }

    inject_buttons() {
        toolbar.custom_view.after(
            function() {
                return $(this)
                    .clone()
                    .addClass('gcal-unlim-weeks-adjust-weeks')
                    .addClass('gcal-unlim-weeks-remove-weeks')
                    .find('.goog-imageless-button-content')
                        .text('-')
                    .end()
                    .click(() => unlimited_weeks.remove_week())

            }
        ).after(
            function() {
                return $(this)
                    .clone()
                    .addClass('gcal-unlim-weeks-adjust-weeks')
                    .addClass('gcal-unlim-weeks-add-weeks')
                    .find('.goog-imageless-button-content')
                        .text('+')
                    .end()
                    .click(() => unlimited_weeks.add_week())
            }
        )

        $('.gcal-unlim-weeks-adjust-weeks')
            .removeClass(BUTTON_CHECKED)
            // replicate button behavior
            .mousedown(function(){$(this).addClass('goog-imageless-button-focused')})
            .mouseup(function(){$(this).removeClass('goog-imageless-button-focused')})
            .mouseenter(function(){$(this).addClass('goog-imageless-button-hover')})
            .mouseleave(function(){$(this).removeClass('goog-imageless-button-hover')})

        if (this.is_custom_view_active) {
            unlimited_weeks.restore_weeks()
        } else {
            unlimited_weeks.load_num_weeks().then(
                function(num_weeks) {
                    unlimited_weeks.write_custom_button_label(num_weeks)
                })
        }

        this.custom_view.click(() => {
            unlimited_weeks.restore_weeks()
        })

        this.buttons.mousedown(() => {
            this.buttons.removeClass(BUTTON_CHECKED)
        })
    }
}

class BigCal {
    get first_day_num() {
        if (!$('#gridcontainer span[class^="ca-cdp"]').is(':visible')) {
            // no grid, must be in agenda mode
            trigger('mousedown mouseup', toolbar.month_view)
        }
        return parseInt(
            // take a fresh look, in case it's only just appeared
            $('#gridcontainer span[class^="ca-cdp"]')
            .attr('class')
            .split('ca-cdp')
            .slice(-1)[0]
        )
    }

    get num_weeks() {
        return $('.month-row').length
    }
}

class MiniCal {
    constructor(height = 6) {
        // in week rows
        this.height = height
    }

    get cells() {
        return $('.dp-cell[class*="dp-o"]')
    }
    nth(n) {
        return this.cells.eq(n)
    }
    get first() {
        return this.nth(0)
    }
    get last() {
        return this.nth(7 * this.height - 1)
    }
    extract_day_num(el) {
        // Google Calendar seems to label each day with a monotonic number
        // that skips in an unknown way
        return parseInt(el.eq(0).attr('id').split('_').slice(-1)[0])
    }
    get first_day_num() {
        return this.extract_day_num(this.first)
    }
    get last_day_num() {
        return this.extract_day_num(this.last)
    }
    get month_start_indexes() {
        // return the positions of the 1st of the current
        // and next months
        return this.cells.map((i, el) => {
            if ($(el).text() === '1') {
                return i
            } else {
                return null
            }
        })
    }
    get current_month_start_index() {
        return this.month_start_indexes[0]
    }
    get next_month_start_index() {
        return this.month_start_indexes[1]
    }
    // current month may start in either first or second row
    get current_month_starts_high() {
        return this.current_month_start_index < 7
    }
    // next month may start in either last or penultimate row
    get next_month_starts_low() {
        return this.next_month_start_index >= 7 * (this.height - 1)
    }
    get weeks_in_month() {
        // bools get cast to 0 or 1 here. each true is an extra week
        return 3 + this.current_month_starts_high + this.next_month_starts_low
    }
    cell_from_day_num(day_num) {
        return this.cells.filter(`[id$="${day_num}"]`)
    }
    month_backward() {
        trigger('mousedown mouseup', $('.dp-sb-prev'))
    }
    month_forward() {
        trigger('mousedown mouseup', $('.dp-sb-next'))
    }
    navigate_to_day_num(day_num) {
        let i = 0
        while (day_num < this.first_day_num || this.last_day_num < day_num) {
            if (++i > 10) {
                throw "Too many iterations"
            }
            if (day_num < this.first_day_num) {
                this.month_backward()
            } else if (this.last_day_num < day_num) {
                this.month_forward()
            } else {
                throw 'unknown condition'
            }
        }
        if (this.cell_from_day_num(day_num).length != 1) {
            throw "target not found on mini cal"
        }
    }
}

class UnlimitedWeeks {
    add_week() {
        $('.gcal-unlim-weeks-add-weeks').addClass(BUTTON_CHECKED)
        this.alter_weeks(+1)
    }

    remove_week() {
        $('.gcal-unlim-weeks-remove-weeks').addClass(BUTTON_CHECKED)
        this.alter_weeks(-1)
    }

    get can_persist() {
        return chrome && chrome.storage && chrome.storage.sync
    }

    save_num_weeks() {
        if (this.can_persist) {
            chrome.storage.sync.set({
                'num_weeks': big_cal.num_weeks
            })
        }
    }

    load_num_weeks() {
        // returns a promise
        if (!this.can_persist) {
            return new Promise((resolve) => resolve(big_cal.num_weeks))
        }
        return new Promise(function(resolve){
            chrome.storage.sync.get('num_weeks', function(data) {
                if (
                    $.isEmptyObject(data) ||
                    typeof data.num_weeks === 'number' ||
                    typeof data.num_weeks >= 2
                ) {
                    return resolve(data.num_weeks)
                } else {
                    return resolve(big_cal.num_weeks)
                }
            })
        })
    }

    restore_weeks() {
        this.load_num_weeks().then(
            num_weeks => this.display_weeks(num_weeks)
        )
    }

    allocate_weeks(weeks_left) {
        while (weeks_left > 0) {
            let weeks_in_month = mini_cal.weeks_in_month
            if (weeks_in_month > weeks_left) {
                break
            }
            weeks_left -= weeks_in_month
            mini_cal.month_forward()
        }
        if (weeks_left > 0) {
            return 7 * weeks_left
        }
        return 0
    }

    get_start_cell() {
        let index = mini_cal.current_month_start_index + 7
        return mini_cal.nth(index)
    }

    get_end_cell(days_remaining) {
        let index = days_remaining + mini_cal.current_month_start_index
        return mini_cal.nth(index)
    }

    move_weeks(start_day_num) {
        // move the calandar back to the date it started at

        // move active range forward, out the way
        mini_cal.month_forward()
        // we must click outside the active range, otherwise, we just select a single day
        trigger('mousedown mouseup', mini_cal.last)

        // now click the date we want, in the mini map
        mini_cal.navigate_to_day_num(start_day_num)
        trigger('mousedown mouseup', mini_cal.cell_from_day_num(start_day_num))
    }

    write_custom_button_label(num_weeks=null) {
        toolbar.custom_view
            .find(`.${BUTTON_CONTENT}`)
            .text(`${num_weeks || big_cal.num_weeks} weeks`)
    }

    alter_weeks(delta) {
        if (toolbar.is_custom_view_active) {
            return this.display_weeks(big_cal.num_weeks + delta)
        }
        let that = this
        this.load_num_weeks().then(function(num_weeks) {
            that.display_weeks(num_weeks + delta)
        })
    }

    display_weeks(weeks_left) {
        if (weeks_left < 2) {
            weeks_left = 2
        }
        let target_start_day_num = big_cal.first_day_num

        // move to custom view, click doesn't work here
        trigger('mousedown mouseup', toolbar.custom_view)

        // ensure start date in visible in mini cal
        mini_cal.navigate_to_day_num(target_start_day_num)

        // do a double manoeuvre: click next month during a click drag over the mini calendar.
        // this is how we reach more than one month
        trigger('mousedown', this.get_start_cell())
        let days_remaining = this.allocate_weeks(weeks_left)
        trigger('mousemove mouseup', this.get_end_cell(days_remaining))

        this.write_custom_button_label()

        this.move_weeks(target_start_day_num)

        // preserve number of weeks for next page (re)load
        this.save_num_weeks()
        toolbar.custom_view.addClass(BUTTON_CHECKED)

        $('.gcal-unlim-weeks-adjust-weeks').removeClass(BUTTON_CHECKED)
    }
}

let demo = false
let mini_cal = new MiniCal()
let big_cal = new BigCal()
let toolbar = new Toolbar()
let unlimited_weeks = new UnlimitedWeeks()

$(document)
    .on("custom_view_buttons_visible", function() {
        toolbar.inject_buttons()
        if (demo === true) {
            console.log('demo')
            setTimeout(unlimited_weeks.add_week, 1000)
            setTimeout(unlimited_weeks.add_week, 1500)
            setTimeout(unlimited_weeks.remove_week, 3000)
        }
    })

$(document).ready(
    function() {
        // triggers custom_view_buttons_visible event
        toolbar.poll_custom_button_visibility()
    })
<|endoftext|>


<commit_before>/* eslint-disable guard-for-in, complexity, no-try-catch */
import assert from 'assert';
import {loadFile, loadImage} from './platform';
import {Texture2D} from '../webgl';

function noop() {}

/*
 * Loads (Requests) multiple files asynchronously
 */
export function loadFiles({urls, onProgress = noop, ...opts}) {
  assert(urls.every(url => typeof url === 'string'),
    'loadImages: {urls} must be array of strings');
  let count = 0;
  return Promise.all(urls.map(
    url => {
      const promise = loadFile({url, ...opts});
      promise.then(file => onProgress({
        progress: ++count / urls.length,
        count,
        total: urls.length,
        url
      }));
      return promise;
    }
  ));
}

/*
 * Loads (requests) multiple images asynchronously
 */
export function loadImages({urls, onProgress = noop, ...opts}) {
  assert(urls.every(url => typeof url === 'string'),
    'loadImages: {urls} must be array of strings');
  let count = 0;
  return Promise.all(urls.map(
    url => {
      const promise = loadImage({url, ...opts});
      promise.then(file => onProgress({
        progress: ++count / urls.length,
        count,
        total: urls.length,
        url
      }));
      return promise;
    }
  ));
}

export function loadTextures(gl, {urls, onProgress = noop, ...opts}) {
  assert(urls.every(url => typeof url === 'string'),
    'loadTextures: {urls} must be array of strings');
  return loadImages({urls, onProgress, ...opts})
  .then(images => images.map((img, i) => {
    let params = Array.isArray(opts.parameters) ?
      opts.parameters[i] : opts.parameters;
    params = params === undefined ? {} : params;
    return new Texture2D(gl, {
      ...params,
      data: img
    });
  }));
}
<commit_msg>Fixing the texture loading error in lesson 6<commit_after>/* eslint-disable guard-for-in, complexity, no-try-catch */
import assert from 'assert';
import {loadFile, loadImage} from './platform';
import {Texture2D} from '../webgl';

function noop() {}

/*
 * Loads (Requests) multiple files asynchronously
 */
export function loadFiles({urls, onProgress = noop, ...opts}) {
  assert(urls.every(url => typeof url === 'string'),
    'loadImages: {urls} must be array of strings');
  let count = 0;
  return Promise.all(urls.map(
    url => {
      const promise = loadFile({url, ...opts});
      promise.then(file => onProgress({
        progress: ++count / urls.length,
        count,
        total: urls.length,
        url
      }));
      return promise;
    }
  ));
}

/*
 * Loads (requests) multiple images asynchronously
 */
export function loadImages({urls, onProgress = noop, ...opts}) {
  assert(urls.every(url => typeof url === 'string'),
    'loadImages: {urls} must be array of strings');
  let count = 0;
  return Promise.all(urls.map(
    url => {
      const promise = loadImage(url);
      promise.then(file => onProgress({
        progress: ++count / urls.length,
        count,
        total: urls.length,
        url
      }));
      return promise;
    }
  ));
}

export function loadTextures(gl, {urls, onProgress = noop, ...opts}) {
  assert(urls.every(url => typeof url === 'string'),
    'loadTextures: {urls} must be array of strings');
  return loadImages({urls, onProgress, ...opts})
  .then(images => images.map((img, i) => {
    let params = Array.isArray(opts.parameters) ?
      opts.parameters[i] : opts.parameters;
    params = params === undefined ? {} : params;
    return new Texture2D(gl, {
      ...params,
      data: img
    });
  }));
}
<|endoftext|>


<commit_before>var ServerClient = function(params) {

    var polisTypes = {
        reactions: {
            push: 1,
            pull: -1,
            pass: 0,
            see: 'see'
        }
    };

    // stimulusId -> Lawnchair of comments
    var commentStores = {};
    function getCommentStore() {
        if (undefined === commentStores[currentStimulusId]) {
            commentStores[currentStimulusId] = new Lawnchair({
                name: 'v2_comments_' + currentStimulusId
            }, function() {
                console.log('lawnchair for '+ currentStimulusId +' ready'); // TODO make 'this' available to the module manager to prevent race conditions
            });
        }
        return commentStores[currentStimulusId];
    }

    var protocol = params.protocol;
    var domain = params.domain;
    var basePath = params.basePath;

    var reactionsPath = "/v2/reactions";
    var reactionsByMePath = "/v2/reactions/me";
    var txtPath = "/v2/txt";

    var createAccountPath = "/v2/auth/new";
    var loginPath = "/v2/auth/login";
    var deregisterPath = "/v2/auth/deregister";

    var authenticatedCalls = [reactionsByMePath, reactionsPath, txtPath, deregisterPath];

    var logger = params.logger;

    var authStateChangeCallbacks = $.Callbacks();

    var reactionsByMeStore = params.reactionsByMeStore;
    var usernameStore = params.usernameStore;
    var tokenStore = params.tokenStore;
    var emailStore = params.emailStore;
    var authStores = [tokenStore, usernameStore, emailStore];
    function clearAuthStores() {
        authStores.forEach(function(x) {
            x.clear();
        });
    }

    var needAuthCallbacks = $.Callbacks();

    var currentStimulusId;

    var comments = [];
    var users = [];

    // TODO if we decide to do manifest the chain of comments in a discussion, then we might want discussionClients that spawn discussionClients..?
    // Err... I guess discussions can be circular.
    //function discussionClient(params)

    function makeEmptyComment() {
        return {
            s: currentStimulusId,
            _id: "",
            txt: "There are no more comments for this story."
        };
    }

    function isValidCommentId(commentId) {
        return isNumber(commentId);
    }

    function getAllReactionsForSelf() {
        var params = {
            s: currentStimulusId
        };
        return polisGet(reactionsByMePath, params).done( function(data) {
            // BAD! use a real DB
            var oldStore = JSON.parse(reactionsStore.get());
            oldStore[currentStimulusId] = data.evs;
            reactionsStore.set(JSON.stringify(oldStore));
            console.dir(oldStore);
        });
    }

    function syncAllCommentsForCurrentStimulus() { // more like sync?
        var dfd = $.Deferred();
        var params = {
            s: currentStimulusId
            //?
        };
        polisGet(txtPath, params).then( function(data) {
                var evs = data.events;
                if (!evs) {
                    logger.log('no comments for stimulus');
                    dfd.resolve(0);
                } else {
                    var IDs = _.pluck(evs, "_id");
                    var commentStore = getCommentStore();
                    commentStore.keys(function(keys) {
                        var newIDs = _.difference(IDs, keys);
                        var newComments = evs.filter(function(ev) {
                            return _.contains(newIDs, ev._id);
                        });
                        // Lawnchair wants the key to be "key".
                        // could it be modified to have options.keyname?  a9w8ehfdfzgh
                        newComments = newComments.map(function(ev) {
                            ev.key = ev._id;
                            return ev;
                        });
                        commentStore.batch(newComments);
                        dfd.resolve(newComments.length);
                    });
                }
        }, function(err) {
            logger.error('failed to fetch comments for ' + currentStimulusId);
            logger.dir(err);
            dfd.reject(0);
        });
        return dfd.promise();
    }

    var getNextComment = function() {
        var dfd = $.Deferred();
        getCommentStore().all(function(comments) {
            for (var i = 0; i < comments.length; i++) {
                var comment = comments[i];
                if (undefined === comment.myReaction) {
                    delete comment.key; // a9w8ehfdfzgh
                    dfd.resolve(comment);
                    return;
                }
            }
            dfd.resolve(makeEmptyComment()); // may already be resolved above
        });
        return dfd.promise();
    };

    function submitStimulus(data) {
        if (typeof data.txt !== 'string' || data.txt.length === 0) {
            logger.error("fill out the txt field!");
            return $.Deferred().reject().promise();
        }
        return polisPost(txtPath, {
            events: [data]
        });
    }

    function submitComment(txt, optionalSpecificSubStimulus) {
        if (typeof txt !== 'string' || txt.length === 0) {
            logger.error('bad comment');
            return $.Deferred().reject().promise();
        }
        var ev = {
            s: currentStimulusId,
            txt: txt
        };
        // This allows for comments against other comments.
        // We may not want to allow that, but want to make sure
        // we could support it.
        // We may even want to add the entire stimulus chain
        // as an array?
        if (optionalSpecificSubStimulus) {
            ev.to = optionalSpecificSubStimulus;
        }
        return polisPost(txtPath, {
            events: [ev]
        });
    }

    function markReaction(commentId, reaction) {
        getCommentStore().get(commentId, function(comment) {
            comment.myReaction = reaction;
            getCommentStore().save(comment);
        });
    }

    function push(commentId) {
        markReaction(commentId, "push");
        return react({
            type: polisTypes.reactions.push,
            to: commentId
        });
    }

    function react(params) {
        return polisPost(reactionsPath, {
            events: [ $.extend({}, params, {
                s: currentStimulusId
            }) ]
        });
    }

    function pull(commentId) {
        markReaction(commentId, "pull");
        return react({
            type: polisTypes.reactions.pull,
            to: commentId
        });
    }

    // optionalSpecificSubStimulus (aka commentId)
    function see(optionalSpecificSubStimulus) {
        var ev = {
            type: polisTypes.reactions.see
        };
        if (optionalSpecificSubStimulus) {
            ev.to = optionalSpecificSubStimulus;
        }
        return react(ev);
    }

    function pass(optionalSpecificSubStimulus) {
        var ev = {
            type: polisTypes.reactions.pass
        };
        if (optionalSpecificSubStimulus) {
            ev.to = optionalSpecificSubStimulus;
        }
        if (ev.to) {
            markReaction(ev.to, "pass");
        }
        return react(ev);
    }

    function polisPost(api, data) {
        return polisAjax(api, data, "POST");
    }

    function polisGet(api, data) {
        return polisAjax(api, data, "GET");
    }

    function polisAjax(api, data, type) {
        var url = protocol ? (protocol + "://") : "" + domain + basePath + api;

        // Add the auth token if needed.
        if (_.contains(authenticatedCalls, api)) {
            var token = tokenStore.get();
            if (!token) {
                needAuthCallbacks.fire();
                return $.Deferred().reject('auth needed');
            }
            data = $.extend({ token: token}, data);
        }

        var promise;
        if ("GET" === type) {
            promise = $.get(url, data);
        } else if ("POST" === type) {
            promise = $.post(url, JSON.stringify(data));
        }
        promise.fail( function(jqXHR, message, errorType) {
                logger.error('SEND ERROR');
                //logger.dir(data);
                //logger.dir(message);
                //logger.dir(errorType);
        });
        return promise;
    }

    function observeStimulus(newStimulusId) {
        currentStimulusId = newStimulusId;
        return see();
    }

    function authNew(params) {
        if (!params.password) { return $.Deferred().reject("need password"); }
        if (!params.username && !params.email) { return $.Deferred().reject("need username or email"); }
        return polisPost(createAccountPath, params).done( function(authData) {

            clearAuthStores();
            var temporary = !params.rememberMe;
            tokenStore.set(authData.token, temporary);
            if (params.username) {
                usernameStore.set(params.username, temporary);
            }
            if (params.email) {
                emailStore.set(params.email, temporary);
            }
            authStateChangeCallbacks.fire(assemble({
                email: params.email,
                username: params.username,
                state: "p_registered"
            }));
        });//.then(logger.log, logger.error);
    }

    function authLogin(params) {
        if (!params.password) { return $.Deferred().reject("need password"); }
        if (!params.username && !params.email) { return $.Deferred().reject("need username or email"); }
        return polisPost(loginPath, params).done( function(authData) {
            clearAuthStores();
            var temporary = !params.rememberMe;
            tokenStore.set(authData.token, temporary);
            if (params.username) {
                usernameStore.set(params.username, temporary);
            }
            if (params.email) {
                emailStore.set(params.email, temporary);
            }
            authStateChangeCallbacks.fire(assemble({
                email: params.email,
                username: params.username,
                state: "p_registered"
            }));
        });//.then(logger.log, logger.error);
    }

    function authDeregister() {
        return polisPost(deregisterPath).always( function(authData) {
            clearAuthStores();
            authStateChangeCallbacks.fire(assemble({
                state: "p_deregistered"
            }));
        });//.then(logger.log, logger.error);
    }

    function authenticated() {
        return !!tokenStore.get();
    }

    return {
        authenticated: authenticated,
        authNew: authNew,
        authLogin: authLogin,
        authDeregister: authDeregister,
        getNextComment: getNextComment,
        observeStimulus: observeStimulus, // with no args
        push: push,
        pull: pull,
        pass: pass,
        see: see,
        syncAllCommentsForCurrentStimulus: syncAllCommentsForCurrentStimulus,
        addAuthStatChangeListener: authStateChangeCallbacks.add,
        addAuthNeededListener: needAuthCallbacks.add, // needed?
        submitStimulus: submitStimulus,
        submitComment: submitComment
    };
};
<commit_msg>clear db after deregister<commit_after>var ServerClient = function(params) {

    var polisTypes = {
        reactions: {
            push: 1,
            pull: -1,
            pass: 0,
            see: 'see'
        }
    };

    // stimulusId -> Lawnchair of comments
    var commentStores = {};
    function getCommentStore() {
        if (undefined === commentStores[currentStimulusId]) {
            commentStores[currentStimulusId] = new Lawnchair({
                name: 'v2_comments_' + currentStimulusId
            }, function() {
                console.log('lawnchair for '+ currentStimulusId +' ready'); // TODO make 'this' available to the module manager to prevent race conditions
            });
        }
        return commentStores[currentStimulusId];
    }

    function clearDb() {
        for (var name in commentStores) {
            commentStores[name].nuke();
        }
    }

    var protocol = params.protocol;
    var domain = params.domain;
    var basePath = params.basePath;

    var reactionsPath = "/v2/reactions";
    var reactionsByMePath = "/v2/reactions/me";
    var txtPath = "/v2/txt";

    var createAccountPath = "/v2/auth/new";
    var loginPath = "/v2/auth/login";
    var deregisterPath = "/v2/auth/deregister";

    var authenticatedCalls = [reactionsByMePath, reactionsPath, txtPath, deregisterPath];

    var logger = params.logger;

    var authStateChangeCallbacks = $.Callbacks();

    var reactionsByMeStore = params.reactionsByMeStore;
    var usernameStore = params.usernameStore;
    var tokenStore = params.tokenStore;
    var emailStore = params.emailStore;
    var authStores = [tokenStore, usernameStore, emailStore];
    function clearAuthStores() {
        authStores.forEach(function(x) {
            x.clear();
        });
    }

    var needAuthCallbacks = $.Callbacks();

    var currentStimulusId;

    var comments = [];
    var users = [];

    // TODO if we decide to do manifest the chain of comments in a discussion, then we might want discussionClients that spawn discussionClients..?
    // Err... I guess discussions can be circular.
    //function discussionClient(params)

    function makeEmptyComment() {
        return {
            s: currentStimulusId,
            _id: "",
            txt: "There are no more comments for this story."
        };
    }

    function isValidCommentId(commentId) {
        return isNumber(commentId);
    }

    function getAllReactionsForSelf() {
        var params = {
            s: currentStimulusId
        };
        return polisGet(reactionsByMePath, params).done( function(data) {
            // BAD! use a real DB
            var oldStore = JSON.parse(reactionsStore.get());
            oldStore[currentStimulusId] = data.evs;
            reactionsStore.set(JSON.stringify(oldStore));
            console.dir(oldStore);
        });
    }

    function syncAllCommentsForCurrentStimulus() { // more like sync?
        var dfd = $.Deferred();
        var params = {
            s: currentStimulusId
            //?
        };
        polisGet(txtPath, params).then( function(data) {
                var evs = data.events;
                if (!evs) {
                    logger.log('no comments for stimulus');
                    dfd.resolve(0);
                } else {
                    var IDs = _.pluck(evs, "_id");
                    var commentStore = getCommentStore();
                    commentStore.keys(function(keys) {
                        var newIDs = _.difference(IDs, keys);
                        var newComments = evs.filter(function(ev) {
                            return _.contains(newIDs, ev._id);
                        });
                        // Lawnchair wants the key to be "key".
                        // could it be modified to have options.keyname?  a9w8ehfdfzgh
                        newComments = newComments.map(function(ev) {
                            ev.key = ev._id;
                            return ev;
                        });
                        commentStore.batch(newComments);
                        dfd.resolve(newComments.length);
                    });
                }
        }, function(err) {
            logger.error('failed to fetch comments for ' + currentStimulusId);
            logger.dir(err);
            dfd.reject(0);
        });
        return dfd.promise();
    }

    var getNextComment = function() {
        var dfd = $.Deferred();
        getCommentStore().all(function(comments) {
            for (var i = 0; i < comments.length; i++) {
                var comment = comments[i];
                if (undefined === comment.myReaction) {
                    delete comment.key; // a9w8ehfdfzgh
                    dfd.resolve(comment);
                    return;
                }
            }
            dfd.resolve(makeEmptyComment()); // may already be resolved above
        });
        return dfd.promise();
    };

    function submitStimulus(data) {
        if (typeof data.txt !== 'string' || data.txt.length === 0) {
            logger.error("fill out the txt field!");
            return $.Deferred().reject().promise();
        }
        return polisPost(txtPath, {
            events: [data]
        });
    }

    function submitComment(txt, optionalSpecificSubStimulus) {
        if (typeof txt !== 'string' || txt.length === 0) {
            logger.error('bad comment');
            return $.Deferred().reject().promise();
        }
        var ev = {
            s: currentStimulusId,
            txt: txt
        };
        // This allows for comments against other comments.
        // We may not want to allow that, but want to make sure
        // we could support it.
        // We may even want to add the entire stimulus chain
        // as an array?
        if (optionalSpecificSubStimulus) {
            ev.to = optionalSpecificSubStimulus;
        }
        return polisPost(txtPath, {
            events: [ev]
        });
    }

    function markReaction(commentId, reaction) {
        getCommentStore().get(commentId, function(comment) {
            comment.myReaction = reaction;
            getCommentStore().save(comment);
        });
    }

    function push(commentId) {
        markReaction(commentId, "push");
        return react({
            type: polisTypes.reactions.push,
            to: commentId
        });
    }

    function react(params) {
        return polisPost(reactionsPath, {
            events: [ $.extend({}, params, {
                s: currentStimulusId
            }) ]
        });
    }

    function pull(commentId) {
        markReaction(commentId, "pull");
        return react({
            type: polisTypes.reactions.pull,
            to: commentId
        });
    }

    // optionalSpecificSubStimulus (aka commentId)
    function see(optionalSpecificSubStimulus) {
        var ev = {
            type: polisTypes.reactions.see
        };
        if (optionalSpecificSubStimulus) {
            ev.to = optionalSpecificSubStimulus;
        }
        return react(ev);
    }

    function pass(optionalSpecificSubStimulus) {
        var ev = {
            type: polisTypes.reactions.pass
        };
        if (optionalSpecificSubStimulus) {
            ev.to = optionalSpecificSubStimulus;
        }
        if (ev.to) {
            markReaction(ev.to, "pass");
        }
        return react(ev);
    }

    function polisPost(api, data) {
        return polisAjax(api, data, "POST");
    }

    function polisGet(api, data) {
        return polisAjax(api, data, "GET");
    }

    function polisAjax(api, data, type) {
        var url = protocol ? (protocol + "://") : "" + domain + basePath + api;

        // Add the auth token if needed.
        if (_.contains(authenticatedCalls, api)) {
            var token = tokenStore.get();
            if (!token) {
                needAuthCallbacks.fire();
                return $.Deferred().reject('auth needed');
            }
            data = $.extend({ token: token}, data);
        }

        var promise;
        if ("GET" === type) {
            promise = $.get(url, data);
        } else if ("POST" === type) {
            promise = $.post(url, JSON.stringify(data));
        }
        promise.fail( function(jqXHR, message, errorType) {
                logger.error('SEND ERROR');
                //logger.dir(data);
                //logger.dir(message);
                //logger.dir(errorType);
        });
        return promise;
    }

    function observeStimulus(newStimulusId) {
        currentStimulusId = newStimulusId;
        return see();
    }

    function authNew(params) {
        if (!params.password) { return $.Deferred().reject("need password"); }
        if (!params.username && !params.email) { return $.Deferred().reject("need username or email"); }
        return polisPost(createAccountPath, params).done( function(authData) {

            clearAuthStores();
            var temporary = !params.rememberMe;
            tokenStore.set(authData.token, temporary);
            if (params.username) {
                usernameStore.set(params.username, temporary);
            }
            if (params.email) {
                emailStore.set(params.email, temporary);
            }
            authStateChangeCallbacks.fire(assemble({
                email: params.email,
                username: params.username,
                state: "p_registered"
            }));
        });//.then(logger.log, logger.error);
    }

    function authLogin(params) {
        if (!params.password) { return $.Deferred().reject("need password"); }
        if (!params.username && !params.email) { return $.Deferred().reject("need username or email"); }
        return polisPost(loginPath, params).done( function(authData) {
            clearAuthStores();
            var temporary = !params.rememberMe;
            tokenStore.set(authData.token, temporary);
            if (params.username) {
                usernameStore.set(params.username, temporary);
            }
            if (params.email) {
                emailStore.set(params.email, temporary);
            }
            authStateChangeCallbacks.fire(assemble({
                email: params.email,
                username: params.username,
                state: "p_registered"
            }));
        });//.then(logger.log, logger.error);
    }

    function authDeregister() {
        return polisPost(deregisterPath).always( function(authData) {
            clearAuthStores();
            clearDb();
            authStateChangeCallbacks.fire(assemble({
                state: "p_deregistered"
            }));
        });
    }

    function authenticated() {
        return !!tokenStore.get();
    }

    return {
        authenticated: authenticated,
        authNew: authNew,
        authLogin: authLogin,
        authDeregister: authDeregister,
        getNextComment: getNextComment,
        observeStimulus: observeStimulus, // with no args
        push: push,
        pull: pull,
        pass: pass,
        see: see,
        syncAllCommentsForCurrentStimulus: syncAllCommentsForCurrentStimulus,
        addAuthStatChangeListener: authStateChangeCallbacks.add,
        addAuthNeededListener: needAuthCallbacks.add, // needed?
        submitStimulus: submitStimulus,
        submitComment: submitComment
    };
};
<|endoftext|>


<commit_before>// Generics
var ChopChop = (function($, ChopChop) {
    var api = ChopChop.api = {},
        plugins = ChopChop.plugins = {};

    // Exception
    var Exception = ChopChop.Exception = function(message) {
        this.message = message;
    };

    Exception.prototype = {
        toString: function() {
            return 'EXCEPTION: ' + this.message;
        }
    };

    // Initialisation
    var defaultInitOptions = {
        'toggle': {}
    };

    ChopChop.init = api.init = function(options) {
        options = options || defaultInitOptions;

        for (var plugin in options) {
            if (!options.hasOwnProperty(plugin)) {
                continue;
            }

            var name = plugin.charAt(0).toUpperCase() + plugin.substr(1);

            if (!plugins[plugin] && ChopChop[name]) {
                plugins[plugin] = new ChopChop[name](options);
            }
        }
    };

    // jQuery goodness
    $.fn.chopchop = function() {
        var args = Array.prototype.slice.call(arguments),
            numArgs = args.length;

        if (numArgs === 0) {
            return api;
        }

        var method = args.shift();

        if (api.hasOwnProperty(method)) {
            return this.each(function() {
                var params = args.slice(0);
                params.unshift(this);
                api[method].apply(api, params);
            });
        }
    };

    return ChopChop;
})(jQuery, ChopChop || {});

// Toggles
var ChopChop = (function($, ChopChop) {
    var defaultOptions = {
        classActive: 'is-active',
        classInactive: 'is-inactive'
    };

    // Toggle
    var Toggle = ChopChop.Toggle = function(options) {
        this.options = $.extend({}, defaultOptions, options);
        this.init();
    };

	var Action = {
		TOGGLE: 'toggle',
		ACTIVATE: 'activate',
		DEACTIVATE: 'deactivate'
	};

    var Trigger = {
        DIRECT_ONLY: 'direct-only',
        INDIRECT_ONLY: 'indirect-only',     // Why would you want this? Oh well.
        BOTH: 'both'    // Default
    };

    Toggle.prototype = {
        init: function() {
            var self = this;

            $(document).on('click', '[data-action]', function(e) {
                var $this = $(this), t,
                    $target = (t = $this.data('target')) ? $('#' + t) : false,
                    mode = $this.data('trigger-on') || Trigger.BOTH;

                if (!$target) {
                    return;
                }

                if (mode === Trigger.DIRECT_ONLY && e.target !== this) {
                    return;
                } else if (mode === Trigger.INDIRECT_ONLY && e.target === this) {
                    return;
                }

                e.preventDefault();
				self.performAction($target, $this.data('action') || Action.TOGGLE);
            });
        },
        toggle: function(target, action) {
            var $target = typeof target === 'string' ? $('#' + target) : $(target);

            if (!$target.size()) {
                return;
            }

            this.performAction($target, action || Action.TOGGLE);
        },
		performAction: function($el, action) {
			if (action === Action.TOGGLE) {
				if ($el.hasClass('is-active')) {
					action = Action.DEACTIVATE;
				} else {
					action = Action.ACTIVATE;
				}
			}

			var pending = [$el], processed = [],
				id, dependants, group, $current,
                iterations = 0,
                el, chain, $other, i;

			while (pending.length > 0) {
				$current = pending.shift();

				// Skip already processed nodes
				if (processed.indexOf($current) !== -1) {
					continue;
				}

				id = $current.attr('id');
				group = $current.data('group');

				// NOTE: Perhaps don't process these; add to some kind of queue first (with activate or deactivate action)
				for (i = 0, all = $('[data-group="' + group + '"]'), l = all.length; i < l; ++i) {
                    el = all[i];
    			    $other = $(el);

                    // Skip element itself
					if (el === $current[0]) {
                        continue;
                    }

                    // Just a group member, not the target: deactivate
                    $other.addClass('is-inactive').removeClass('is-active');
				}

                // Process target
                if (action === Action.ACTIVATE) {
                    $current.addClass('is-active').removeClass('is-inactive');
                } else {
                    $current.addClass('is-inactive').removeClass('is-active');
                }

                // Chain onto targets
                chain = ($current.data('cascade-' + action) || '').split(',')
                    .concat(($current.data('cascade') || '').split(','));

                for (i = 0, l = chain.length; i < l; ++i) {
                    $other = $('#' + chain[i]);

                    if (processed.indexOf($other[0]) !== -1 || pending.indexOf($other[0]) !== -1) {
                        continue;
                    }

                    pending.push($other);
                }

				processed.push($current[0]);
                iterations += 1;

                if (iterations > 100) {
                    throw new ChopChop.Exception("Too many iterations, aborting");
                }
			}
		}
    };

    // Expose toggle api
    ChopChop.api.toggle = function() {
        return ChopChop.plugins.toggle.toggle.apply(ChopChop.plugins.toggle, arguments);
    };

    return ChopChop;
})(jQuery, ChopChop || {});

<commit_msg>Trigger activate and deactivate events<commit_after>// Generics
var ChopChop = (function($, ChopChop) {
    var api = ChopChop.api = {},
        plugins = ChopChop.plugins = {};

    // Exception
    var Exception = ChopChop.Exception = function(message) {
        this.message = message;
    };

    Exception.prototype = {
        toString: function() {
            return 'EXCEPTION: ' + this.message;
        }
    };

    // Initialisation
    var defaultInitOptions = {
        'toggle': {}
    };

    ChopChop.init = api.init = function(options) {
        options = options || defaultInitOptions;

        for (var plugin in options) {
            if (!options.hasOwnProperty(plugin)) {
                continue;
            }

            var name = plugin.charAt(0).toUpperCase() + plugin.substr(1);

            if (!plugins[plugin] && ChopChop[name]) {
                plugins[plugin] = new ChopChop[name](options);
            }
        }
    };

    // jQuery goodness
    $.fn.chopchop = function() {
        var args = Array.prototype.slice.call(arguments),
            numArgs = args.length;

        if (numArgs === 0) {
            return api;
        }

        var method = args.shift();

        if (api.hasOwnProperty(method)) {
            return this.each(function() {
                var params = args.slice(0);
                params.unshift(this);
                api[method].apply(api, params);
            });
        }
    };

    return ChopChop;
})(jQuery, ChopChop || {});

// Toggles
var ChopChop = (function($, ChopChop) {
    var defaultOptions = {
        classActive: 'is-active',
        classInactive: 'is-inactive'
    };

    // Toggle
    var Toggle = ChopChop.Toggle = function(options) {
        this.options = $.extend({}, defaultOptions, options);
        this.init();
    };

	var Action = {
		TOGGLE: 'toggle',
		ACTIVATE: 'activate',
		DEACTIVATE: 'deactivate'
	};

    var Trigger = {
        DIRECT_ONLY: 'direct-only',
        INDIRECT_ONLY: 'indirect-only',     // Why would you want this? Oh well.
        BOTH: 'both'    // Default
    };

    Toggle.prototype = {
        init: function() {
            var self = this;

            $(document).on('click', '[data-action]', function(e) {
                var $this = $(this), t,
                    $target = (t = $this.data('target')) ? $('#' + t) : false,
                    mode = $this.data('trigger-on') || Trigger.BOTH;

                if (!$target) {
                    return;
                }

                if (mode === Trigger.DIRECT_ONLY && e.target !== this) {
                    return;
                } else if (mode === Trigger.INDIRECT_ONLY && e.target === this) {
                    return;
                }

                e.preventDefault();
				self.performAction($target, $this.data('action') || Action.TOGGLE);
            });
        },
        toggle: function(target, action) {
            var $target = typeof target === 'string' ? $('#' + target) : $(target);

            if (!$target.size()) {
                return;
            }

            this.performAction($target, action || Action.TOGGLE);
        },
		performAction: function($el, action) {
			if (action === Action.TOGGLE) {
				if ($el.hasClass('is-active')) {
					action = Action.DEACTIVATE;
				} else {
					action = Action.ACTIVATE;
				}
			}

			var pending = [$el], processed = [],
				id, dependants, group, $current,
                iterations = 0,
                el, chain, $other, i;

			while (pending.length > 0) {
				$current = pending.shift();

				// Skip already processed nodes
				if (processed.indexOf($current) !== -1) {
					continue;
				}

				id = $current.attr('id');
				group = $current.data('group');

				// NOTE: Perhaps don't process these; add to some kind of queue first (with activate or deactivate action)
				for (i = 0, all = $('[data-group="' + group + '"]'), l = all.length; i < l; ++i) {
                    el = all[i];
    			    $other = $(el);

                    // Skip element itself
					if (el === $current[0]) {
                        continue;
                    }

                    // Just a group member, not the target: deactivate
                    $other.addClass('is-inactive').removeClass('is-active');
				}

                // Process target
                if (action === Action.ACTIVATE) {
                    $current.addClass('is-active').removeClass('is-inactive');
                    $current.trigger('chopchop:activate');
                } else {
                    $current.addClass('is-inactive').removeClass('is-active');
                    $current.trigger('chopchop:deactivate');
                }

                // Chain onto targets
                chain = ($current.data('cascade-' + action) || '').split(',')
                    .concat(($current.data('cascade') || '').split(','));

                for (i = 0, l = chain.length; i < l; ++i) {
                    $other = $('#' + chain[i]);

                    if (processed.indexOf($other[0]) !== -1 || pending.indexOf($other[0]) !== -1) {
                        continue;
                    }

                    pending.push($other);
                }

				processed.push($current[0]);
                iterations += 1;

                if (iterations > 100) {
                    throw new ChopChop.Exception("Too many iterations, aborting");
                }
			}
		}
    };

    // Expose toggle api
    ChopChop.api.toggle = function() {
        return ChopChop.plugins.toggle.toggle.apply(ChopChop.plugins.toggle, arguments);
    };

    return ChopChop;
})(jQuery, ChopChop || {});

<|endoftext|>


<commit_before>(function($){
    $.fn.metroSelect = function(options) {
        var settings = $.extend( {
            'peeking'           : '2',
            'active-class'      : 'sel-active',
            'onchange'          : function(){}
        }, options);

        var uniq = this.attr("id") + "-";
        var sel = $("

");
        var select = this;
        sel.attr("class", select.attr("class"));
        select.parent().append(sel);
        isel = $("#" + uniq + "inner-selector");

        select.hide();
        var max_width = 0;
        select.children().each(function(key, val) {
            var opt = $("" + val.text + "");
            isel.append(opt);
            max_width = Math.max(max_width, opt.width());
        });
        //left side button
        $("#" + uniq + "sel-0").click(function () {
            //if left button clicked, swipe left
            select.val($("#" + uniq + "sel-0").text());
            $("#" + uniq + "sel-0").addClass(settings['active-class']);
            $("#" + uniq + "sel-1").removeClass(settings['active-class']);
            $("#" + uniq + "selector").animate({scrollLeft: 0}, "fast");
            settings.onchange();
        });

        //right side button
        $("#" + uniq + "sel-1").click(function () {
            //if the right button clicked swipe right
            select.val($("#" + uniq + "sel-1").text());
            $("#" + uniq + "sel-0").removeClass(settings['active-class']);
            $("#" + uniq + "sel-1").addClass(settings['active-class']);
            $("#" + uniq + "selector").animate({scrollLeft: $("#" + uniq + "sel-1").offset().left}, "fast");
            settings.onchange();
        });

        //setup the view and show what needs to be shown
        sel.css('height', $("#" + uniq + "sel-opt").css('font-size'));
        sel.css('width', max_width + max_width/parseInt(settings['peeking']));
        sel.css('overflow', 'hidden');

        //set the default visibilities
        if(select.val() == $("#" + uniq + "sel-1").text()) {
            $("#" + uniq + "sel-1").click();
        } else {
            $("#" + uniq + "sel-0").click();
        }
    };
})(jQuery);
<commit_msg>Changed strings to indexes<commit_after>(function($){
    $.fn.metroSelect = function(options) {
        var settings = $.extend( {
            'peeking'           : '2',
            'active-class'      : 'sel-active',
            'onchange'          : function(){}
        }, options);

        var uniq = this.attr("id") + "-";
        var sel = $("

");
        var select = this;
        sel.attr("class", select.attr("class"));
        select.parent().append(sel);
        isel = $("#" + uniq + "inner-selector");

        select.hide();
        var max_width = 0;
        select.children().each(function(key, val) {
            var opt = $("" + val.text + "");
            isel.append(opt);
            max_width = Math.max(max_width, opt.width());
        });
        //left side button
        $("#" + uniq + "sel-0").click(function () {
            //if left button clicked, swipe left
            select.prop("selectedIndex", 0);
            $("#" + uniq + "sel-0").addClass(settings['active-class']);
            $("#" + uniq + "sel-1").removeClass(settings['active-class']);
            $("#" + uniq + "selector").animate({scrollLeft: 0}, "fast");
            settings.onchange();
        });

        //right side button
        $("#" + uniq + "sel-1").click(function () {
            //if the right button clicked swipe right
            select.prop("selectedIndex", 1);
            $("#" + uniq + "sel-0").removeClass(settings['active-class']);
            $("#" + uniq + "sel-1").addClass(settings['active-class']);
            $("#" + uniq + "selector").animate({scrollLeft: $("#" + uniq + "sel-1").offset().left + max_width}, "fast");
            //$("#" + uniq + "selector").animate({scrollLeft: max_width}, "fast");
            settings.onchange();
        });

        //setup the view and show what needs to be shown
        //sel.css('height', $("#" + uniq + "sel-opt").css('font-size'));
        sel.css('width', max_width + max_width/parseInt(settings['peeking']));
        sel.css('overflow', 'hidden');

        //set the default visibilities
        $("#" + uniq + "sel-" + select.prop("selectedIndex")).click();
    };
})(jQuery);
<|endoftext|>


<commit_before>/*
 * jQuery UI CoverFlow II
 *
 * Refactored for jQuery 1.8 / jQueryUI 1.9 Sebastian Sauer
 * Re-written for jQueryUI 1.8.6/jQuery core 1.4.4+ by Addy Osmani with adjustments
 * Maintenance updates for 1.8.9/jQuery core 1.5, 1.6.2 made.
 * Original Component: Paul Bakaus for jQueryUI 1.7
 *
 * Released under the MIT license.
 *
 * Depends:
 *  jquery.ui.core.js
 *  jquery.ui.widget.js
 *  jquery.ui.effect.js
 *
 * Events:
 *  beforeselect
 *  select
 *  orientationchange
 */

(function( $ ) {

	if( $.support.transform != null ) {
		return;
	}

	if( typeof Modernizr !== "undefined" && Modernizr.csstransforms != null ) {
		$.support.transform = Modernizr.csstransforms;
		return;
	}

	var el = $( "<div />" ),
		style = el.get(0).style,
		prefixes = 'Webkit Moz O ms'.split( ' ' );

	$.support.transform = 'transform' in style;

	if( $.support.transform ) {
		el.remove();
		return;
	}

	$.each( prefixes, function( i, p ) {
		if( p + 'Transform' in style ) {
			$.support.transform = true;
			// stop iteration
			return false;
		}
		return true;
	});

	el.remove();

})( jQuery );

(function ( $ ) {

	var _capitalize = function( str ) {
		return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
	};

	$.widget( "ui.coverflow", {

		options: {
			items: "> *",
			// item stacking - value 0>x<1
			stacking : 0.73,
			orientation: "horizontal",
			active: 0,
			duration : 400,
			easing: "easeOutQuint",
			// selection triggers
			trigger: [
				"focus",
				"click"
			]
		},

		_create: function () {

			var o = this.options,
				itemBindings = {},
				i, binding;

			this.items = this.element.find( o.items );

			this.currentIndex = o.active;

			if( $.isArray( o.trigger ) ) {
				for( i = 0; !! ( binding = o.trigger[ i++ ] ); ) {
					itemBindings[ binding ] = this._select;
				}

			} else if( ( typeof o.trigger === "string" ) && $.trim( o.trigger ) ) {

				itemBindings[ $.trim( o.trigger ).toLowerCase() ] = this._select;

			} else {

				itemBindings.click = this._select;

			}

			this.origElementDimensions = {
				width: this.element.width(),
				height: this.element.height()
			};

			this._on( this.items, itemBindings );
		},
		_init : function () {

			var o = this.options,
				css;

			o.stacking = parseFloat( o.stacking );
			o.stacking = o.stacking < 1 && o.stacking > 0
				? o.stacking
				: 0.73;

			this.element
				.addClass( "ui-coverflow" )
				.parent()
				.addClass( "ui-coverflow-wrapper" );

			this.itemMargin = - Math.floor( ( 1 - o.stacking ) / 2 * this.items.innerWidth() );

			this.items
				// apply a negative margin so items stack
				.css({
					margin : this.itemMargin
				})
				// set tabindex so widget items get focusable
				// makes items accessible by keyboard
				.addClass( 'ui-coverflow-item' )
				.prop( "tabIndex", 0 );

			this.itemWidth = this.items.width();
			this.itemHeight = this.items.height();

			if( o.orientation === "vertical" ) {
				this._topOrLeft = "top";
				this._widthOrHeight = "height";
				if( this._orientation != null && this._orientation === "horizontal" ) {
					this._trigger( "orientationchange", null, this._ui() );
				}
				this._orientation = "vertical";
				this.itemSize = o.stacking * this.items.innerHeight();
			} else {
				this._topOrLeft = "left";
				this._widthOrHeight = "width";
				if( this._orientation != null && this._orientation === "vertical" ) {
					this._trigger( "orientationchange", null, this._ui() );
				}
				this._orientation = "horizontal";
				this.itemSize = o.stacking * this.items.innerWidth();
			}

			this.outerWidthOrHeight = ( this._widthOrHeight === "width" )
				? this.element.parent().outerWidth( false )
				: this.element.parent().outerHeight( false );

			//Center the actual parents' left side within it's parent
			css = this._getCenterPosition();
			// make sure there's enough space
			css[ this._widthOrHeight ] = this.itemWidth * this.items.length;

			this.element.css( css );

			//Jump to the first item
			this._refresh( 1, 0, this.currentIndex );
			this._trigger( "select", null, this._ui() );
		},
		_getCenterPosition : function () {
			var animation = {},
				pos;

			pos = - this.currentIndex * this.itemSize / 2;
			pos += this.outerWidthOrHeight / 2 - this.itemSize / 2;
			pos -= parseInt( this.element.css('padding' + _capitalize( this._topOrLeft ) ) ,10 ) || 0;

			animation[ this._topOrLeft ] = Math.floor( pos );

			return animation;
		},
		_select: function ( ev ) {

			this.select( ev.currentTarget );

		},
		select : function( item ) {

			var o = this.options,
				index = ! isNaN( parseInt( item, 10 ) )
					? parseInt( item, 10 )
					: this.items.index( item );

			if( this.currentIndex === index ) {
				return false;
			}

			if( false === this._trigger(
					"beforeselect",
					null,
					this._ui(
						this.items.eq( index ), index
					)
				)
			) {
				return false;
			}

			this.previousIndex = this.currentIndex;

			this.currentIndex = index;

			var self = this,
				to = Math.abs( self.previous - self.currentIndex ) <= 1
					? self.previousIndex
					: self.currentIndex + ( self.previousIndex < self.currentIndex ? -1 : 1 ),
				animation = {
					coverflow : 1
				},
				delta = this.previousIndex - this.currentIndex;

			//Overwrite $.fx.step.coverflow everytime again with custom scoped values for this specific animation
			$.fx.step.coverflow = function( fx ) {
				self._refresh( fx.now, to, self.currentIndex );
			};

			// 1. Stop the previous animation
			// 2. Animate the parent"s left/top property so the current item is in the center
			// 3. Use our custom coverflow animation which animates the item

			$.extend( animation, this._getCenterPosition() );
			this.element
				// jump to end and release select trigger
				.stop( true, true )
				.animate(
					animation,
					{
						duration: o.duration,
						easing: o.easing,
						complete : function () {
							// fire select after animation has finished
							self._trigger( "select", null, self._ui() );
						}
					}
				);

			return true;
		},

		_refresh: function( state, from, to ) {

			var self = this,
				offset = null;

			this.items.each( function ( i ) {

				var side = ( ( i === to && from - to < 0 ) || i - to  > 0 )
						? "left"
						: "right",
					mod = ( i === to )
						? ( 1 - state )
						: ( i === from ? state : 1 ),
					before = ( i > from && i !== to ),
					css = {
						zIndex: self.items.length + ( side === "left" ? to - i : i - to )
					},
					scale = ( 1 + ( ( 1 - mod ) * 0.3 ) ),
					matrixT, filters;

				css[ self._topOrLeft ] = (
					( -i * ( self.itemSize / 2 ) )
					+ ( side === "right"
						? -self.itemSize / 2
						: self.itemSize / 2
					) * mod
				);

				// transponed matrix
				matrixT = [
					scale, ( mod * ( side === "right" ? -0.2 : 0.2 ) ),
					0, scale,
					0, 0
				];

				if( ! $.support.transform && $.browser.msie ) {

					// Adapted from Paul Baukus transformie lib
					if( ! this.filters[ "DXImageTransform.Microsoft.Matrix" ] ) {
						this.style.filter = (this.style.filter ? '' : ' ' ) + "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand')";
					}
					filters = this.filters[ "DXImageTransform.Microsoft.Matrix" ];
					filters.M11 = matrixT[ 0 ];
					filters.M12 = matrixT[ 2 ];
					filters.M21 = matrixT[ 1 ];
					filters.M22 = matrixT[ 3 ];

				} else {
					css.transform = "matrix(" + matrixT.join( "," ) + ")";
				}

				$( this ).css( css );

			});

			this.element
				.parent()
				.scrollTop( 0 );
		},

		_ui : function ( active, index ) {
			return {
				active: active || this.items.eq( this.currentIndex ),
				index: index || this.currentIndex
			};
		},
		_destroy : function () {

			this.element
				.css( this.origElementDimensions )
				.removeClass( 'ui-coverflow' )
				.parent()
				.removeClass( 'ui-coverflow-wrapper' );

			this.items.each( function () {
				// TODO: needs testing
				// remove transform
				this.style = this.style.replace( /(webkit|moz|o|ms)?transform.*;/i, '' );
				// remove margin
				this.style = this.style.replace( /margin.*;/, '' );
			});

			this._superApply( "destroy", arguments );
		}

	});

})( jQuery );
<commit_msg>integrate mouse wheel bindings<commit_after>/*
 * jQuery UI CoverFlow II
 *
 * Refactored for jQuery 1.8 / jQueryUI 1.9 Sebastian Sauer
 * Re-written for jQueryUI 1.8.6/jQuery core 1.4.4+ by Addy Osmani with adjustments
 * Maintenance updates for 1.8.9/jQuery core 1.5, 1.6.2 made.
 * Original Component: Paul Bakaus for jQueryUI 1.7
 *
 * Released under the MIT license.
 *
 * Depends:
 *  jquery.ui.core.js
 *  jquery.ui.widget.js
 *  jquery.ui.effect.js
 *
 * Events:
 *  beforeselect
 *  select
 *  orientationchange
 */

(function( $ ) {

	if( $.support.transform != null ) {
		return;
	}

	if( typeof Modernizr !== "undefined" && Modernizr.csstransforms != null ) {
		$.support.transform = Modernizr.csstransforms;
		return;
	}

	var el = $( "<div />" ),
		style = el.get(0).style,
		prefixes = 'Webkit Moz O ms'.split( ' ' );

	$.support.transform = 'transform' in style;

	if( $.support.transform ) {
		el.remove();
		return;
	}

	$.each( prefixes, function( i, p ) {
		if( p + 'Transform' in style ) {
			$.support.transform = true;
			// stop iteration
			return false;
		}
		return true;
	});

	el.remove();

})( jQuery );

(function ( $ ) {

	var _capitalize = function( str ) {
		return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
	};

	$.widget( "ui.coverflow", {

		options: {
			items: "> *",
			// item stacking - value 0>x<1
			stacking : 0.73,
			orientation: "horizontal",
			active: 0,
			duration : 400,
			easing: "easeOutQuint",
			// selection triggers
			trigger: [
				"focus",
				"click"
			]
		},

		_create: function () {

			var o = this.options,
				itemBindings = {},
				i, binding;

			this.items = this.element.find( o.items );

			this.currentIndex = o.active;

			if( $.isArray( o.trigger ) ) {
				for( i = 0; !! ( binding = o.trigger[ i++ ] ); ) {
					itemBindings[ binding ] = this._select;
				}

			} else if( ( typeof o.trigger === "string" ) && $.trim( o.trigger ) ) {

				itemBindings[ $.trim( o.trigger ).toLowerCase() ] = this._select;

			} else {

				itemBindings.click = this._select;

			}

			this.origElementDimensions = {
				width: this.element.width(),
				height: this.element.height()
			};

			this._on( this.items, itemBindings );

			this._on({
				mousewheel : this._onMouseWheel,
				DOMMouseScroll : this._onMouseWheel
			});
		},
		_init : function () {

			var o = this.options,
				css;

			o.stacking = parseFloat( o.stacking );
			o.stacking = o.stacking < 1 && o.stacking > 0
				? o.stacking
				: 0.73;

			this.element
				.addClass( "ui-coverflow" )
				.parent()
				.addClass( "ui-coverflow-wrapper" );

			this.itemMargin = - Math.floor( ( 1 - o.stacking ) / 2 * this.items.innerWidth() );

			this.items
				// apply a negative margin so items stack
				.css({
					margin : this.itemMargin
				})
				// set tabindex so widget items get focusable
				// makes items accessible by keyboard
				.addClass( 'ui-coverflow-item' )
				.prop( "tabIndex", 0 );

			this.itemWidth = this.items.width();
			this.itemHeight = this.items.height();

			if( o.orientation === "vertical" ) {
				this._topOrLeft = "top";
				this._widthOrHeight = "height";
				if( this._orientation != null && this._orientation === "horizontal" ) {
					this._trigger( "orientationchange", null, this._ui() );
				}
				this._orientation = "vertical";
				this.itemSize = o.stacking * this.items.innerHeight();
			} else {
				this._topOrLeft = "left";
				this._widthOrHeight = "width";
				if( this._orientation != null && this._orientation === "vertical" ) {
					this._trigger( "orientationchange", null, this._ui() );
				}
				this._orientation = "horizontal";
				this.itemSize = o.stacking * this.items.innerWidth();
			}

			this.outerWidthOrHeight = ( this._widthOrHeight === "width" )
				? this.element.parent().outerWidth( false )
				: this.element.parent().outerHeight( false );

			//Center the actual parents' left side within it's parent
			css = this._getCenterPosition();
			// make sure there's enough space
			css[ this._widthOrHeight ] = this.itemWidth * this.items.length;

			this.element.css( css );

			//Jump to the first item
			this._refresh( 1, 0, this.currentIndex );
			this._trigger( "select", null, this._ui() );
		},
		_getCenterPosition : function () {
			var animation = {},
				pos;

			pos = - this.currentIndex * this.itemSize / 2;
			pos += this.outerWidthOrHeight / 2 - this.itemSize / 2;
			pos -= parseInt( this.element.css('padding' + _capitalize( this._topOrLeft ) ) ,10 ) || 0;

			animation[ this._topOrLeft ] = Math.floor( pos );

			return animation;
		},
		_select: function ( ev ) {
			this.select( ev.currentTarget );
		},
		next : function () {
			return this.select( this.currentIndex + 1 );
		},
		prev : function () {
			return this.select( this.currentIndex - 1 );
		},
		select : function( item ) {

			var o = this.options,
				index = ! isNaN( parseInt( item, 10 ) )
					? parseInt( item, 10 )
					: this.items.index( item );

			if( this.currentIndex === index || index < 0 || index > this.items.length ) {
				return false;
			}

			if( false === this._trigger(
					"beforeselect",
					null,
					this._ui(
						this.items.eq( index ), index
					)
				)
			) {
				return false;
			}

			this.previousIndex = this.currentIndex;

			this.currentIndex = index;

			var self = this,
				to = Math.abs( self.previous - self.currentIndex ) <= 1
					? self.previousIndex
					: self.currentIndex + ( self.previousIndex < self.currentIndex ? -1 : 1 ),
				animation = {
					coverflow : 1
				},
				delta = this.previousIndex - this.currentIndex;

			//Overwrite $.fx.step.coverflow everytime again with custom scoped values for this specific animation
			$.fx.step.coverflow = function( fx ) {
				self._refresh( fx.now, to, self.currentIndex );
			};

			// 1. Stop the previous animation
			// 2. Animate the parent"s left/top property so the current item is in the center
			// 3. Use our custom coverflow animation which animates the item

			$.extend( animation, this._getCenterPosition() );
			this.element
				// jump to end and release select trigger
				.stop( true, true )
				.animate(
					animation,
					{
						duration: o.duration,
						easing: o.easing,
						complete : function () {
							// fire select after animation has finished
							self._trigger( "select", null, self._ui() );
						}
					}
				);

			return true;
		},

		_refresh: function( state, from, to ) {

			var self = this,
				offset = null;

			this.items.each( function ( i ) {

				var side = ( ( i === to && from - to < 0 ) || i - to  > 0 )
						? "left"
						: "right",
					mod = ( i === to )
						? ( 1 - state )
						: ( i === from ? state : 1 ),
					before = ( i > from && i !== to ),
					css = {
						zIndex: self.items.length + ( side === "left" ? to - i : i - to )
					},
					scale = ( 1 + ( ( 1 - mod ) * 0.3 ) ),
					matrixT, filters;

				css[ self._topOrLeft ] = (
					( -i * ( self.itemSize / 2 ) )
					+ ( side === "right"
						? -self.itemSize / 2
						: self.itemSize / 2
					) * mod
				);

				// transponed matrix
				matrixT = [
					scale, ( mod * ( side === "right" ? -0.2 : 0.2 ) ),
					0, scale,
					0, 0
				];

				if( ! $.support.transform && $.browser.msie ) {

					// Adapted from Paul Baukus transformie lib
					if( ! this.filters[ "DXImageTransform.Microsoft.Matrix" ] ) {
						this.style.filter = (this.style.filter ? '' : ' ' ) + "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand')";
					}
					filters = this.filters[ "DXImageTransform.Microsoft.Matrix" ];
					filters.M11 = matrixT[ 0 ];
					filters.M12 = matrixT[ 2 ];
					filters.M21 = matrixT[ 1 ];
					filters.M22 = matrixT[ 3 ];

				} else {
					css.transform = "matrix(" + matrixT.join( "," ) + ")";
				}

				$( this ).css( css );

			});

			this.element
				.parent()
				.scrollTop( 0 );
		},

		_ui : function ( active, index ) {
			return {
				active: active || this.items.eq( this.currentIndex ),
				index: index || this.currentIndex
			};
		},
		_onMouseWheel : function ( ev ) {
			var origEv = ev.originalEvent;

			ev.preventDefault();

			if( origEv.wheelDelta > 0 || origEv.detail < 0 ) {
				this.next();
				return;
			}
			this.prev();
		},
		_destroy : function () {

			this.element
				.css( this.origElementDimensions )
				.removeClass( 'ui-coverflow' )
				.parent()
				.removeClass( 'ui-coverflow-wrapper' );

			this.items.each( function () {
				// TODO: needs testing
				// remove transform
				this.style = this.style.replace( /(webkit|moz|o|ms)?transform.*;/i, '' );
				// remove margin
				this.style = this.style.replace( /margin.*;/, '' );
			});

			this._superApply( "destroy", arguments );
		}

	});

})( jQuery );
<|endoftext|>


<commit_before>/**
 * SignIn to mapzen.com
 */
import store from '../store';
import config from '../config';
import { USER_SIGNED_IN, USER_SIGNED_OUT, SET_APP_STATE } from '../store/actions';
import { getURLSearchParam } from '../tools/url-state';

const SIGN_IN_STATE_API_ENDPOINT = '/api/developer.json';
const SIGN_OUT_API_ENDPOINT = '/api/developer/sign_out';

// Set credentials option for window.fetch depending on host.
// Cookies are sent for each request only if the origin matches on mapzen.com,
// but are always included for local environments (this allows back-ends to
// run on different ports than Tangram Play.
const signInCredentials = window.location.hostname === 'localhost' ? 'include' : 'same-origin';
const signInHost = window.location.hostname === 'localhost' ? config.MAPZEN_API.ORIGIN.DEVELOPMENT : '';

let cachedSignInData;

// Sign-in is enabled if the host matches https://mapzen.com/ or https://dev.mapzen.com/
// Or if it is a localhost server (for local testing) and the query string ?forceSignIn=true
// It is disabled on http and any other host.
function isSignInEnabled() {
  const system = store.getState().system;
  return (system.mapzen && system.ssl) || (system.localhost && getURLSearchParam('forceSignIn') === 'true');
}

function enableAdminFlags() {
  store.dispatch({
    type: SET_APP_STATE,
    disableMultiFile: false,
  });
}

function disableAdminFlags() {
  store.dispatch({
    type: SET_APP_STATE,
    // Only disable again if not on localhost
    disableMultiFile: (window.location.hostname !== 'localhost'),
  });
}

/**
 * Request user sign-in information from mapzen.com. This only works with
 * mapzen.com, dev.mapzen.com, or www.mapzen.com. Other mapzen.com domains
 * (e.g. Precog) do not have this `/api/developer.json` endpoint. Also check for
 * https protocol. A no-CORS error is thrown if accessed on http
 *
 * If logged in, the response looks like this:
 *
 * {"logged_in":true,"id":7,"email":"name@domain.com","nickname":"name","admin":null}
 *
 * If not logged in, the response is an empty object, like this:
 *
 * {}
 *
 * If the user is an admin, the "admin" field is boolean `true`. Note that
 * non-admins are `null` not `false`.
 *
 * @returns {Promise} - resolved value is contents of `/api/developer.json`
 *          or `null` if Tangram Play is not hosted on a domain where this
 *          API is available.
 * @todo Handle errors related to fetching API.
 */
export function requestUserSignInState() {
  if (isSignInEnabled()) {
    return window.fetch(signInHost + SIGN_IN_STATE_API_ENDPOINT, { credentials: signInCredentials })
      .then((response) => {
        if (!response.ok) {
          throw new Error(response.status);
        }

        return response.json();
      })
      .then((data) => {
        const previousUserData = cachedSignInData;
        cachedSignInData = data;

        if (Object.keys(data).length > 0) {
          // Only dispatch if data does not match cache, or cache not present.
          if (!previousUserData || data.id !== previousUserData.id) {
            store.dispatch({
              type: USER_SIGNED_IN,
              id: data.id,
              nickname: data.nickname,
              email: data.email,
              avatar: data.avatar,
              admin: data.admin,
            });

            if (data.admin === true) {
              enableAdminFlags();
            }
          }
        }

        return data;
      });
  }

  // Returns a promise that resolves to `null` if Tangram Play is not
  // hosted somewhere where the `/api/developer.json` endpoint is
  // available.
  return Promise.resolve(null);
}

// Note: deprecated (only used for Gist)
export function getCachedUserSignInData() {
  return cachedSignInData;
}

/**
 * Assumes this is only available when already logged in, so no host check
 * is performed.
 */
export function requestUserSignOut() {
  return window.fetch(signInHost + SIGN_OUT_API_ENDPOINT, {
    method: 'POST',
    credentials: signInCredentials,
  }).then((response) => {
    if (!response.ok) {
      throw new Error(response.status);
    }

    store.dispatch({ type: USER_SIGNED_OUT });
    disableAdminFlags();
  });
}
<commit_msg>Clear cached sign in data on sign out<commit_after>/**
 * SignIn to mapzen.com
 */
import store from '../store';
import config from '../config';
import { USER_SIGNED_IN, USER_SIGNED_OUT, SET_APP_STATE } from '../store/actions';
import { getURLSearchParam } from '../tools/url-state';

const SIGN_IN_STATE_API_ENDPOINT = '/api/developer.json';
const SIGN_OUT_API_ENDPOINT = '/api/developer/sign_out';

// Set credentials option for window.fetch depending on host.
// Cookies are sent for each request only if the origin matches on mapzen.com,
// but are always included for local environments (this allows back-ends to
// run on different ports than Tangram Play.
const signInCredentials = window.location.hostname === 'localhost' ? 'include' : 'same-origin';
const signInHost = window.location.hostname === 'localhost' ? config.MAPZEN_API.ORIGIN.DEVELOPMENT : '';

let cachedSignInData;

// Sign-in is enabled if the host matches https://mapzen.com/ or https://dev.mapzen.com/
// Or if it is a localhost server (for local testing) and the query string ?forceSignIn=true
// It is disabled on http and any other host.
function isSignInEnabled() {
  const system = store.getState().system;
  return (system.mapzen && system.ssl) || (system.localhost && getURLSearchParam('forceSignIn') === 'true');
}

function enableAdminFlags() {
  store.dispatch({
    type: SET_APP_STATE,
    disableMultiFile: false,
  });
}

function disableAdminFlags() {
  store.dispatch({
    type: SET_APP_STATE,
    // Only disable again if not on localhost
    disableMultiFile: (window.location.hostname !== 'localhost'),
  });
}

/**
 * Request user sign-in information from mapzen.com. This only works with
 * mapzen.com, dev.mapzen.com, or www.mapzen.com. Other mapzen.com domains
 * (e.g. Precog) do not have this `/api/developer.json` endpoint. Also check for
 * https protocol. A no-CORS error is thrown if accessed on http
 *
 * If logged in, the response looks like this:
 *
 * {"logged_in":true,"id":7,"email":"name@domain.com","nickname":"name","admin":null}
 *
 * If not logged in, the response is an empty object, like this:
 *
 * {}
 *
 * If the user is an admin, the "admin" field is boolean `true`. Note that
 * non-admins are `null` not `false`.
 *
 * @returns {Promise} - resolved value is contents of `/api/developer.json`
 *          or `null` if Tangram Play is not hosted on a domain where this
 *          API is available.
 * @todo Handle errors related to fetching API.
 */
export function requestUserSignInState() {
  if (isSignInEnabled()) {
    return window.fetch(signInHost + SIGN_IN_STATE_API_ENDPOINT, { credentials: signInCredentials })
      .then((response) => {
        if (!response.ok) {
          throw new Error(response.status);
        }

        return response.json();
      })
      .then((data) => {
        const previousUserData = cachedSignInData;
        cachedSignInData = data;

        if (Object.keys(data).length > 0) {
          // Only dispatch if data does not match cache, or cache not present.
          if (!previousUserData || data.id !== previousUserData.id) {
            store.dispatch({
              type: USER_SIGNED_IN,
              id: data.id,
              nickname: data.nickname,
              email: data.email,
              avatar: data.avatar,
              admin: data.admin,
            });

            if (data.admin === true) {
              enableAdminFlags();
            }
          }
        }

        return data;
      });
  }

  // Returns a promise that resolves to `null` if Tangram Play is not
  // hosted somewhere where the `/api/developer.json` endpoint is
  // available.
  return Promise.resolve(null);
}

// Note: deprecated (only used for Gist)
export function getCachedUserSignInData() {
  return cachedSignInData;
}

/**
 * Assumes this is only available when already logged in, so no host check
 * is performed.
 */
export function requestUserSignOut() {
  return window.fetch(signInHost + SIGN_OUT_API_ENDPOINT, {
    method: 'POST',
    credentials: signInCredentials,
  }).then((response) => {
    if (!response.ok) {
      throw new Error(response.status);
    }

    store.dispatch({ type: USER_SIGNED_OUT });
    cachedSignInData = undefined;
    disableAdminFlags();
  });
}
<|endoftext|>