<commit_before>import _ from 'lodash'
import loadCharts from '../utils/googleChartsLoader.js'
import eventsBinder from '../utils/eventsBinder.js'
import propsWatcher from '../utils/propsWatcher.js'

let props = {
  packages: {
    type: Array,
    default: function () {
      return ['corechart']
    }
  },
  version: {
    default: 'current',
    coerce: function (val) {
      // They might pass in an integer, make it a string.
      return _.toString(val)
    }
  },
  chartType: {
    required: true,
    type: String,
    default: function () {
      return 'LineChart'
    }
  },
  chartEvents: {
    type: Object,
    default: function () {
      return {}
    }
  },
  columns: {
    required: true,
    type: Array
  },
  rows: {
    type: Array,
    default: function () {
      return []
    }
  },
  options: {
    type: Object,
    default: function () {
      return {
        chart: {
          title: 'Chart Title',
          subtitle: 'Subtitle'
        },
        hAxis: {
          title: 'X Label'
        },
        vAxis: {
          title: 'Y Label'
        },
        width: '400px',
        height: '300px',
        animation: {
          duration: 500,
          easing: 'out'
        }
      }
    }
  }
}

export default {
  name: 'vue-chart',
  props: props,
  template: '
' +
              '

' +
            '
',
  data: function () {
    return {
      chart: null,
      /*
          We put the uid in the DOM element so the component can be used multiple
          times in the same view. Otherwise Google Charts will only make one chart.

          The X is prepended because there must be at least
          1 character in id - https://www.w3.org/TR/html5/dom.html#the-id-attribute
      */
      chartId: 'X' + this._uid,
      wrapper: null,
      dataTable: [],
      hiddenColumns: []
    }
  },
  ready: function () {
    let self = this
    loadCharts(self.packages, self.version)
      .then(self.drawChart)
      .then(function () {
        // we don't want to bind props because it's a kind of "computed" property
        const watchProps = props
        delete watchProps.bounds

        // watching properties
        propsWatcher(self, watchProps)

        // binding events
        eventsBinder(self, self.chart, self.chartEvents)
      })
      .catch(function (error) {
        throw error
      })
  },
  methods: {
    /**
     * Initialize the datatable and add the initial data.
     *
     * @link https://developers.google.com/chart/interactive/docs/reference#DataTable
     * @return object
     */
    buildDataTable: function () {
      let self = this

      let dataTable = new google.visualization.DataTable()

      _.each(self.columns, function (value) {
        dataTable.addColumn(value)
      })

      if (!_.isEmpty(self.rows)) {
        dataTable.addRows(self.rows)
      }

      return dataTable
    },
    /**
     * Update the datatable.
     *
     * @return void
     */
    updateDataTable: function () {
      let self = this

      // Remove all data from the datatable.
      self.dataTable.removeRows(0, self.dataTable.getNumberOfRows())
      self.dataTable.removeColumns(0, self.dataTable.getNumberOfColumns())

      // Add
      _.each(self.columns, function (value) {
        self.dataTable.addColumn(value)
      })

      if (!_.isEmpty(self.rows)) {
        self.dataTable.addRows(self.rows)
      }
    },
    /**
     * Initialize the wrapper
     *
     * @link https://developers.google.com/chart/interactive/docs/reference#chartwrapper-class
     *
     * @return object
     */
    buildWrapper: function (chartType, dataTable, options, containerId) {
      let wrapper = new google.visualization.ChartWrapper({
        chartType: chartType,
        dataTable: dataTable,
        options: options,
        containerId: containerId
      })

      return wrapper
    },
    /**
     * Build the chart.
     *
     * @return void
     */
    buildChart: function () {
      let self = this

      // If dataTable isn't set, build it
      let dataTable = _.isEmpty(self.dataTable) ? self.buildDataTable() : self.dataTable

      self.wrapper = self.buildWrapper(self.chartType, dataTable, self.options, self.chartId)

      // Set the datatable on this instance
      self.dataTable = self.wrapper.getDataTable()

      // After chart is built, set it on this instance
      google.visualization.events.addOneTimeListener(self.wrapper, 'ready', function () {
        self.chart = self.wrapper.getChart()
      })
    },
    /**
     * Draw the chart.
     *
     * @return void
     */
    drawChart: function () {
      let self = this

      // We don't have any (usable) data, or we don't have columns. We can't draw a chart without those.
      if ((!_.isEmpty(self.rows) && !_.isObjectLike(self.rows)) || _.isEmpty(self.columns)) {
        return
      }

      if (_.isNull(self.chart)) {
        // We haven't built the chart yet, so JUST. DO. IT!
        self.buildChart()
      } else {
        // Chart already exists, just update the data
        self.updateDataTable()
      }

      // Chart has been built/Data has been updated, draw the chart.
      self.wrapper.draw()
    }
  }
}
<commit_msg>fixes haydenbbickerton/vue-charts#3<commit_after>import _ from 'lodash'
import loadCharts from '../utils/googleChartsLoader.js'
import eventsBinder from '../utils/eventsBinder.js'
import propsWatcher from '../utils/propsWatcher.js'

let props = {
  packages: {
    type: Array,
    default: function () {
      return ['corechart']
    }
  },
  version: {
    default: 'current'
  },
  chartType: {
    required: true,
    type: String,
    default: function () {
      return 'LineChart'
    }
  },
  chartEvents: {
    type: Object,
    default: function () {
      return {}
    }
  },
  columns: {
    required: true,
    type: Array
  },
  rows: {
    type: Array,
    default: function () {
      return []
    }
  },
  options: {
    type: Object,
    default: function () {
      return {
        chart: {
          title: 'Chart Title',
          subtitle: 'Subtitle'
        },
        hAxis: {
          title: 'X Label'
        },
        vAxis: {
          title: 'Y Label'
        },
        width: '400px',
        height: '300px',
        animation: {
          duration: 500,
          easing: 'out'
        }
      }
    }
  }
}

export default {
  name: 'vue-chart',
  props: props,
  template: '
' +
              '

' +
            '
',
  data: function () {
    return {
      chart: null,
      /*
          We put the uid in the DOM element so the component can be used multiple
          times in the same view. Otherwise Google Charts will only make one chart.

          The X is prepended because there must be at least
          1 character in id - https://www.w3.org/TR/html5/dom.html#the-id-attribute
      */
      chartId: 'X' + this._uid,
      wrapper: null,
      dataTable: [],
      hiddenColumns: []
    }
  },
  ready: function () {
    let self = this
    loadCharts(self.packages, self.version)
      .then(self.drawChart)
      .then(function () {
        // we don't want to bind props because it's a kind of "computed" property
        const watchProps = props
        delete watchProps.bounds

        // watching properties
        propsWatcher(self, watchProps)

        // binding events
        eventsBinder(self, self.chart, self.chartEvents)
      })
      .catch(function (error) {
        throw error
      })
  },
  methods: {
    /**
     * Initialize the datatable and add the initial data.
     *
     * @link https://developers.google.com/chart/interactive/docs/reference#DataTable
     * @return object
     */
    buildDataTable: function () {
      let self = this

      let dataTable = new google.visualization.DataTable()

      _.each(self.columns, function (value) {
        dataTable.addColumn(value)
      })

      if (!_.isEmpty(self.rows)) {
        dataTable.addRows(self.rows)
      }

      return dataTable
    },
    /**
     * Update the datatable.
     *
     * @return void
     */
    updateDataTable: function () {
      let self = this

      // Remove all data from the datatable.
      self.dataTable.removeRows(0, self.dataTable.getNumberOfRows())
      self.dataTable.removeColumns(0, self.dataTable.getNumberOfColumns())

      // Add
      _.each(self.columns, function (value) {
        self.dataTable.addColumn(value)
      })

      if (!_.isEmpty(self.rows)) {
        self.dataTable.addRows(self.rows)
      }
    },
    /**
     * Initialize the wrapper
     *
     * @link https://developers.google.com/chart/interactive/docs/reference#chartwrapper-class
     *
     * @return object
     */
    buildWrapper: function (chartType, dataTable, options, containerId) {
      let wrapper = new google.visualization.ChartWrapper({
        chartType: chartType,
        dataTable: dataTable,
        options: options,
        containerId: containerId
      })

      return wrapper
    },
    /**
     * Build the chart.
     *
     * @return void
     */
    buildChart: function () {
      let self = this

      // If dataTable isn't set, build it
      let dataTable = _.isEmpty(self.dataTable) ? self.buildDataTable() : self.dataTable

      self.wrapper = self.buildWrapper(self.chartType, dataTable, self.options, self.chartId)

      // Set the datatable on this instance
      self.dataTable = self.wrapper.getDataTable()

      // After chart is built, set it on this instance
      google.visualization.events.addOneTimeListener(self.wrapper, 'ready', function () {
        self.chart = self.wrapper.getChart()
      })
    },
    /**
     * Draw the chart.
     *
     * @return void
     */
    drawChart: function () {
      let self = this

      // We don't have any (usable) data, or we don't have columns. We can't draw a chart without those.
      if ((!_.isEmpty(self.rows) && !_.isObjectLike(self.rows)) || _.isEmpty(self.columns)) {
        return
      }

      if (_.isNull(self.chart)) {
        // We haven't built the chart yet, so JUST. DO. IT!
        self.buildChart()
      } else {
        // Chart already exists, just update the data
        self.updateDataTable()
      }

      // Chart has been built/Data has been updated, draw the chart.
      self.wrapper.draw()
    }
  }
}
<|endoftext|>


<commit_before>import expect from 'expect.js';
import React from 'react/addons';
const { TestUtils } = React.addons;
import TabPane from '../src/TabPane';
import Tab from '../src/Tab';

describe('TabPane', function () {

    const tabPane = TestUtils.renderIntoDocument(
        <TabPane orientation="horizontal" className="my-tabs">
            <Tab id="A" name="A">some stuff</Tab>
            <Tab id="B" name="B" active="true">whatevs</Tab>
            <Tab id="C" name="C">innit</Tab>
        </TabPane>
    );

    it('renders the tabs', function () {
        new Asserter(tabPane)
            .assertTabs(['A', 'B', 'C'])
        ;
    });

    it('indicate active tab', function () {
        new Asserter(tabPane)
            .assertActiveTab('B')
        ;
    });

    it('render active tab contents', function () {
        new Asserter(tabPane)
            .assertTabContents('whatevs')
        ;
    });

    it('click to activate tab', function () {
        new Asserter(tabPane)
            .assertActiveTab('B')
            .activateTab('C')
            .assertActiveTab('C')
        ;
    });

    class Asserter {

        constructor(tabPane) {
            this.component = TestUtils.findRenderedDOMComponentWithClass(tabPane, 'TabPane');
        }

        assertTabs(expectedTabs) {
            const tabs = TestUtils.scryRenderedDOMComponentsWithClass(this.component, 'Tab');
            const values = tabs.map((tab) => {
                return tab.getDOMNode().innerHTML;
            });
            expect(values).to.eql(expectedTabs);
            return this;
        }

        assertTabContents(expectedTabContents) {
            const tabContents = TestUtils.findRenderedDOMComponentWithClass(this.component, 'TabContent');
            expect(tabContents.getDOMNode().innerHTML).to.eql(expectedTabContents);
            return this;
        }

        assertActiveTab(expected) {
            const active = TestUtils.findRenderedDOMComponentWithClass(this.component, 'active');
            expect(active.getDOMNode().innerHTML).to.equal(expected);
            return this;
        }

        activateTab(tabName) {
            const tabs = TestUtils.scryRenderedDOMComponentsWithClass(this.component, 'Tab');
            tabs.forEach((tab) => {
                if (tab.getDOMNode().innerHTML === tabName) {
                    TestUtils.Simulate.click(tab);
                }
            });
            return this;
        }
    }

});<commit_msg>use chai<commit_after>import chai from 'chai';
const expect = chai.expect;
import React from 'react/addons';
const { TestUtils } = React.addons;
import TabPane from '../src/TabPane';
import Tab from '../src/Tab';

describe('TabPane', function () {

    const tabPane = TestUtils.renderIntoDocument(
        <TabPane orientation="horizontal" className="my-tabs">
            <Tab id="A" name="A">some stuff</Tab>
            <Tab id="B" name="B" active="true">whatevs</Tab>
            <Tab id="C" name="C">innit</Tab>
        </TabPane>
    );

    it('renders the tabs', function () {
        new Asserter(tabPane)
            .assertTabs(['A', 'B', 'C'])
        ;
    });

    it('indicate active tab', function () {
        new Asserter(tabPane)
            .assertActiveTab('B')
        ;
    });

    it('render active tab contents', function () {
        new Asserter(tabPane)
            .assertTabContents('whatevs')
        ;
    });

    it('click to activate tab', function () {
        new Asserter(tabPane)
            .assertActiveTab('B')
            .activateTab('C')
            .assertActiveTab('C')
        ;
    });

    class Asserter {

        constructor(tabPane) {
            this.component = TestUtils.findRenderedDOMComponentWithClass(tabPane, 'TabPane');
        }

        assertTabs(expectedTabs) {
            const tabs = TestUtils.scryRenderedDOMComponentsWithClass(this.component, 'Tab');
            const values = tabs.map((tab) => {
                return tab.getDOMNode().innerHTML;
            });
            expect(values).to.eql(expectedTabs);
            return this;
        }

        assertTabContents(expectedTabContents) {
            const tabContents = TestUtils.findRenderedDOMComponentWithClass(this.component, 'TabContent');
            expect(tabContents.getDOMNode().innerHTML).to.eql(expectedTabContents);
            return this;
        }

        assertActiveTab(expected) {
            const active = TestUtils.findRenderedDOMComponentWithClass(this.component, 'active');
            expect(active.getDOMNode().innerHTML).to.equal(expected);
            return this;
        }

        activateTab(tabName) {
            const tabs = TestUtils.scryRenderedDOMComponentsWithClass(this.component, 'Tab');
            tabs.forEach((tab) => {
                if (tab.getDOMNode().innerHTML === tabName) {
                    TestUtils.Simulate.click(tab);
                }
            });
            return this;
        }
    }

});<|endoftext|>


<commit_before>/**
 * Dialog for member selections
 */
var SelectionsModal = Modal.extend({
    type: "selections",

    buttons: [
        { text: "Save", method: "save" },
        { text: "Cancel", method: "close" }
    ],

    events: {
        'click a': 'call',
        'change #show_unique': 'show_unique'
    },

    initialize: function(args) {
        // Initialize properties
        _.extend(this, args);
        this.options.title = "Selections for " + this.name;
        this.message = "Fetching members...";
        this.show_unique = false;
        this.query = args.workspace.query;
        _.bindAll(this, "fetch_members", "populate", "finished");

        // Bind selection handlers
        _.extend(this.events, {
            'click div.selection_buttons a.form_button': 'move_selection'
        });

        // Determine axis
        this.axis = "undefined";
        if (args.target.parents('.fields_list_body').hasClass('rows')) {
            this.axis = "ROWS";
        }
        if (args.target.parents('.fields_list_body').hasClass('columns')) {
            this.axis = "COLUMNS";
        }
        if (args.target.parents('.fields_list_body').hasClass('filter')) {
            this.axis = "FILTER";
        }
        // Resize when rendered
        this.bind('open', this.post_render);
        this.render();

        // Fetch available members
        this.member = new Member({}, {
            cube: args.workspace.selected_cube,
            dimension: args.key
        });
        this.member.fetch({
            success: this.fetch_members
        });
    },

    fetch_members: function(model, response) {
        this.available_members = response;

        this.workspace.query.action.get("/axis/" + this.axis + "/", {
            success: this.populate
        });
    },

    populate: function(model, response) {
        try {
            // Load template
            $(this.el).find('.dialog_body')
                .html(_.template($("#template-selections").html())(this));

            this.selected_members = _.detect(response, function(obj) {
                return obj.name == this.member.dimension;
            }, this).selections;
            var used_members = [];

            // Populate both boxes
            $(this.el).find('.used_selections select').removeAttr('disabled');
            for (var j = 0; j < this.selected_members.length; j++) {
                var member = this.selected_members[j];
                if (member.levelUniqueName == this.member.level &&
                    member.type == "MEMBER") {
                    $("<option />").text(member.caption)
                        .val(member.uniqueName)
                        .appendTo($(this.el).find('.used_selections select'));
                    used_members.push(member.caption);
                }
            }

            // Filter out used members
            this.available_members = _.select(this.available_members, function(obj) {
                return used_members.indexOf(obj.caption) === -1;
            });

            $(this.el).find('.available_selections select').removeAttr('disabled');
            for (var i = 0; i < this.available_members.length; i++) {
                var member = this.available_members[i];
                $("<option />").text(member.caption)
                    .val(member.uniqueName)
                    .appendTo($(this.el).find('.available_selections select'));
            }
        } catch (e) {
            $(this.el).html("Could not load selections");
        }

        // Show dialog
        Saiku.ui.unblock();
    },

    post_render: function(args) {
        $(args.modal.el).parents('.ui-dialog').css({ width: "500px" });
    },

    move_selection: function(event) {
        var action = $(event.target).attr('id');
        var $to = action.indexOf('add') !== -1 ?
            $(this.el).find('.used_selections select') :
            $(this.el).find('.available_selections select');
        var $from = action.indexOf('add') !== -1 ?
            $(this.el).find('.available_selections select') :
            $(this.el).find('.used_selections select');
        var $els = action.indexOf('all') !== -1 ?
            $from.find('option') :$from.find('option:selected');
        $els.detach().appendTo($to);
    },

    show_unique: function() {
        $.each($(this.el).find('option'), function(i, option) {
            var text = $(option).text();
            $(option).text($(option).val());
            $(option).val(text);
        });
        this.show_unique = ! this.show_unique;
    },

    save: function() {
        // Notify user that updates are in progress
        var $loading = $("
Saving...
");
        $(this.el).find('.dialog_body').children().hide();
        $(this.el).find('.dialog_body').prepend($loading);

        // Determine updates
        var updates = [{
            hierarchy: this.member.hierarchy,
            uniquename: this.member.level,
            type: 'level',
            action: 'delete'
        }];

        // If no selections are used, add level
        if ($(this.el).find('.used_selections option').length === 0) {
            updates.push({
                hierarchy: this.member.hierarchy,
                uniquename: this.member.level,
                type: 'level',
                action: 'add'
            });
        } else {
            // Loop through selections
            $(this.el).find('.used_selections option')
                .each(function(i, selection) {
                var value = this.show_unique ?
                    $(selection).text() : $(selection).val();
                updates.push({
                    uniquename: value,
                    type: 'member',
                    action: 'add'
                });
            });
        }

        // Notify server
        this.query.action.put('/axis/' + this.axis + '/dimension/' + this.member.dimension, {
            success: this.finished,
            data: {
                selections: JSON.stringify(updates)
            }
        });

        return false;
    },

    finished: function() {
        $(this.el).dialog('destroy').remove();
        this.query.run();
    }
});<commit_msg>fix slowly loading selections modal for large levels<commit_after>/**
 * Dialog for member selections
 */
var SelectionsModal = Modal.extend({
    type: "selections",

    buttons: [
        { text: "Save", method: "save" },
        { text: "Cancel", method: "close" }
    ],

    events: {
        'click a': 'call',
        'change #show_unique': 'show_unique'
    },

    initialize: function(args) {
        // Initialize properties
        _.extend(this, args);
        this.options.title = "Selections for " + this.name;
        this.message = "Fetching members...";
        this.show_unique = false;
        this.query = args.workspace.query;
        _.bindAll(this, "fetch_members", "populate", "finished");

        // Bind selection handlers
        _.extend(this.events, {
            'click div.selection_buttons a.form_button': 'move_selection'
        });

        // Determine axis
        this.axis = "undefined";
        if (args.target.parents('.fields_list_body').hasClass('rows')) {
            this.axis = "ROWS";
        }
        if (args.target.parents('.fields_list_body').hasClass('columns')) {
            this.axis = "COLUMNS";
        }
        if (args.target.parents('.fields_list_body').hasClass('filter')) {
            this.axis = "FILTER";
        }
        // Resize when rendered
        this.bind('open', this.post_render);
        this.render();

        // Fetch available members
        this.member = new Member({}, {
            cube: args.workspace.selected_cube,
            dimension: args.key
        });
        this.member.fetch({
            success: this.fetch_members
        });
    },

    fetch_members: function(model, response) {
        this.available_members = response;

        this.workspace.query.action.get("/axis/" + this.axis + "/", {
            success: this.populate
        });
    },

    populate: function(model, response) {
        try {
            // Load template
            $(this.el).find('.dialog_body')
                .html(_.template($("#template-selections").html())(this));

            this.selected_members = _.detect(response, function(obj) {
                return obj.name == this.member.dimension;
            }, this).selections;
            var used_members = [];

            // Populate both boxes
            $(this.el).find('.used_selections select').removeAttr('disabled');
            var selected_members_opts = "";
            for (var j = 0; j < this.selected_members.length; j++) {
                var member = this.selected_members[j];
                if (member.levelUniqueName == this.member.level &&
                    member.type == "MEMBER") {
                    selected_members_opts += "<option value='" + member.uniqueName + "'>" + member.caption + "</option>";
                    used_members.push(member.caption);
                }
            }
            if (used_members.length > 0) {
                $(selected_members_opts).appendTo($(this.el).find('.used_selections select'));
            }

            // Filter out used members
            this.available_members = _.select(this.available_members, function(obj) {
                return used_members.indexOf(obj.caption) === -1;
            });

            $(this.el).find('.available_selections select').removeAttr('disabled');
            var available_members_opts = "";
            for (var i = 0; i < this.available_members.length; i++) {
                var member = this.available_members[i];
                available_members_opts += "<option value='" + member.uniqueName + "'>" + member.caption + "</option>";
            }
            if (this.available_members.length > 0) {
                $(available_members_opts).appendTo($(this.el).find('.available_selections select'));
            }
        } catch (e) {
            $(this.el).html("Could not load selections");
        }

        // Show dialog
        Saiku.ui.unblock();
    },

    post_render: function(args) {
        $(args.modal.el).parents('.ui-dialog').css({ width: "500px" });
    },

    move_selection: function(event) {
        var action = $(event.target).attr('id');
        var $to = action.indexOf('add') !== -1 ?
            $(this.el).find('.used_selections select') :
            $(this.el).find('.available_selections select');
        var $from = action.indexOf('add') !== -1 ?
            $(this.el).find('.available_selections select') :
            $(this.el).find('.used_selections select');
        var $els = action.indexOf('all') !== -1 ?
            $from.find('option') :$from.find('option:selected');
        $els.detach().appendTo($to);
    },

    show_unique: function() {
        $.each($(this.el).find('option'), function(i, option) {
            var text = $(option).text();
            $(option).text($(option).val());
            $(option).val(text);
        });
        this.show_unique = ! this.show_unique;
    },

    save: function() {
        // Notify user that updates are in progress
        var $loading = $("
Saving...
");
        $(this.el).find('.dialog_body').children().hide();
        $(this.el).find('.dialog_body').prepend($loading);

        // Determine updates
        var updates = [{
            hierarchy: this.member.hierarchy,
            uniquename: this.member.level,
            type: 'level',
            action: 'delete'
        }];

        // If no selections are used, add level
        if ($(this.el).find('.used_selections option').length === 0) {
            updates.push({
                hierarchy: this.member.hierarchy,
                uniquename: this.member.level,
                type: 'level',
                action: 'add'
            });
        } else {
            // Loop through selections
            $(this.el).find('.used_selections option')
                .each(function(i, selection) {
                var value = this.show_unique ?
                    $(selection).text() : $(selection).val();
                updates.push({
                    uniquename: value,
                    type: 'member',
                    action: 'add'
                });
            });
        }

        // Notify server
        this.query.action.put('/axis/' + this.axis + '/dimension/' + this.member.dimension, {
            success: this.finished,
            data: {
                selections: JSON.stringify(updates)
            }
        });

        return false;
    },

    finished: function() {
        $(this.el).dialog('destroy').remove();
        this.query.run();
    }
});<|endoftext|>


<commit_before>var ADSR = require('adsr')

context = new AudioContext()

// declare globals
var voices = [],
    mixAmp = context.createGain(),
    waveform = 'sine',
    ampEnvAttack = 0.1,
    ampEnvDecay = 0.0,
    ampEnvSustain = 1.0,
    ampEnvRelease = 0.5

// ---------- init instrument ----------------
$(document).ready(function() {
    initTuning()
    initControls()
})

function initTuning() {
    $('#voice-one-pitch').attr("value","440")
    $('#voice-two-pitch').attr("value","495")
    $('#voice-three-pitch').attr("value","528")
    $('#voice-four-pitch').attr("value","594")
    $('#voice-five-pitch').attr("value","660")
    $('#voice-six-pitch').attr("value","742.5")
    $('#voice-seven-pitch').attr("value","792")
    $('#voice-eight-pitch').attr("value","880")
}

function initControls() {
    mixAmp.gain.value = 0.1
}

// ---------- start and stop voices------------
function startVoice(n,freq) {
    // check for retrigger
    if (voices[n]) {
        voices[n].osc.stop()
        // delete voices[n]
    }

    // instantiate and start voice
    voices[n] = new Voice(mixAmp)
    voices[n].play(freq)
}

function stopVoice(n) {
    voices[n].stop()
    // delete voices[n]
}

// ---------- Voice class ----------------
function Voice(mixAmp) {
    this.osc = context.createOscillator()
    this.oscAmp = context.createGain()
    this.ampEnv = ADSR(context)

    this.oscAmp.gain.value = 0.0

    this.ampEnv.attack = ampEnvAttack
    this.ampEnv.decay = ampEnvDecay
    this.ampEnv.sustain = ampEnvSustain
    this.ampEnv.release = ampEnvRelease

    // routing
    this.osc.connect(this.oscAmp)
    this.oscAmp.connect(mixAmp)
    this.ampEnv.connect(this.oscAmp.gain)
    mixAmp.connect(context.destination)

    this.play = function(frequency) {
        // this.osc.type = 'triangle'
        this.osc.type = waveform
        this.osc.frequency.value = frequency
        console.log(frequency)

        /*
        this.oscAmp.gain.value = 0.0

        this.ampEnv.attack = ampEnvAttack
        this.ampEnv.decay = ampEnvDecay
        this.ampEnv.sustain = ampEnvSustain
        this.ampEnv.release = ampEnvRelease

        // routing
        this.osc.connect(this.oscAmp)
        this.oscAmp.connect(mixAmp)
        this.ampEnv.connect(this.oscAmp.gain)
        mixAmp.connect(context.destination)
        */

        this.ampEnv.start(context.currentTime)
        this.osc.start(context.CurrentTime)
        console.log(this.oscAmp.gain.value)
    }

    this.stop = function() {
        // console.log(this.ampEnv.release)
        var stopAt = this.ampEnv.stop(context.currentTime)
        this.osc.stop(stopAt)
    }
}

// ---------- Controls --------------
function setWaveform(wv) {
    waveform = wv
}

function setMixGain(gain) {
    mixAmp.gain.value = gain
}

function setAttack(attack) {
    ampEnvAttack = attack
}

function setDecay(decay) {
    ampEnvDecay = decay
}

function setSustain(sustain) {
    ampEnvSustain = sustain
}

function setRelease(release) {
    ampEnvRelease = release
}

// ---------- Exports --------------
exports.startVoice = startVoice
exports.stopVoice = stopVoice
exports.setWaveform = setWaveform
exports.setMixGain = setMixGain
exports.setAttack = setAttack
exports.setDecay = setDecay
exports.setSustain = setSustain
exports.setRelease = setRelease
<commit_msg>Add envelope-genertator module by itsjoesullivan<commit_after>var Envelope = require('envelope-generator')

context = new AudioContext()

// declare globals
var voices = [],
    mixAmp = context.createGain(),
    waveform = 'sine',
    ampEnvAttack = 0.1,
    ampEnvDecay = 0.1,
    ampEnvSustain = 1.0,
    ampEnvRelease = 0.5

// ---------- init instrument ----------------
$(document).ready(function() {
    initTuning()
    initControls()
})

function initTuning() {
    $('#voice-one-pitch').attr("value","440")
    $('#voice-two-pitch').attr("value","495")
    $('#voice-three-pitch').attr("value","528")
    $('#voice-four-pitch').attr("value","594")
    $('#voice-five-pitch').attr("value","660")
    $('#voice-six-pitch').attr("value","742.5")
    $('#voice-seven-pitch').attr("value","792")
    $('#voice-eight-pitch').attr("value","880")
}

function initControls() {
    mixAmp.gain.value = 0.1
}

// ---------- start and stop voices------------
function startVoice(n,freq) {
    // check for retrigger
    if (voices[n]) {
        voices[n].osc.stop()
    }

    console.log(ampEnvDecay)
    // instantiate and start voice
    voices[n] = new Voice(mixAmp)
    voices[n].play(freq)
}

function stopVoice(n) {
    voices[n].stop()
}

// ---------- Voice class ----------------
function Voice(mixAmp) {
    console.log(ampEnvDecay)
    this.osc = context.createOscillator()
    this.oscAmp = context.createGain()

    this.oscAmp.gain.value = 0.0

    // envelope settings
    let settings = {
        attackTime: ampEnvAttack,
        decayTime: ampEnvDecay,
        sustainLevel: ampEnvSustain,
        releaseTime: ampEnvRelease
    }
    this.ampEnv = new Envelope(context, settings)

    // routing
    this.osc.connect(this.oscAmp)
    this.oscAmp.connect(mixAmp)
    this.ampEnv.connect(this.oscAmp.gain)
    mixAmp.connect(context.destination)

    this.play = function(frequency) {
        this.osc.type = waveform
        this.osc.frequency.value = frequency
        console.log(frequency)

        /*
        this.oscAmp.gain.value = 0.0

        this.ampEnv.attack = ampEnvAttack
        this.ampEnv.decay = ampEnvDecay
        this.ampEnv.sustain = ampEnvSustain
        this.ampEnv.release = ampEnvRelease

        // routing
        this.osc.connect(this.oscAmp)
        this.oscAmp.connect(mixAmp)
        this.ampEnv.connect(this.oscAmp.gain)
        mixAmp.connect(context.destination)
        */

        this.ampEnv.start(context.currentTime)
        this.osc.start(context.CurrentTime)
        console.log(this.oscAmp.gain.value)
    }

    this.stop = function() {
        this.ampEnv.release(context.currentTime)
        var stopAt = this.ampEnv.getReleaseCompleteTime()
        this.osc.stop(stopAt)
        this.ampEnv.stop(stopAt)
    }
}

// ---------- Controls --------------
function setWaveform(wv) {
    waveform = wv
}

function setMixGain(gain) {
    mixAmp.gain.value = gain
}

function setAttack(attack) {
    ampEnvAttack = attack
}

function setDecay(decay) {
    ampEnvDecay = decay
}

function setSustain(sustain) {
    ampEnvSustain = sustain
}

function setRelease(release) {
    ampEnvRelease = release
}

// ---------- Exports --------------
exports.startVoice = startVoice
exports.stopVoice = stopVoice
exports.setWaveform = setWaveform
exports.setMixGain = setMixGain
exports.setAttack = setAttack
exports.setDecay = setDecay
exports.setSustain = setSustain
exports.setRelease = setRelease
<|endoftext|>


<commit_before>var through = require('through2');
var path = require('path');
var chokidar = require('chokidar');
var xtend = require('xtend');
var anymatch = require('anymatch');

module.exports = watchify;
module.exports.args = {
    cache: {}, packageCache: {}
};

function watchify (b, opts) {
    if (!opts) opts = {};
    var cache = b._options.cache;
    var pkgcache = b._options.packageCache;
    var delay = typeof opts.delay === 'number' ? opts.delay : 600;
    var changingDeps = {};
    var pending = false;
    var updating = false;

    var wopts = {persistent: true};
    if (opts.ignoreWatch) {
        var ignored = opts.ignoreWatch !== true
            ? opts.ignoreWatch
            : '**/node_modules/**';
    }
    if (opts.poll || typeof opts.poll === 'number') {
        wopts.usePolling = true;
        wopts.interval = opts.poll !== true
            ? opts.poll
            : undefined;
    }

    if (cache) {
        b.on('reset', collect);
        collect();
    }

    function collect () {
        b.pipeline.get('deps').push(through.obj(function(row, enc, next) {
            var file = row.expose ? b._expose[row.id] : row.file;
            cache[file] = {
                source: row.source,
                deps: xtend({}, row.deps)
            };
            this.push(row);
            next();
        }));
    }

    b.on('file', function (file) {
        watchFile(file);
    });

    b.on('package', function (pkg) {
        var file = path.join(pkg.__dirname, 'package.json');
        watchFile(file);
        if (pkgcache) pkgcache[file] = pkg;
    });

    b.on('reset', reset);
    reset();

    function reset () {
        var time = null;
        var bytes = 0;
        b.pipeline.get('record').on('end', function () {
            time = Date.now();
        });

        b.pipeline.get('wrap').push(through(write, end));
        function write (buf, enc, next) {
            bytes += buf.length;
            this.push(buf);
            next();
        }
        function end () {
            var delta = Date.now() - time;
            b.emit('time', delta);
            b.emit('bytes', bytes);
            b.emit('log', bytes + ' bytes written ('
                + (delta / 1000).toFixed(2) + ' seconds)'
            );
            this.push(null);
        }
    }

    var fwatchers = {};
    var fwatcherFiles = {};
    var ignoredFiles = {};

    b.on('transform', function (tr, mfile) {
        tr.on('file', function (dep) {
            watchFile(mfile, dep);
        });
    });
    b.on('bundle', function (bundle) {
        updating = true;
        bundle.on('error', onend);
        bundle.on('end', onend);
        function onend () { updating = false }
    });

    function watchFile (file, dep) {
        dep = dep || file;
        if (ignored) {
            if (!ignoredFiles.hasOwnProperty(file)) {
                ignoredFiles[file] = anymatch(ignored, file);
            }
            if (ignoredFiles[file]) return;
        }
        if (!fwatchers[file]) fwatchers[file] = [];
        if (!fwatcherFiles[file]) fwatcherFiles[file] = [];
        if (fwatcherFiles[file].indexOf(dep) >= 0) return;

        var w = b._watcher(dep, wopts);
        w.setMaxListeners(0);
        w.on('error', b.emit.bind(b, 'error'));
        w.on('change', function () {
            invalidate(file);
        });
        fwatchers[file].push(w);
        fwatcherFiles[file].push(dep);
    }

    function invalidate (id) {
        if (cache) delete cache[id];
        if (pkgcache) delete pkgcache[id];
        changingDeps[id] = true;
        if (updating) return;

        if (fwatchers[id]) {
            fwatchers[id].forEach(function (w) {
                w.close();
            });
            delete fwatchers[id];
            delete fwatcherFiles[id];
        }

        // wait for the disk/editor to quiet down first:
        if (!pending) setTimeout(function () {
            pending = false;
            if (!updating) {
                b.emit('update', Object.keys(changingDeps));
                changingDeps = {};
            }
        }, delay);
        pending = true;
    }

    b.close = function () {
        Object.keys(fwatchers).forEach(function (id) {
            fwatchers[id].forEach(function (w) { w.close() });
        });
    };

    b._watcher = function (file, opts) {
        return chokidar.watch(file, opts);
    };

    return b;
}
<commit_msg>make invalidate public<commit_after>var through = require('through2');
var path = require('path');
var chokidar = require('chokidar');
var xtend = require('xtend');
var anymatch = require('anymatch');

module.exports = watchify;
module.exports.args = {
    cache: {}, packageCache: {}
};

function watchify (b, opts) {
    if (!opts) opts = {};
    var cache = b._options.cache;
    var pkgcache = b._options.packageCache;
    var delay = typeof opts.delay === 'number' ? opts.delay : 600;
    var changingDeps = {};
    var pending = false;
    var updating = false;

    var wopts = {persistent: true};
    if (opts.ignoreWatch) {
        var ignored = opts.ignoreWatch !== true
            ? opts.ignoreWatch
            : '**/node_modules/**';
    }
    if (opts.poll || typeof opts.poll === 'number') {
        wopts.usePolling = true;
        wopts.interval = opts.poll !== true
            ? opts.poll
            : undefined;
    }

    if (cache) {
        b.on('reset', collect);
        collect();
    }

    function collect () {
        b.pipeline.get('deps').push(through.obj(function(row, enc, next) {
            var file = row.expose ? b._expose[row.id] : row.file;
            cache[file] = {
                source: row.source,
                deps: xtend({}, row.deps)
            };
            this.push(row);
            next();
        }));
    }

    b.on('file', function (file) {
        watchFile(file);
    });

    b.on('package', function (pkg) {
        var file = path.join(pkg.__dirname, 'package.json');
        watchFile(file);
        if (pkgcache) pkgcache[file] = pkg;
    });

    b.on('reset', reset);
    reset();

    function reset () {
        var time = null;
        var bytes = 0;
        b.pipeline.get('record').on('end', function () {
            time = Date.now();
        });

        b.pipeline.get('wrap').push(through(write, end));
        function write (buf, enc, next) {
            bytes += buf.length;
            this.push(buf);
            next();
        }
        function end () {
            var delta = Date.now() - time;
            b.emit('time', delta);
            b.emit('bytes', bytes);
            b.emit('log', bytes + ' bytes written ('
                + (delta / 1000).toFixed(2) + ' seconds)'
            );
            this.push(null);
        }
    }

    var fwatchers = {};
    var fwatcherFiles = {};
    var ignoredFiles = {};

    b.on('transform', function (tr, mfile) {
        tr.on('file', function (dep) {
            watchFile(mfile, dep);
        });
    });
    b.on('bundle', function (bundle) {
        updating = true;
        bundle.on('error', onend);
        bundle.on('end', onend);
        function onend () { updating = false }
    });

    function watchFile (file, dep) {
        dep = dep || file;
        if (ignored) {
            if (!ignoredFiles.hasOwnProperty(file)) {
                ignoredFiles[file] = anymatch(ignored, file);
            }
            if (ignoredFiles[file]) return;
        }
        if (!fwatchers[file]) fwatchers[file] = [];
        if (!fwatcherFiles[file]) fwatcherFiles[file] = [];
        if (fwatcherFiles[file].indexOf(dep) >= 0) return;

        var w = b._watcher(dep, wopts);
        w.setMaxListeners(0);
        w.on('error', b.emit.bind(b, 'error'));
        w.on('change', function () {
            invalidate(file);
        });
        fwatchers[file].push(w);
        fwatcherFiles[file].push(dep);
    }

    function invalidate (id) {
        if (cache) delete cache[id];
        if (pkgcache) delete pkgcache[id];
        changingDeps[id] = true;
        if (updating) return;

        if (fwatchers[id]) {
            fwatchers[id].forEach(function (w) {
                w.close();
            });
            delete fwatchers[id];
            delete fwatcherFiles[id];
        }

        // wait for the disk/editor to quiet down first:
        if (!pending) setTimeout(function () {
            pending = false;
            if (!updating) {
                b.emit('update', Object.keys(changingDeps));
                changingDeps = {};
            }
        }, delay);
        pending = true;
    }

    b.close = function () {
        Object.keys(fwatchers).forEach(function (id) {
            fwatchers[id].forEach(function (w) { w.close() });
        });
    };

    b._watcher = function (file, opts) {
        return chokidar.watch(file, opts);
    };

    b.invalidate = invalidate;

    return b;
}
<|endoftext|>


<commit_before>import React from "react";
import Search from "./search";
import {Authenticated} from "react-stormpath";
import {Tooltip, OverlayTrigger} from "react-bootstrap";
const calculateDistance = require("../calculate-distance");

export default class Front extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            businessList: null,
            coordLat: "",
            coordLong: "",
            attending: false,
            sorted: 0
        }
    }
    static contextTypes = {
        authenticated: React.PropTypes.bool,
        user: React.PropTypes.object
    };
    componentDidMount = () => {
        //if (Modernizr.geolocation) {
            navigator.geolocation.getCurrentPosition((position) => {
                this.setState({
                    coordLat: position.coords.latitude,
                    coordLong: position.coords.longitude
                });
            });
        //}
    }
    changeState = (json) => {
        this.setState({
            businessList: json.data
        });
    };
    handleSort = (e) => {
        if (!Array.isArray(e)) {
            if (e.target.textContent === "Sort by distance") {
                this.setState({
                    businessList: this.state.businessList.sort((a, b) => {
                        return a.distance - b.distance;
                    })
                });
                this.setState({
                    sorted: 1
                })
            }
            else if (e.target.textContent === "Sort by rating") {
                this.setState({
                    businessList: this.state.businessList.sort((a, b) => {
                        return b.rating - a.rating;
                    })
                });
                this.setState({
                    sorted: 2
                })
            }
            else {
                this.setState({
                    businessList: this.state.businessList.sort((a, b) => {
                        return b.review_count - a.review_count;
                    })
                });
            }
        }
        else {
            if (this.state.sorted === 1) {
                e.sort((a, b) => {
                    return a.distance - b.distance;
                });
                return e;
            }
            else if (this.state.sorted === 2) {
                e.sort((a, b) => {
                    return b.rating - a.rating;
                });
                return e;
            }
            else {
                e.sort((a, b) => {
                    return b.review_count - a.review_count;
                });
                return e;
            }
        }
    };
    handleAttend = (event, name, id, location, query) => {
        let text = event.target.textContent;
        fetch("/add-bar", {
            method: "post",
            body: JSON.stringify({data: {name: name, username: this.context.user.givenName, id: id, location: location, query: query, text: text}}),
            headers: {
                "Content-Type": "application/json"
            }
        }).then((res) => {
            return res.json();
        }).then((json) => {
            if (json.data === true) {
                alert("You are already added!");
            }
            else if (!json.data) {
                alert("You have not added yourself to this bar!");
            }
            else {
                this.setState({
                    businessList: this.handleSort(json.data)
                });
            }
        })
    };
    render() {
        let heading = (

Night Owls

Explore the Night

        )
        let list = this.state.businessList ? (

<button className="sort-button" onClick={this.handleSort}>Sort by distance</button>

<button className="sort-button" onClick={this.handleSort}>Sort by rating</button>

<button className="sort-button" onClick={this.handleSort}>Sort by review count</button>

What's in {this.state.businessList[0].loc}?

              {this.state.businessList.map((object, id) => {
                  /*let tooltip = object.users.length ? (<Tooltip id={id}>{object.users.map((user) => {

{user}

                  })}</Tooltip>) : "No users";*/
                  let distance = this.state.coordLat ? calculateDistance(object.location.coordinate.latitude, object.location.coordinate.longitude, this.state.coordLat, this.state.coordLong) : null;
                  if (distance) {
                      object.distance = distance;
                  }
                  return (

{object.review_count} reviews

                          <Authenticated>
                            <button className="add-remove" onClick={(event) => this.handleAttend(event, object.name, object.id, object.loc, object.query)}>I'm going here tonight</button>
                          </Authenticated>
                          <Authenticated>
                            <button className="add-remove" onClick={(event) => this.handleAttend(event, object.name, object.id, object.loc, object.query)}>Remove Me</button>
                          </Authenticated>

{object.name}

{distance}{distance ? " km away" : ""}

{object.attendance + " going"}

Customers say: "{object.snippet_text}"

{object.location.display_address[0]}

{object.location.display_address[1]}

{object.location.display_address[2]}

                  )
              })}

        ) : null;
        return (

                {heading}
                <Search changeState={this.changeState} businessList={this.state.businessList}/>

              {list}

        )
    }
}<commit_msg>add/remove buttons no longer hidden from unauthenticated users<commit_after>import React from "react";
import Search from "./search";
import {Authenticated} from "react-stormpath";
import {Tooltip, OverlayTrigger} from "react-bootstrap";
const calculateDistance = require("../calculate-distance");

export default class Front extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            businessList: null,
            coordLat: "",
            coordLong: "",
            attending: false,
            sorted: 0,
        }
    }
    static contextTypes = {
        authenticated: React.PropTypes.bool,
        user: React.PropTypes.object
    };
    componentDidMount = () => {
        //if (Modernizr.geolocation) {
            navigator.geolocation.getCurrentPosition((position) => {
                this.setState({
                    coordLat: position.coords.latitude,
                    coordLong: position.coords.longitude
                });
            });
        //}
    }
    changeState = (json) => {
        this.setState({
            businessList: json.data
        });
    };
    handleSort = (e) => {
        if (!Array.isArray(e)) {
            if (e.target.textContent === "Sort by distance") {
                this.setState({
                    businessList: this.state.businessList.sort((a, b) => {
                        return a.distance - b.distance;
                    })
                });
                this.setState({
                    sorted: 1
                })
            }
            else if (e.target.textContent === "Sort by rating") {
                this.setState({
                    businessList: this.state.businessList.sort((a, b) => {
                        return b.rating - a.rating;
                    })
                });
                this.setState({
                    sorted: 2
                })
            }
            else {
                this.setState({
                    businessList: this.state.businessList.sort((a, b) => {
                        return b.review_count - a.review_count;
                    })
                });
            }
        }
        else {
            if (this.state.sorted === 1) {
                e.sort((a, b) => {
                    return a.distance - b.distance;
                });
                return e;
            }
            else if (this.state.sorted === 2) {
                e.sort((a, b) => {
                    return b.rating - a.rating;
                });
                return e;
            }
            else {
                e.sort((a, b) => {
                    return b.review_count - a.review_count;
                });
                return e;
            }
        }
    };
    handleAttend = (event, name, id, location, query) => {
        if (!this.context.authenticated) {
            alert("You must be signed in to access these features");
            return;
        }
        let text = event.target.textContent;
        fetch("/add-bar", {
            method: "post",
            body: JSON.stringify({data: {name: name, username: this.context.user.givenName, id: id, location: location, query: query, text: text}}),
            headers: {
                "Content-Type": "application/json"
            }
        }).then((res) => {
            return res.json();
        }).then((json) => {
            if (json.data === true) {
                alert("You are already added!");
            }
            else if (!json.data) {
                alert("You have not added yourself to this bar!");
            }
            else {
                this.setState({
                    businessList: this.handleSort(json.data)
                });
            }
        })
    };
    render() {
        let heading = (

Night Owls

Explore the Night

        )
        let list = this.state.businessList ? (

              <button className="sort-button" onClick={this.handleSort}>Sort by distance</button>
              <button className="sort-button" onClick={this.handleSort}>Sort by rating</button>
              <button className="sort-button" onClick={this.handleSort}>Sort by review count</button>

What's in {this.state.businessList[0].loc}?

              {this.state.businessList.map((object, id) => {
                  /*let tooltip = object.users.length ? (<Tooltip id={id}>{object.users.map((user) => {

{user}

                  })}</Tooltip>) : "No users";*/
                  let distance = this.state.coordLat ? calculateDistance(object.location.coordinate.latitude, object.location.coordinate.longitude, this.state.coordLat, this.state.coordLong) : null;
                  if (distance) {
                      object.distance = distance;
                  }
                  return (

{object.review_count} reviews

                          <button className="add-remove" onClick={(event) => this.handleAttend(event, object.name, object.id, object.loc, object.query)}>I'm going here tonight</button>
                          <button className="add-remove" onClick={(event) => this.handleAttend(event, object.name, object.id, object.loc, object.query)}>Remove Me</button>

{object.name}

{distance}{distance ? " km away" : ""}

{object.attendance + " going"}

Customers say: "{object.snippet_text}"

{object.location.display_address[0]}

{object.location.display_address[1]}

{object.location.display_address[2]}

                  )
              })}

        ) : null;
        return (

                {heading}
                <Search changeState={this.changeState} businessList={this.state.businessList}/>

              {list}

        )
    }
}<|endoftext|>


<commit_before>var TaskList = {
  in_sort: false,

  sortableChange: function(draggable) {
    this.currentDraggable = draggable
  },

  sortableUpdate: function() {
    task_list_ids = this.currentDraggable.up().select('.task_list').collect(
    function(task_list) {
        return task_list.readAttribute('data-task-list-id')
    }).join(',')

    new Ajax.Request('/projects/' + current_project + '/task_lists/reorder', {
      method: 'put',
      parameters: { task_list_ids: task_list_ids }
    })
  }.debounce(100),

  makeSortable: function() {
    TaskList.in_sort = true;
    Sortable.create('task_lists', {
      constraint:'vertical',
      handle:'img.drag',
      tag:'div',
      only:'task_list_container',
      onChange: TaskList.sortableChange.bind(TaskList),
      onUpdate: TaskList.sortableUpdate.bind(TaskList)
    });
  },
  destroySortable: function() {
    TaskList.in_sort = false;
    Sortable.destroy('task_lists');
  },

  // Inserts new list
  insertList: function(id, content, archived) {
    if (archived)
      $('task_lists').insert({bottom: content});
    else
      $('task_lists').insert({top: content});
    new Effect.Highlight(id, {duration:3});
  },

  // Replaces new list (or inserts if applicable)
  replaceList: function(id, content, archived) {
    var existing = $(id);
    if (!id)
      TaskList.insertList(id,content,archived);
    else
      existing.replace(content);
  },

  // Removes an existing list
  removeList: function(id) {
    var el = $(id);
    if (el)
    {
      el.remove();
    }
  },

  // Updates task list state
  updateList: function(id) {
    // ...
    var list = $(id);
    if (!list)
      return;

    var open = $(id + '_the_main_tasks');
    var archived = $(id + '_the_closed_tasks');
    var open_count = open.childElements().length;
    var archived_count = archived.childElements().length;
    var link = $(id + '_archive_link');
    if (open_count == 0 && archived_count > 0)
      link.show();
    else
      link.hide();
  },

  setArchived: function(id, value) {
    var el = $(id);
    if (el) {
      if (value)
        el.addClassName('archived');
      else
        el.removeClassName('archived');
    }
  },

  // Destruction handler
  destroy: function(element, url) {
    new Ajax.Request(url, {
      method: 'delete',
      asynchronous: true,
      evalScripts: true,
      onLoading: function() {
        Actions.setLoading(element, true);
      },
      onSuccess: function(response){
        // ...
        setTimeout(function(){TaskList.updatePrimer();}, 0);
      },
      onFailure: function(response){
        Actions.setLoading(element, false);
      }
    });
  },

  updateForm: function(element, url) {
    new Ajax.Request(url, {
      method: 'get',
      asynchronous: true,
      evalScripts: true,
      onLoading: function() {
        Actions.setLoading(element, true);
      },
      onSuccess: function(response){
        Actions.setActions(element, false);
        Actions.setLoading(element, false);
      },
      onFailure: function(response){
        Actions.setLoading(element, false);
      }
    });
  },

  resolveAndArchive: function(element, url) {
    new Ajax.Request(url, {
      method: 'put',
      asynchronous: true,
      evalScripts: true,
      onLoading: function() {
        Actions.setLoading(element, true);
      },
      onSuccess: function(response){
        TaskList.saveColumn();
        TaskList.updatePage('column', TaskList.restoreColumn);
      },
      onFailure: function(response){
        Actions.setLoading(element, false);
      }
    });
  },

  unarchive: function(element, url) {
    new Ajax.Request(url, {
      method: 'put',
      asynchronous: true,
      evalScripts: true,
      parameters:'task_list[archived]=false',
      onLoading: function() {
        //Actions.setLoading(element, true);
      },
      onSuccess: function(response){
        TaskList.saveColumn();
        TaskList.updatePage('column', TaskList.restoreColumn);
      },
      onFailure: function(response){
        //Actions.setLoading(element, false);
      }
    });
  },

  saveColumn: function() {
    var saved = {};
    if ($('filter_assigned'))
    {
      saved.filter_assigned = $('filter_assigned').selectedIndex;
      saved.filter_due_date = $('filter_due_date').selectedIndex;
    }
    TaskList.saved = saved;
  },

  restoreColumn: function() {
    var saved = TaskList.saved;
    if (saved) {
      if (saved.filter_assigned)
      {
        $('filter_assigned').selectedIndex = saved.filter_assigned;
        $('filter_due_date').selectedIndex = saved.filter_due_date;
      }
    }
    Filter.updateCounts();
  },

  updatePage: function(part, callback) {
    var el = $(document.body);
    var url = el.readAttribute('reload_url');
    if (!url)
      return;
    url = url.indexOf('?') >= 0 ? (url + '&part=' + part) : (url + '?part=' + part);
    new Ajax.Request(url, {
      asynchronous: true,
      evalScripts: true,
      method: 'get',
      onComplete: callback
    });
  },

  reloadList: function(el, part, callback) {
    var url = el.readAttribute('action_url');
    var on_index = el.hasClassName('index_task_lists');
    if (!url)
      return;
    url = url.indexOf('?') >= 0 ? (url + '&part=' + part) : (url + '?part=' + part);
    if (on_index)
      url = url + '&on_index=1';
    new Ajax.Request(url, {
      asynchronous: true,
      evalScripts: true,
      method: 'get',
      onComplete: function(request) {
        callback(request);
        Task.make_all_sortable();
      }
    });
  },

  setTitle: function(element, visible) {
    var title = $(element.readAttribute('jennybase') + 'title');
    if (title == null)
      return;
    if (visible)
      title.show();
    else
      title.hide();
  },

  setReorder: function(active) {
    $$('.task_list_container').invoke(active ? 'addClassName' : 'removeClassName', 'reordering')

    if (active) {
      $('reorder_task_lists_link').swapVisibility('done_reordering_task_lists_link')
      Filter.showAllTaskLists()
      $$('.filters').invoke('hide')
      TaskList.makeSortable()
    } else {
      $('done_reordering_task_lists_link').swapVisibility('reorder_task_lists_link')
      Filter.updateFilters()
      $$('.filters').invoke('show')
      TaskList.destroySortable()
    }
  },

  updatePrimer: function() {
    var primer = $('primer');
    if (primer && $$('.task_list').length == 0)
      primer.show();
    else if (primer)
      primer.hide();
  },
  populateTaskListSelect: function(project_id, select, callback) {

    new Ajax.Request('/api/1/projects/' + project_id + '/task_lists.json', {
      method:'get',
      requestHeaders: {Accept: 'application/json'},
      onSuccess: function(transport){
        var json = transport.responseText.evalJSON(true);
        select.options.length = 0;
        json.objects.each(function(taskList) {
          select.options.add(new Option(taskList.name, taskList.id));
        });
        select.options.add(new Option("Inbox", ''));
      },
      onFailure: function() {
        alert('Error loading page! Please reload.');
        if (callback) {
          callback();
        }
      }
    });
  }
};

document.on('click', '#reorder_task_lists_link', function(e, element){
  e.stop()
  TaskList.setReorder(true);
});

document.on('click', '#done_reordering_task_lists_link', function(e, element){
  e.stop()
  TaskList.setReorder(false);
});

document.observe('jenny:loaded:edit_task_list', function(evt) {
  // Reload sort
  if (TaskList.in_sort) {
    setTimeout(function(){
      TaskList.setReorder(false);
      TaskList.setReorder(true);
    }, 0);
  }
  TaskList.saveColumn();
  TaskList.updatePage('column', TaskList.restoreColumn);
});

document.observe('jenny:loaded:new_task_list', function(evt) {
  // Reload sort
  if (TaskList.in_sort) {
	setTimeout(function(){
      TaskList.setReorder(false);
      TaskList.setReorder(true);
    }, 0);
  }
  setTimeout(function(){
    Task.make_all_sortable();
    TaskList.updatePrimer();
    TaskList.saveColumn();
  }, 0);
});

document.observe('jenny:cancel:edit_task_list', function(evt) {
  // Only do this on the index
  if (evt.memo.form.up('.task_list_container'))
  {
    TaskList.setTitle(evt.memo.form, true);
    Actions.setActions(evt.memo.form, true);
  }
  else
  {
    evt.memo.form.up().down(".task_header").show();
  }
});

// update action
document.on('click', 'a.taskListUpdate', function(e, el) {
  e.stop();
  TaskList.updateForm(el, el.readAttribute('href'));
});

// delete action
document.on('click', 'a.taskListDelete', function(e, el) {
  e.stop();
  if (confirm(el.readAttribute('aconfirm')))
    TaskList.destroy(el, el.readAttribute('action_url'));
});

// resolve action
document.on('click', 'a.taskListResolve', function(e, el) {
  e.stop();
  if (confirm(el.readAttribute('aconfirm')))
    TaskList.resolveAndArchive(el, el.readAttribute('action_url'));
});

document.on('click', 'a.create_first_task_list_link', function(e, el) {
  e.stop();
  Jenny.toggleElement(el); // edit form on task list show
});

document.on('click', 'a.edit_task_list_link', function(e, el) {
  e.stop();
  Jenny.toggleElement(el); // edit form on task list show
});

document.on('click', 'a.unarchive_task_list_link', function(e, el) {
  e.stop();
  TaskList.unarchive(el, el.readAttribute('action_url'));
});

// creating new tasks
document.on('click', '.task_list .new_task a[href$="/new"]', function(e, link) {
  if (!e.isMiddleClick()) {
    e.stop()
    link.hide().next('form').forceShow().focusFirstElement()
  }
})

var hideTaskFormAndShowLink = function(form) {
  Form.reset(form).hide().previous('a[href$="/new"]').show()
}

document.on('click', '.task_list .new_task form a[href="#cancel"]', function(e, link) {
  e.stop()
  hideTaskFormAndShowLink(link.up('form'))
})

document.on('keyup', '.task_list .new_task form', function(e, form) {
  if (e.keyCode == Event.KEY_ESC) hideTaskFormAndShowLink(form)
})

document.on('ajax:success', '.task_list .new_task form', function(e, form) {
  resetCommentsForm(form)
  Form.reset(form).focusFirstElement().up('.task_list').down('.tasks').insert(e.memo.responseText.unescapeHTML())
})
<commit_msg>Fix commenting without upload, detect type of request<commit_after>var TaskList = {
  in_sort: false,

  sortableChange: function(draggable) {
    this.currentDraggable = draggable
  },

  sortableUpdate: function() {
    task_list_ids = this.currentDraggable.up().select('.task_list').collect(
    function(task_list) {
        return task_list.readAttribute('data-task-list-id')
    }).join(',')

    new Ajax.Request('/projects/' + current_project + '/task_lists/reorder', {
      method: 'put',
      parameters: { task_list_ids: task_list_ids }
    })
  }.debounce(100),

  makeSortable: function() {
    TaskList.in_sort = true;
    Sortable.create('task_lists', {
      constraint:'vertical',
      handle:'img.drag',
      tag:'div',
      only:'task_list_container',
      onChange: TaskList.sortableChange.bind(TaskList),
      onUpdate: TaskList.sortableUpdate.bind(TaskList)
    });
  },
  destroySortable: function() {
    TaskList.in_sort = false;
    Sortable.destroy('task_lists');
  },

  // Inserts new list
  insertList: function(id, content, archived) {
    if (archived)
      $('task_lists').insert({bottom: content});
    else
      $('task_lists').insert({top: content});
    new Effect.Highlight(id, {duration:3});
  },

  // Replaces new list (or inserts if applicable)
  replaceList: function(id, content, archived) {
    var existing = $(id);
    if (!id)
      TaskList.insertList(id,content,archived);
    else
      existing.replace(content);
  },

  // Removes an existing list
  removeList: function(id) {
    var el = $(id);
    if (el)
    {
      el.remove();
    }
  },

  // Updates task list state
  updateList: function(id) {
    // ...
    var list = $(id);
    if (!list)
      return;

    var open = $(id + '_the_main_tasks');
    var archived = $(id + '_the_closed_tasks');
    var open_count = open.childElements().length;
    var archived_count = archived.childElements().length;
    var link = $(id + '_archive_link');
    if (open_count == 0 && archived_count > 0)
      link.show();
    else
      link.hide();
  },

  setArchived: function(id, value) {
    var el = $(id);
    if (el) {
      if (value)
        el.addClassName('archived');
      else
        el.removeClassName('archived');
    }
  },

  // Destruction handler
  destroy: function(element, url) {
    new Ajax.Request(url, {
      method: 'delete',
      asynchronous: true,
      evalScripts: true,
      onLoading: function() {
        Actions.setLoading(element, true);
      },
      onSuccess: function(response){
        // ...
        setTimeout(function(){TaskList.updatePrimer();}, 0);
      },
      onFailure: function(response){
        Actions.setLoading(element, false);
      }
    });
  },

  updateForm: function(element, url) {
    new Ajax.Request(url, {
      method: 'get',
      asynchronous: true,
      evalScripts: true,
      onLoading: function() {
        Actions.setLoading(element, true);
      },
      onSuccess: function(response){
        Actions.setActions(element, false);
        Actions.setLoading(element, false);
      },
      onFailure: function(response){
        Actions.setLoading(element, false);
      }
    });
  },

  resolveAndArchive: function(element, url) {
    new Ajax.Request(url, {
      method: 'put',
      asynchronous: true,
      evalScripts: true,
      onLoading: function() {
        Actions.setLoading(element, true);
      },
      onSuccess: function(response){
        TaskList.saveColumn();
        TaskList.updatePage('column', TaskList.restoreColumn);
      },
      onFailure: function(response){
        Actions.setLoading(element, false);
      }
    });
  },

  unarchive: function(element, url) {
    new Ajax.Request(url, {
      method: 'put',
      asynchronous: true,
      evalScripts: true,
      parameters:'task_list[archived]=false',
      onLoading: function() {
        //Actions.setLoading(element, true);
      },
      onSuccess: function(response){
        TaskList.saveColumn();
        TaskList.updatePage('column', TaskList.restoreColumn);
      },
      onFailure: function(response){
        //Actions.setLoading(element, false);
      }
    });
  },

  saveColumn: function() {
    var saved = {};
    if ($('filter_assigned'))
    {
      saved.filter_assigned = $('filter_assigned').selectedIndex;
      saved.filter_due_date = $('filter_due_date').selectedIndex;
    }
    TaskList.saved = saved;
  },

  restoreColumn: function() {
    var saved = TaskList.saved;
    if (saved) {
      if (saved.filter_assigned)
      {
        $('filter_assigned').selectedIndex = saved.filter_assigned;
        $('filter_due_date').selectedIndex = saved.filter_due_date;
      }
    }
    Filter.updateCounts();
  },

  updatePage: function(part, callback) {
    var el = $(document.body);
    var url = el.readAttribute('reload_url');
    if (!url)
      return;
    url = url.indexOf('?') >= 0 ? (url + '&part=' + part) : (url + '?part=' + part);
    new Ajax.Request(url, {
      asynchronous: true,
      evalScripts: true,
      method: 'get',
      onComplete: callback
    });
  },

  reloadList: function(el, part, callback) {
    var url = el.readAttribute('action_url');
    var on_index = el.hasClassName('index_task_lists');
    if (!url)
      return;
    url = url.indexOf('?') >= 0 ? (url + '&part=' + part) : (url + '?part=' + part);
    if (on_index)
      url = url + '&on_index=1';
    new Ajax.Request(url, {
      asynchronous: true,
      evalScripts: true,
      method: 'get',
      onComplete: function(request) {
        callback(request);
        Task.make_all_sortable();
      }
    });
  },

  setTitle: function(element, visible) {
    var title = $(element.readAttribute('jennybase') + 'title');
    if (title == null)
      return;
    if (visible)
      title.show();
    else
      title.hide();
  },

  setReorder: function(active) {
    $$('.task_list_container').invoke(active ? 'addClassName' : 'removeClassName', 'reordering')

    if (active) {
      $('reorder_task_lists_link').swapVisibility('done_reordering_task_lists_link')
      Filter.showAllTaskLists()
      $$('.filters').invoke('hide')
      TaskList.makeSortable()
    } else {
      $('done_reordering_task_lists_link').swapVisibility('reorder_task_lists_link')
      Filter.updateFilters()
      $$('.filters').invoke('show')
      TaskList.destroySortable()
    }
  },

  updatePrimer: function() {
    var primer = $('primer');
    if (primer && $$('.task_list').length == 0)
      primer.show();
    else if (primer)
      primer.hide();
  },
  populateTaskListSelect: function(project_id, select, callback) {

    new Ajax.Request('/api/1/projects/' + project_id + '/task_lists.json', {
      method:'get',
      requestHeaders: {Accept: 'application/json'},
      onSuccess: function(transport){
        var json = transport.responseText.evalJSON(true);
        select.options.length = 0;
        json.objects.each(function(taskList) {
          select.options.add(new Option(taskList.name, taskList.id));
        });
        select.options.add(new Option("Inbox", ''));
      },
      onFailure: function() {
        alert('Error loading page! Please reload.');
        if (callback) {
          callback();
        }
      }
    });
  }
};

document.on('click', '#reorder_task_lists_link', function(e, element){
  e.stop()
  TaskList.setReorder(true);
});

document.on('click', '#done_reordering_task_lists_link', function(e, element){
  e.stop()
  TaskList.setReorder(false);
});

document.observe('jenny:loaded:edit_task_list', function(evt) {
  // Reload sort
  if (TaskList.in_sort) {
    setTimeout(function(){
      TaskList.setReorder(false);
      TaskList.setReorder(true);
    }, 0);
  }
  TaskList.saveColumn();
  TaskList.updatePage('column', TaskList.restoreColumn);
});

document.observe('jenny:loaded:new_task_list', function(evt) {
  // Reload sort
  if (TaskList.in_sort) {
	setTimeout(function(){
      TaskList.setReorder(false);
      TaskList.setReorder(true);
    }, 0);
  }
  setTimeout(function(){
    Task.make_all_sortable();
    TaskList.updatePrimer();
    TaskList.saveColumn();
  }, 0);
});

document.observe('jenny:cancel:edit_task_list', function(evt) {
  // Only do this on the index
  if (evt.memo.form.up('.task_list_container'))
  {
    TaskList.setTitle(evt.memo.form, true);
    Actions.setActions(evt.memo.form, true);
  }
  else
  {
    evt.memo.form.up().down(".task_header").show();
  }
});

// update action
document.on('click', 'a.taskListUpdate', function(e, el) {
  e.stop();
  TaskList.updateForm(el, el.readAttribute('href'));
});

// delete action
document.on('click', 'a.taskListDelete', function(e, el) {
  e.stop();
  if (confirm(el.readAttribute('aconfirm')))
    TaskList.destroy(el, el.readAttribute('action_url'));
});

// resolve action
document.on('click', 'a.taskListResolve', function(e, el) {
  e.stop();
  if (confirm(el.readAttribute('aconfirm')))
    TaskList.resolveAndArchive(el, el.readAttribute('action_url'));
});

document.on('click', 'a.create_first_task_list_link', function(e, el) {
  e.stop();
  Jenny.toggleElement(el); // edit form on task list show
});

document.on('click', 'a.edit_task_list_link', function(e, el) {
  e.stop();
  Jenny.toggleElement(el); // edit form on task list show
});

document.on('click', 'a.unarchive_task_list_link', function(e, el) {
  e.stop();
  TaskList.unarchive(el, el.readAttribute('action_url'));
});

// creating new tasks
document.on('click', '.task_list .new_task a[href$="/new"]', function(e, link) {
  if (!e.isMiddleClick()) {
    e.stop()
    link.hide().next('form').forceShow().focusFirstElement()
  }
})

var hideTaskFormAndShowLink = function(form) {
  Form.reset(form).hide().previous('a[href$="/new"]').show()
}

document.on('click', '.task_list .new_task form a[href="#cancel"]', function(e, link) {
  e.stop()
  hideTaskFormAndShowLink(link.up('form'))
})

document.on('keyup', '.task_list .new_task form', function(e, form) {
  if (e.keyCode == Event.KEY_ESC) hideTaskFormAndShowLink(form)
})

document.on('ajax:success', '.task_list .new_task form', function(e, form) {
  if (e.memo.transport) {
    var response = e.memo.responseText
  } else {
    var response = e.memo.responseText.unescapeHTML()
    resetCommentsForm(form)
  }
  Form.reset(form).focusFirstElement().up('.task_list').down('.tasks').insert(response)
})
<|endoftext|>


<commit_before>'use strict';

var kdbush = require('kdbush');

module.exports = supercluster;

function supercluster(options) {
    return new SuperCluster(options);
}

function SuperCluster(options) {
    this.options = extend(Object.create(this.options), options);
    this.trees = new Array(this.options.maxZoom + 1);
}

SuperCluster.prototype = {
    options: {
        minZoom: 0,   // min zoom to generate clusters on
        maxZoom: 16,  // max zoom level to cluster the points on
        radius: 40,   // cluster radius in pixels
        extent: 512,  // tile extent (radius is calculated relative to it)
        nodeSize: 64, // size of the KD-tree leaf node, affects performance
        log: false,   // whether to log timing info

        // a reduce function for calculating custom cluster properties
        reduce: null, // function (accumulated, props) { accumulated.sum += props.sum; }

        // initial properties of a cluster (before running the reducer)
        initial: function () { return {}; }, // function () { return {sum: 0}; },

        // properties to use for individual points when running the reducer
        map: function (props) { return props; } // function (props) { return {sum: props.my_value}; },
    },

    load: function (points) {
        var log = this.options.log;

        if (log) console.time('total time');

        var timerId = 'prepare ' + points.length + ' points';
        if (log) console.time(timerId);

        this.points = points;

        // generate a cluster object for each point
        var clusters = points.map(createPointCluster);
        if (log) console.timeEnd(timerId);

        // cluster points on max zoom, then cluster the results on previous zoom, etc.;
        // results in a cluster hierarchy across zoom levels
        for (var z = this.options.maxZoom; z >= this.options.minZoom; z--) {
            var now = +Date.now();

            // index input points into a KD-tree
            this.trees[z + 1] = kdbush(clusters, getX, getY, this.options.nodeSize, Float32Array);

            clusters = this._cluster(clusters, z); // create a new set of clusters for the zoom

            if (log) console.log('z%d: %d clusters in %dms', z, clusters.length, +Date.now() - now);
        }

        // index top-level clusters
        this.trees[this.options.minZoom] = kdbush(clusters, getX, getY, this.options.nodeSize, Float32Array);

        if (log) console.timeEnd('total time');

        return this;
    },

    getClusters: function (bbox, zoom) {
        var tree = this.trees[this._limitZoom(zoom)];
        var ids = tree.range(lngX(bbox[0]), latY(bbox[3]), lngX(bbox[2]), latY(bbox[1]));
        var clusters = [];
        for (var i = 0; i < ids.length; i++) {
            var c = tree.points[ids[i]];
            clusters.push(c.numPoints ? getClusterJSON(c) : this.points[c.id]);
        }
        return clusters;
    },

    getChildren: function (clusterId, clusterZoom) {
        var origin = this.trees[clusterZoom + 1].points[clusterId];
        var r = this.options.radius / (this.options.extent * Math.pow(2, clusterZoom));
        var points = this.trees[clusterZoom + 1].within(origin.x, origin.y, r);
        var children = [];
        for (var i = 0; i < points.length; i++) {
            var c = this.trees[clusterZoom + 1].points[points[i]];
            if (c.parentId === clusterId) {
                children.push(c.numPoints ? getClusterJSON(c) : this.points[c.id]);
            }
        }
        return children;
    },

    getLeaves: function (clusterId, clusterZoom, limit, offset) {
        limit = limit || 10;
        offset = offset || 0;

        var leaves = [];
        this._appendLeaves(leaves, clusterId, clusterZoom, limit, offset, 0);

        return leaves;
    },

    getTile: function (z, x, y) {
        var tree = this.trees[this._limitZoom(z)];
        var z2 = Math.pow(2, z);
        var extent = this.options.extent;
        var r = this.options.radius;
        var p = r / extent;
        var top = (y - p) / z2;
        var bottom = (y + 1 + p) / z2;

        var tile = {
            features: []
        };

        this._addTileFeatures(
            tree.range((x - p) / z2, top, (x + 1 + p) / z2, bottom),
            tree.points, x, y, z2, tile);

        if (x === 0) {
            this._addTileFeatures(
                tree.range(1 - p / z2, top, 1, bottom),
                tree.points, z2, y, z2, tile);
        }
        if (x === z2 - 1) {
            this._addTileFeatures(
                tree.range(0, top, p / z2, bottom),
                tree.points, -1, y, z2, tile);
        }

        return tile.features.length ? tile : null;
    },

    getClusterExpansionZoom: function (clusterId, clusterZoom) {
        while (clusterZoom < this.options.maxZoom) {
            var children = this.getChildren(clusterId, clusterZoom);
            clusterZoom++;
            if (children.length !== 1) break;
            clusterId = children[0].properties.cluster_id;
        }
        return clusterZoom;
    },

    _appendLeaves: function (result, clusterId, clusterZoom, limit, offset, skipped) {
        var children = this.getChildren(clusterId, clusterZoom);

        for (var i = 0; i < children.length; i++) {
            var props = children[i].properties;

            if (props.cluster) {
                if (skipped + props.point_count <= offset) {
                    // skip the whole cluster
                    skipped += props.point_count;
                } else {
                    // enter the cluster
                    skipped = this._appendLeaves(
                        result, props.cluster_id, clusterZoom + 1, limit, offset, skipped);
                    // exit the cluster
                }
            } else if (skipped < offset) {
                // skip a single point
                skipped++;
            } else {
                // add a single point
                result.push(children[i]);
            }
            if (result.length === limit) break;
        }

        return skipped;
    },

    _addTileFeatures: function (ids, points, x, y, z2, tile) {
        for (var i = 0; i < ids.length; i++) {
            var c = points[ids[i]];
            tile.features.push({
                type: 1,
                geometry: [[
                    Math.round(this.options.extent * (c.x * z2 - x)),
                    Math.round(this.options.extent * (c.y * z2 - y))
                ]],
                tags: c.numPoints ? getClusterProperties(c) : this.points[c.id].properties
            });
        }
    },

    _limitZoom: function (z) {
        return Math.max(this.options.minZoom, Math.min(z, this.options.maxZoom + 1));
    },

    _cluster: function (points, zoom) {
        var clusters = [];
        var r = this.options.radius / (this.options.extent * Math.pow(2, zoom));

        // loop through each point
        for (var i = 0; i < points.length; i++) {
            var p = points[i];
            // if we've already visited the point at this zoom level, skip it
            if (p.zoom <= zoom) continue;
            p.zoom = zoom;

            // find all nearby points
            var tree = this.trees[zoom + 1];
            var neighborIds = tree.within(p.x, p.y, r);

            var numPoints = p.numPoints || 1;
            var wx = p.x * numPoints;
            var wy = p.y * numPoints;

            var clusterProperties = null;

            if (this.options.reduce) {
                clusterProperties = this.options.initial();
                this._accumulate(clusterProperties, p);
            }

            for (var j = 0; j < neighborIds.length; j++) {
                var b = tree.points[neighborIds[j]];
                // filter out neighbors that are too far or already processed
                if (zoom < b.zoom) {
                    var numPoints2 = b.numPoints || 1;
                    b.zoom = zoom; // save the zoom (so it doesn't get processed twice)
                    wx += b.x * numPoints2; // accumulate coordinates for calculating weighted center
                    wy += b.y * numPoints2;
                    numPoints += numPoints2;
                    b.parentId = i;

                    if (this.options.reduce) {
                        this._accumulate(clusterProperties, b);
                    }
                }
            }

            if (numPoints === 1) {
                clusters.push(p);
            } else {
                p.parentId = i;
                clusters.push(createCluster(wx / numPoints, wy / numPoints, numPoints, i, clusterProperties));
            }
        }

        return clusters;
    },

    _accumulate: function (clusterProperties, point) {
        var properties = point.numPoints ?
            point.properties :
            this.options.map(this.points[point.id].properties);

        this.options.reduce(clusterProperties, properties);
    }
};

function createCluster(x, y, numPoints, id, properties) {
    return {
        x: x, // weighted cluster center
        y: y,
        zoom: Infinity, // the last zoom the cluster was processed at
        id: id, // index of the first child of the cluster in the zoom level tree
        properties: properties,
        parentId: -1, // parent cluster id
        numPoints: numPoints
    };
}

function createPointCluster(p, id) {
    var coords = p.geometry.coordinates;
    return {
        x: lngX(coords[0]), // projected point coordinates
        y: latY(coords[1]),
        zoom: Infinity, // the last zoom the point was processed at
        id: id, // index of the source feature in the original input array
        parentId: -1 // parent cluster id
    };
}

function getClusterJSON(cluster) {
    return {
        type: 'Feature',
        properties: getClusterProperties(cluster),
        geometry: {
            type: 'Point',
            coordinates: [xLng(cluster.x), yLat(cluster.y)]
        }
    };
}

function getClusterProperties(cluster) {
    var count = cluster.numPoints;
    var abbrev = count >= 10000 ? Math.round(count / 1000) + 'k' :
                 count >= 1000 ? (Math.round(count / 100) / 10) + 'k' : count;
    return extend(extend({}, cluster.properties), {
        cluster: true,
        cluster_id: cluster.id,
        point_count: count,
        point_count_abbreviated: abbrev
    });
}

// longitude/latitude to spherical mercator in [0..1] range
function lngX(lng) {
    return lng / 360 + 0.5;
}
function latY(lat) {
    var sin = Math.sin(lat * Math.PI / 180),
        y = (0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI);
    return y < 0 ? 0 :
           y > 1 ? 1 : y;
}

// spherical mercator to longitude/latitude
function xLng(x) {
    return (x - 0.5) * 360;
}
function yLat(y) {
    var y2 = (180 - y * 360) * Math.PI / 180;
    return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
}

function extend(dest, src) {
    for (var id in src) dest[id] = src[id];
    return dest;
}

function getX(p) {
    return p.x;
}
function getY(p) {
    return p.y;
}
<commit_msg>minor code cleanup (#37)<commit_after>'use strict';

var kdbush = require('kdbush');

module.exports = supercluster;

function supercluster(options) {
    return new SuperCluster(options);
}

function SuperCluster(options) {
    this.options = extend(Object.create(this.options), options);
    this.trees = new Array(this.options.maxZoom + 1);
}

SuperCluster.prototype = {
    options: {
        minZoom: 0,   // min zoom to generate clusters on
        maxZoom: 16,  // max zoom level to cluster the points on
        radius: 40,   // cluster radius in pixels
        extent: 512,  // tile extent (radius is calculated relative to it)
        nodeSize: 64, // size of the KD-tree leaf node, affects performance
        log: false,   // whether to log timing info

        // a reduce function for calculating custom cluster properties
        reduce: null, // function (accumulated, props) { accumulated.sum += props.sum; }

        // initial properties of a cluster (before running the reducer)
        initial: function () { return {}; }, // function () { return {sum: 0}; },

        // properties to use for individual points when running the reducer
        map: function (props) { return props; } // function (props) { return {sum: props.my_value}; },
    },

    load: function (points) {
        var log = this.options.log;

        if (log) console.time('total time');

        var timerId = 'prepare ' + points.length + ' points';
        if (log) console.time(timerId);

        this.points = points;

        // generate a cluster object for each point and index input points into a KD-tree
        var clusters = points.map(createPointCluster);
        this.trees[this.options.maxZoom + 1] = kdbush(clusters, getX, getY, this.options.nodeSize, Float32Array);

        if (log) console.timeEnd(timerId);

        // cluster points on max zoom, then cluster the results on previous zoom, etc.;
        // results in a cluster hierarchy across zoom levels
        for (var z = this.options.maxZoom; z >= this.options.minZoom; z--) {
            var now = +Date.now();

            // create a new set of clusters for the zoom and index them with a KD-tree
            clusters = this._cluster(clusters, z);
            this.trees[z] = kdbush(clusters, getX, getY, this.options.nodeSize, Float32Array);

            if (log) console.log('z%d: %d clusters in %dms', z, clusters.length, +Date.now() - now);
        }

        if (log) console.timeEnd('total time');

        return this;
    },

    getClusters: function (bbox, zoom) {
        var tree = this.trees[this._limitZoom(zoom)];
        var ids = tree.range(lngX(bbox[0]), latY(bbox[3]), lngX(bbox[2]), latY(bbox[1]));
        var clusters = [];
        for (var i = 0; i < ids.length; i++) {
            var c = tree.points[ids[i]];
            clusters.push(c.numPoints ? getClusterJSON(c) : this.points[c.id]);
        }
        return clusters;
    },

    getChildren: function (clusterId, clusterZoom) {
        var origin = this.trees[clusterZoom + 1].points[clusterId];
        var r = this.options.radius / (this.options.extent * Math.pow(2, clusterZoom));
        var ids = this.trees[clusterZoom + 1].within(origin.x, origin.y, r);
        var children = [];
        for (var i = 0; i < ids.length; i++) {
            var c = this.trees[clusterZoom + 1].points[ids[i]];
            if (c.parentId === clusterId) {
                children.push(c.numPoints ? getClusterJSON(c) : this.points[c.id]);
            }
        }
        return children;
    },

    getLeaves: function (clusterId, clusterZoom, limit, offset) {
        limit = limit || 10;
        offset = offset || 0;

        var leaves = [];
        this._appendLeaves(leaves, clusterId, clusterZoom, limit, offset, 0);

        return leaves;
    },

    getTile: function (z, x, y) {
        var tree = this.trees[this._limitZoom(z)];
        var z2 = Math.pow(2, z);
        var extent = this.options.extent;
        var r = this.options.radius;
        var p = r / extent;
        var top = (y - p) / z2;
        var bottom = (y + 1 + p) / z2;

        var tile = {
            features: []
        };

        this._addTileFeatures(
            tree.range((x - p) / z2, top, (x + 1 + p) / z2, bottom),
            tree.points, x, y, z2, tile);

        if (x === 0) {
            this._addTileFeatures(
                tree.range(1 - p / z2, top, 1, bottom),
                tree.points, z2, y, z2, tile);
        }
        if (x === z2 - 1) {
            this._addTileFeatures(
                tree.range(0, top, p / z2, bottom),
                tree.points, -1, y, z2, tile);
        }

        return tile.features.length ? tile : null;
    },

    getClusterExpansionZoom: function (clusterId, clusterZoom) {
        while (clusterZoom < this.options.maxZoom) {
            var children = this.getChildren(clusterId, clusterZoom);
            clusterZoom++;
            if (children.length !== 1) break;
            clusterId = children[0].properties.cluster_id;
        }
        return clusterZoom;
    },

    _appendLeaves: function (result, clusterId, clusterZoom, limit, offset, skipped) {
        var children = this.getChildren(clusterId, clusterZoom);

        for (var i = 0; i < children.length; i++) {
            var props = children[i].properties;

            if (props.cluster) {
                if (skipped + props.point_count <= offset) {
                    // skip the whole cluster
                    skipped += props.point_count;
                } else {
                    // enter the cluster
                    skipped = this._appendLeaves(
                        result, props.cluster_id, clusterZoom + 1, limit, offset, skipped);
                    // exit the cluster
                }
            } else if (skipped < offset) {
                // skip a single point
                skipped++;
            } else {
                // add a single point
                result.push(children[i]);
            }
            if (result.length === limit) break;
        }

        return skipped;
    },

    _addTileFeatures: function (ids, points, x, y, z2, tile) {
        for (var i = 0; i < ids.length; i++) {
            var c = points[ids[i]];
            tile.features.push({
                type: 1,
                geometry: [[
                    Math.round(this.options.extent * (c.x * z2 - x)),
                    Math.round(this.options.extent * (c.y * z2 - y))
                ]],
                tags: c.numPoints ? getClusterProperties(c) : this.points[c.id].properties
            });
        }
    },

    _limitZoom: function (z) {
        return Math.max(this.options.minZoom, Math.min(z, this.options.maxZoom + 1));
    },

    _cluster: function (points, zoom) {
        var clusters = [];
        var r = this.options.radius / (this.options.extent * Math.pow(2, zoom));

        // loop through each point
        for (var i = 0; i < points.length; i++) {
            var p = points[i];
            // if we've already visited the point at this zoom level, skip it
            if (p.zoom <= zoom) continue;
            p.zoom = zoom;

            // find all nearby points
            var tree = this.trees[zoom + 1];
            var neighborIds = tree.within(p.x, p.y, r);

            var numPoints = p.numPoints || 1;
            var wx = p.x * numPoints;
            var wy = p.y * numPoints;

            var clusterProperties = null;

            if (this.options.reduce) {
                clusterProperties = this.options.initial();
                this._accumulate(clusterProperties, p);
            }

            for (var j = 0; j < neighborIds.length; j++) {
                var b = tree.points[neighborIds[j]];
                // filter out neighbors that are already processed
                if (b.zoom <= zoom) continue;
                b.zoom = zoom; // save the zoom (so it doesn't get processed twice)

                var numPoints2 = b.numPoints || 1;
                wx += b.x * numPoints2; // accumulate coordinates for calculating weighted center
                wy += b.y * numPoints2;

                numPoints += numPoints2;
                b.parentId = i;

                if (this.options.reduce) {
                    this._accumulate(clusterProperties, b);
                }
            }

            if (numPoints === 1) {
                clusters.push(p);
            } else {
                p.parentId = i;
                clusters.push(createCluster(wx / numPoints, wy / numPoints, i, numPoints, clusterProperties));
            }
        }

        return clusters;
    },

    _accumulate: function (clusterProperties, point) {
        var properties = point.numPoints ?
            point.properties :
            this.options.map(this.points[point.id].properties);

        this.options.reduce(clusterProperties, properties);
    }
};

function createCluster(x, y, id, numPoints, properties) {
    return {
        x: x, // weighted cluster center
        y: y,
        zoom: Infinity, // the last zoom the cluster was processed at
        id: id, // index of the first child of the cluster in the zoom level tree
        parentId: -1, // parent cluster id
        numPoints: numPoints,
        properties: properties
    };
}

function createPointCluster(p, id) {
    var coords = p.geometry.coordinates;
    return {
        x: lngX(coords[0]), // projected point coordinates
        y: latY(coords[1]),
        zoom: Infinity, // the last zoom the point was processed at
        id: id, // index of the source feature in the original input array
        parentId: -1 // parent cluster id
    };
}

function getClusterJSON(cluster) {
    return {
        type: 'Feature',
        properties: getClusterProperties(cluster),
        geometry: {
            type: 'Point',
            coordinates: [xLng(cluster.x), yLat(cluster.y)]
        }
    };
}

function getClusterProperties(cluster) {
    var count = cluster.numPoints;
    var abbrev = count >= 10000 ? Math.round(count / 1000) + 'k' :
                 count >= 1000 ? (Math.round(count / 100) / 10) + 'k' : count;
    return extend(extend({}, cluster.properties), {
        cluster: true,
        cluster_id: cluster.id,
        point_count: count,
        point_count_abbreviated: abbrev
    });
}

// longitude/latitude to spherical mercator in [0..1] range
function lngX(lng) {
    return lng / 360 + 0.5;
}
function latY(lat) {
    var sin = Math.sin(lat * Math.PI / 180),
        y = (0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI);
    return y < 0 ? 0 :
           y > 1 ? 1 : y;
}

// spherical mercator to longitude/latitude
function xLng(x) {
    return (x - 0.5) * 360;
}
function yLat(y) {
    var y2 = (180 - y * 360) * Math.PI / 180;
    return 360 * Math.atan(Math.exp(y2)) / Math.PI - 90;
}

function extend(dest, src) {
    for (var id in src) dest[id] = src[id];
    return dest;
}

function getX(p) {
    return p.x;
}
function getY(p) {
    return p.y;
}
<|endoftext|>