<commit_before>var _        = require('underscore')
,   Epg      = require('./lib/epg')
,   conf     = require('./conf')
,   epg      = new Epg(conf.epg)
,   importer = require('./importer/' + conf.importer.type)(conf.importer.options)
,   status   = require('./status_client')('importer')
,   moment   = require('moment')
;

importer.on("error", function(err) {
    console.error("Error importing data", err);
    status.set('status', 'error');
});
importer.on("services", function(services) {
    console.log("Received services from importer");
    _.each(services, function(service) {
        try {
            var serv = epg.listServices();
            if (serv)
                serv = _.find(serv, function(s) {return s.serviceId === parseInt(service.serviceId);});

            if (!serv) {
                console.log("Creating service", service);
                serv = {};
                serv.serviceId = service.serviceId;
                if (service.originalNetworkId)
                    serv.originalNetworkId = service.originalNetworkId;
                else
                    serv.originalNetworkId = service.serviceId;
                if (service.transportStreamId)
                    serv.transportStreamId = service.transportStreamId;
                else
                    serv.transportStreamId = service.serviceId;
                if (service.comment)
                    serv.comment = service.comment;
                else
                    serv.comment = 'Service ' + service.serviceId;
                if (service.signalId)
                    serv.signalId = service.signalId;
                else
                    serv.signalId = service.serviceId;
            } else {
                console.log("Updating service", service);
                if (service.originalNetworkId)
                    serv.originalNetworkId = service.originalNetworkId;
                if (service.transportStreamId)
                    serv.transportStreamId = service.transportStreamId;
                if (service.comment)
                    serv.comment = service.comment;
                if (service.signalId)
                    serv.signalId = service.signalId;
            }
            epg.addService(serv);
            console.log("Service added/updated");
            var cant = epg.deleteEvents({serviceId: serv.serviceId});
            console.log(cant + " events removed from service", serv.serviceId);
        } catch (err) {
            console.error("Error adding/updating service", err, service);
        }
    });
});
importer.on("events", function(events) {
    console.log("Received events from importer");
    var updateEit = _.after(events.length, function() {
        console.log("Updating EIT after import");
        var ret = epg.updateEit();
        if (ret) {
            console.log("EIT updated, updating Carousel");
            epg.updateCarousel();
        }
    });
    var eventId = epg.getMaxEventId();
    _.each(events, function(event) {
        eventId++;
        event.id = eventId;
        console.log("Adding event", event.title);
        try {
            var evt = epg.addEvent(event);
            console.log("Event added");
            updateEit();
        } catch (err) {
            console.error("Error adding event", err, event);
        }
    });
});
importer.on("run", function() {
    console.log("Starting import");
    status.set('status', 'running');
});
importer.on("done", function() {
    console.log("Import finished");
    status.set('status', 'idle');
    status.set('lastRun', moment().toISOString());
    status.set('nextRun', moment().add(importer.options.interval_hours, 'hours').toISOString());
});

status.set('status', 'running', function() {
    console.log("Starting importer");
    importer.run();
});

<commit_msg>Dont use status->error, importer may be still running...<commit_after>var _        = require('underscore')
,   Epg      = require('./lib/epg')
,   conf     = require('./conf')
,   epg      = new Epg(conf.epg)
,   importer = require('./importer/' + conf.importer.type)(conf.importer.options)
,   status   = require('./status_client')('importer')
,   moment   = require('moment')
;

importer.on("error", function(err) {
    console.error("Error importing data", err);
//    status.set('status', 'error');
});
importer.on("services", function(services) {
    console.log("Received services from importer");
    _.each(services, function(service) {
        try {
            var serv = epg.listServices();
            if (serv)
                serv = _.find(serv, function(s) {return s.serviceId === parseInt(service.serviceId);});

            if (!serv) {
                console.log("Creating service", service);
                serv = {};
                serv.serviceId = service.serviceId;
                if (service.originalNetworkId)
                    serv.originalNetworkId = service.originalNetworkId;
                else
                    serv.originalNetworkId = service.serviceId;
                if (service.transportStreamId)
                    serv.transportStreamId = service.transportStreamId;
                else
                    serv.transportStreamId = service.serviceId;
                if (service.comment)
                    serv.comment = service.comment;
                else
                    serv.comment = 'Service ' + service.serviceId;
                if (service.signalId)
                    serv.signalId = service.signalId;
                else
                    serv.signalId = service.serviceId;
            } else {
                console.log("Updating service", service);
                if (service.originalNetworkId)
                    serv.originalNetworkId = service.originalNetworkId;
                if (service.transportStreamId)
                    serv.transportStreamId = service.transportStreamId;
                if (service.comment)
                    serv.comment = service.comment;
                if (service.signalId)
                    serv.signalId = service.signalId;
            }
            epg.addService(serv);
            console.log("Service added/updated");
            var cant = epg.deleteEvents({serviceId: serv.serviceId});
            console.log(cant + " events removed from service", serv.serviceId);
        } catch (err) {
            console.error("Error adding/updating service", err, service);
        }
    });
});
importer.on("events", function(events) {
    console.log("Received events from importer");
    var updateEit = _.after(events.length, function() {
        console.log("Updating EIT after import");
        var ret = epg.updateEit();
        if (ret) {
            console.log("EIT updated, updating Carousel");
            epg.updateCarousel();
        }
    });
    var eventId = epg.getMaxEventId();
    _.each(events, function(event) {
        eventId++;
        event.id = eventId;
        console.log("Adding event", event.title);
        try {
            var evt = epg.addEvent(event);
            console.log("Event added");
            updateEit();
        } catch (err) {
            console.error("Error adding event", err, event);
        }
    });
});
importer.on("run", function() {
    console.log("Starting import");
    status.set('status', 'running');
});
importer.on("done", function() {
    console.log("Import finished");
    status.set('status', 'idle');
    status.set('lastRun', moment().toISOString());
    status.set('nextRun', moment().add(importer.options.interval_hours, 'hours').toISOString());
});

status.set('status', 'running', function() {
    console.log("Starting importer");
    importer.run();
});

<|endoftext|>


<commit_before>var compose = (function () {

var exports = {};

exports.start = function (msg_type, opts) {
    opts = $.extend({ 'message_type': msg_type,
                      'stream': '',
                      'subject': '',
                      'huddle_recipient': '',
                      'message': '' },
                    opts);

    $("#stream").val(opts.stream);
    $("#subject").val(opts.subject);
    $("#huddle_recipient").val(opts.huddle_recipient);
    $("#new_message_content").val(opts.message);

    $('#sidebar a[href="#home"]').tab('show');

    if (msg_type === 'stream') {
        exports.show('stream', $("#stream"));
    } else {
        exports.show('personal', $("#huddle_recipient"));
    }
    hotkeys.set_compose();
    $(document).trigger($.Event('compose_started.zephyr', opts));
};

exports.cancel = function () {
    compose.hide();
    $(document).trigger($.Event('compose_canceled.zephyr'));
};

exports.finish = function () {
    $("#compose form").ajaxSubmit();
    $(document).trigger($.Event('compose_finished.zephyr'));
};

exports.show = function (tabname, focus_area) {
    if (reloading_app) {
        return;
    }
    $("#send-status").removeClass(status_classes).hide();
    $('#compose').css({visibility: "visible"});
    $('.message_comp').slideDown(100);
    $('#message-type-tabs a[href="#' + tabname + '-message"]').tab('show');
    focus_area.focus();
    focus_area.select();
};

exports.hide = function () {
    $('input, textarea, button').blur();
    $('.message_comp').slideUp(100,
                              function() { $('#compose').css({visibility: "hidden"});});
};

exports.clear = function () {
    $("#compose").find('input[type=text], textarea').val('');
};

exports.toggle_mode = function () {
    if ($("#message-type-tabs li.active").find("a[href=#stream-message]").length !== 0) {
        // In stream tab, switch to personals.
        exports.show('personal', $("#huddle_recipient"));
    } else {
        exports.show('stream', $("#stream"));
    }
};

exports.composing = function () {
    if ($("#stream-message").is(":visible")) {
        return 'stream';
    }

    if ($("#personal-message").is(":visible")) {
        return 'huddle';
    }

    return false;
};

exports.stream_name = function (newval) {
    var oldval = $.trim($("#stream").val());
    if (newval !== undefined) {
        $("#stream").val(newval);
    }
    return oldval;
};

exports.subject = function (newval) {
    var oldval =  $.trim($("#subject").val());
    if (newval !== undefined) {
        $("#subject").val(newval);
    }
    return oldval;
};

exports.message = function (newval) {
    var oldval = $.trim($("#new_message_content").val());
    if (newval !== undefined) {
        $("#new_message_content").val(newval);
    }
    return oldval;
};

exports.recipient = function (newval) {
    var oldval = $.trim($("#huddle_recipient").val());
    if (newval !== undefined) {
        $("#huddle_recipient").val(newval);
    }
    return oldval;
};

function compose_error(error_text, bad_input) {
    $('#send-status').removeClass(status_classes)
               .addClass('alert-error')
               .text(error_text)
               .stop(true).fadeTo(0, 1);
    $('#compose').find('input[type="submit"]').removeAttr('disabled');
    bad_input.focus().select();
}

function submit_buttons() {
    return $('#compose').find('input[type="submit"]');
}

// *Synchronously* check if a stream exists.
// If not, displays an error and returns false.
function check_stream_for_send(stream_name) {
    var okay = true;
    $.ajax({
        type: "POST",
        url: "/json/subscriptions/exists",
        data: {'stream': stream_name},
        async: false,
        success: function (data) {
            if (!data.exists) {
                // The stream doesn't exist
                okay = false;
                $('#send-status').removeClass(status_classes).show();
                $('#stream-dne-name').text(stream_name);
                $('#stream-dne').show();
                submit_buttons().removeAttr('disabled');
                exports.hide();
                $('#create-it').focus();
            }
            $("#home-error").hide();
        },
        error: function (xhr) {
            okay = false;
            report_error("Error checking subscription", xhr, $("#home-error"));
            $("#stream").focus();
            submit_buttons().removeAttr('disabled');
        }
    });
    return okay;
}

function validate_stream_message() {
    var stream_name = exports.stream_name();
    if (stream_name === "") {
        compose_error("Please specify a stream", $("#stream"));
        return false;
    }

    if (exports.subject() === "") {
        compose_error("Please specify an subject", $("#subject"));
        return false;
    }

    if (exports.message() === "") {
        compose_error("You have nothing to send!", $("#new_message_content"));
        return false;
    }

    if (!subs.have(stream_name)) {
        if (!check_stream_for_send(stream_name)) {
            return false;
        }
        // You're not subbed to the stream
        $('#send-status').removeClass(status_classes).show();
        $('#stream-nosub-name').text(stream_name);
        $('#stream-nosub').show();
        submit_buttons().removeAttr('disabled');
        exports.hide();
        $('#sub-it').focus();
        return false;
    }

    return true;
}

function validate_huddle_message() {
    if (exports.recipient() === "") {
        compose_error("Please specify at least one recipient", $("#huddle_recipient"));
        return false;
    }

    if (exports.message() === "") {
        compose_error("You have nothing to send!", $("#new_message_content"));
        return false;
    }

    return true;
}

exports.validate = function () {
    submit_buttons().attr('disabled', 'disabled').blur();

    if (exports.composing() === 'huddle') {
        return validate_huddle_message();
    } else {
        return validate_stream_message();
    }
};

return exports;

}());
<commit_msg>Determine whether the user is composing based on a flag instead of element visibility<commit_after>var compose = (function () {

var exports = {};
var is_composing_message = false;

exports.start = function (msg_type, opts) {
    opts = $.extend({ 'message_type': msg_type,
                      'stream': '',
                      'subject': '',
                      'huddle_recipient': '',
                      'message': '' },
                    opts);

    $("#stream").val(opts.stream);
    $("#subject").val(opts.subject);
    $("#huddle_recipient").val(opts.huddle_recipient);
    $("#new_message_content").val(opts.message);

    $('#sidebar a[href="#home"]').tab('show');

    if (msg_type === 'stream') {
        exports.show('stream', $("#stream"));
    } else {
        // for making compose.composing() output consistent
        msg_type = "huddle";

        exports.show('personal', $("#huddle_recipient"));
    }
    hotkeys.set_compose();
    is_composing_message = msg_type;
    $(document).trigger($.Event('compose_started.zephyr', opts));
};

exports.cancel = function () {
    compose.hide();
    is_composing_message = false;
    $(document).trigger($.Event('compose_canceled.zephyr'));
};

exports.finish = function () {
    $("#compose form").ajaxSubmit();
    is_composing_message = false;
    $(document).trigger($.Event('compose_finished.zephyr'));
};

exports.show = function (tabname, focus_area) {
    if (reloading_app) {
        return;
    }
    $("#send-status").removeClass(status_classes).hide();
    $('#compose').css({visibility: "visible"});
    $('.message_comp').slideDown(100);
    $('#message-type-tabs a[href="#' + tabname + '-message"]').tab('show');
    focus_area.focus();
    focus_area.select();
};

exports.hide = function () {
    $('input, textarea, button').blur();
    $('.message_comp').slideUp(100,
                              function() { $('#compose').css({visibility: "hidden"});});
};

exports.clear = function () {
    $("#compose").find('input[type=text], textarea').val('');
};

exports.toggle_mode = function () {
    if ($("#message-type-tabs li.active").find("a[href=#stream-message]").length !== 0) {
        // In stream tab, switch to personals.
        exports.show('personal', $("#huddle_recipient"));
    } else {
        exports.show('stream', $("#stream"));
    }
};

exports.composing = function () {
    return is_composing_message;
};

exports.stream_name = function (newval) {
    var oldval = $.trim($("#stream").val());
    if (newval !== undefined) {
        $("#stream").val(newval);
    }
    return oldval;
};

exports.subject = function (newval) {
    var oldval =  $.trim($("#subject").val());
    if (newval !== undefined) {
        $("#subject").val(newval);
    }
    return oldval;
};

exports.message = function (newval) {
    var oldval = $.trim($("#new_message_content").val());
    if (newval !== undefined) {
        $("#new_message_content").val(newval);
    }
    return oldval;
};

exports.recipient = function (newval) {
    var oldval = $.trim($("#huddle_recipient").val());
    if (newval !== undefined) {
        $("#huddle_recipient").val(newval);
    }
    return oldval;
};

function compose_error(error_text, bad_input) {
    $('#send-status').removeClass(status_classes)
               .addClass('alert-error')
               .text(error_text)
               .stop(true).fadeTo(0, 1);
    $('#compose').find('input[type="submit"]').removeAttr('disabled');
    bad_input.focus().select();
}

function submit_buttons() {
    return $('#compose').find('input[type="submit"]');
}

// *Synchronously* check if a stream exists.
// If not, displays an error and returns false.
function check_stream_for_send(stream_name) {
    var okay = true;
    $.ajax({
        type: "POST",
        url: "/json/subscriptions/exists",
        data: {'stream': stream_name},
        async: false,
        success: function (data) {
            if (!data.exists) {
                // The stream doesn't exist
                okay = false;
                $('#send-status').removeClass(status_classes).show();
                $('#stream-dne-name').text(stream_name);
                $('#stream-dne').show();
                submit_buttons().removeAttr('disabled');
                exports.hide();
                $('#create-it').focus();
            }
            $("#home-error").hide();
        },
        error: function (xhr) {
            okay = false;
            report_error("Error checking subscription", xhr, $("#home-error"));
            $("#stream").focus();
            submit_buttons().removeAttr('disabled');
        }
    });
    return okay;
}

function validate_stream_message() {
    var stream_name = exports.stream_name();
    if (stream_name === "") {
        compose_error("Please specify a stream", $("#stream"));
        return false;
    }

    if (exports.subject() === "") {
        compose_error("Please specify an subject", $("#subject"));
        return false;
    }

    if (exports.message() === "") {
        compose_error("You have nothing to send!", $("#new_message_content"));
        return false;
    }

    if (!subs.have(stream_name)) {
        if (!check_stream_for_send(stream_name)) {
            return false;
        }
        // You're not subbed to the stream
        $('#send-status').removeClass(status_classes).show();
        $('#stream-nosub-name').text(stream_name);
        $('#stream-nosub').show();
        submit_buttons().removeAttr('disabled');
        exports.hide();
        $('#sub-it').focus();
        return false;
    }

    return true;
}

function validate_huddle_message() {
    if (exports.recipient() === "") {
        compose_error("Please specify at least one recipient", $("#huddle_recipient"));
        return false;
    }

    if (exports.message() === "") {
        compose_error("You have nothing to send!", $("#new_message_content"));
        return false;
    }

    return true;
}

exports.validate = function () {
    submit_buttons().attr('disabled', 'disabled').blur();

    if (exports.composing() === 'huddle') {
        return validate_huddle_message();
    } else {
        return validate_stream_message();
    }
};

return exports;

}());
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import * as d3 from 'd3';

class ArcProgress extends Component {
    static propTypes = {
        id: PropTypes.string,
        height: PropTypes.number,
        width: PropTypes.number,
        innerRadius: PropTypes.number,
        outerRadius: PropTypes.number,
        backgroundColor: PropTypes.string,
        foregroundColor: PropTypes.string,
        percentComplete: PropTypes.number
    }

    render() {
        return (

        );
    }

    tau = Math.PI * 2;

    arc() {
        return d3.arc()
            .innerRadius(this.props.innerRadius)
            .outerRadius(this.props.outerRadius)
            .startAngle(0);
    }

    componentDidMount() {
        this.drawArc();
    }

    componentDidUpdate() {
        this.redrawArc();
    }

    drawArc() {
        const context = this.setContext();
        this.setBackground(context);
        this.setForeground(context);
    }

    redrawArc() {
        const context = d3.select('#d3-arc');
        context.remove();
        this.drawArc();
    }

    setContext() {
        const { height, width, id } = this.props;
        return d3.select(this.refs.arc).append('svg')
            .attr('height', height)
            .attr('width', width)
            .attr('id', id)
            .append('g')
            .attr('transform', `translate(${height / 2}, ${width / 2})`);
    }

    setBackground(context) {
        return context.append('path')
        .datum({ endAngle: this.tau })
        .style('fill', this.props.backgroundColor)
        .attr('d', this.arc());
    }

    setForeground(context) {
        return context.append('path')
        .datum({ endAngle: this.tau * this.props.percentComplete })
        .style('fill', this.props.foregroundColor)
        .attr('d', this.arc());
    }

}

export default ArcProgress;
<commit_msg>Add label to arc progress.<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import * as d3 from 'd3';

class ArcProgress extends Component {
    static propTypes = {
        id: PropTypes.string,
        height: PropTypes.number,
        width: PropTypes.number,
        innerRadius: PropTypes.number,
        outerRadius: PropTypes.number,
        backgroundColor: PropTypes.string,
        foregroundColor: PropTypes.string,
        percentComplete: PropTypes.number
    }

    render() {
        return (

        );
    }

    tau = Math.PI * 2;

    arc() {
        return d3.arc()
            .innerRadius(this.props.innerRadius)
            .outerRadius(this.props.outerRadius)
            .startAngle(0);
    }

    componentDidMount() {
        this.drawArc();
    }

    componentDidUpdate() {
        this.redrawArc();
    }

    drawArc() {
        const context = this.setContext();
        this.setBackground(context);
        this.setForeground(context);
        this.setLabel(context);
    }

    redrawArc() {
        const context = d3.select('#d3-arc');
        context.remove();
        this.drawArc();
    }

    setContext() {
        const { height, width, id } = this.props;
        return d3.select(this.refs.arc).append('svg')
            .attr('height', height)
            .attr('width', width)
            .attr('id', id)
            .append('g')
            .attr('transform', `translate(${height / 2}, ${width / 2})`);
    }

    setBackground(context) {
        return context.append('path')
        .datum({ endAngle: this.tau })
        .style('fill', this.props.backgroundColor)
        .attr('d', this.arc());
    }

    setForeground(context) {
        return context.append('path')
        .datum({ endAngle: this.tau * this.props.percentComplete })
        .style('fill', this.props.foregroundColor)
        .attr('d', this.arc());
    }

    setLabel(context) {
        return context.append('text')
            .attr('transform', `translate(${height / 2}, ${width / 2})`)
            .attr('text-anchor', 'middle')
            .text('English Diagnostic');
    }

}

export default ArcProgress;
<|endoftext|>


<commit_before>import { Tasks } from './tasks/tasks.js'
import './tasks/methods.js'

if (Meteor.isServer) {
  console.log("PUBLISHING TASKS!!!");
  // This code only runs on the server
  // Only publish tasks that are public or belong to the current user
  Meteor.publish('tasks', function tasksPublication() {
    return Tasks.find({
      $or: [
        { private: { $ne: true } },
        { owner: this.userId },
      ],
    });
  });
}

export { Tasks }
<commit_msg>wrote sort function<commit_after>import { Tasks } from './tasks/tasks.js'
import './tasks/methods.js'

if (Meteor.isServer) {
  console.log("PUBLISHING TASKS!!!");
  // This code only runs on the server
  // Only publish tasks that are public or belong to the current user
  Meteor.publish('tasks', function tasksPublication() {
    return Tasks.find({
      $or: [
        { private: { $ne: true } },
        { owner: this.userId },
      ],
    });
  });

  Meteor.publish('sort', function sort() {
    return Tasks.find({}, {sort: {is_urgent: 1, is_important: 1}})
  })
}

export { Tasks }
<|endoftext|>


<commit_before>var metrics = (function () {

var exports = {people: {}};

function enable_metrics() {
    return page_params.domain === "humbughq.com";
}

var methods = ["disable", "track", "track_pageview", "track_links", "track_forms",
               "register", "register_once", "alias", "unregister", "identify",
               "name_tag", "set_config"];
var people_methods = ["set", "set_once", "increment", "append", "track_charge",
                      "clear_charges", "delete_user"];

function wrap_method(name, source_container, target_container_func) {
    source_container[name] = function metrics_wrapper () {
        if (enable_metrics()) {
            // We must reference mixpanel indirectly here because
            // mixpanel loads asynchronously with stub methods and
            // replaces its methods when it fully loads
            var target_container = target_container_func();
            return target_container[name].apply(target_container, arguments);
        }
    };
}

$.each(methods, function (idx, method) {
    wrap_method(method, exports, function () { return mixpanel; });
});

$.each(people_methods, function (idx, method) {
    wrap_method(method, exports.people, function () { return mixpanel.people; });
});

// This should probably move elsewhere
$(function () {
    $(document).on('compose_started.zephyr', function (event) {
        metrics.track('compose started', {user: page_params.email,
                                          realm: page_params.domain,
                                          type: event.message_type,
                                          trigger: event.trigger},
                      function (arg) {
                          if (arg !== undefined && arg.status !== 1) {
                              blueslip.warn(arg);
                          }
                      });
    });
    $(document).on('narrow_activated.zephyr', function (event) {
        var operators = event.filter.operators();
        var stream_operands = event.filter.operands('stream');
        var subject_operands = event.filter.operands('subject');
        var reported_operators;
        if (operators.length === 1) {
            reported_operators = operators[0][0];
        } else if (operators.length === 2
                   && stream_operands.length !== 0 && subject_operands.length !== 0) {
            reported_operators = 'stream and subject';
        } else {
            reported_operators = 'multiple';
        }

        metrics.track('narrow activated', {user: page_params.email,
                                           realm: page_params.domain,
                                           operators: reported_operators,
                                           trigger: event.trigger},
                      function (arg) {
                          if (arg !== undefined && arg.status !== 1) {
                              blueslip.warn(arg);
                          }
                      });
    });
});

return exports;
}());<commit_msg>Remove mixpanel wrapper<commit_after>var metrics = (function () {

var exports = {};

function enable_metrics() {
    return page_params.domain === "humbughq.com";
}

if (! enable_metrics()) {
    mixpanel.disable();
}

$(function () {
    $(document).on('compose_started.zephyr', function (event) {
        mixpanel.track('compose started', {user: page_params.email,
                                           realm: page_params.domain,
                                           type: event.message_type,
                                           trigger: event.trigger},
                       function (arg) {
                           if (arg !== undefined && arg.status !== 1) {
                               blueslip.warn(arg);
                           }
                       });
    });
    $(document).on('narrow_activated.zephyr', function (event) {
        var operators = event.filter.operators();
        var stream_operands = event.filter.operands('stream');
        var subject_operands = event.filter.operands('subject');
        var reported_operators;
        if (operators.length === 1) {
            reported_operators = operators[0][0];
        } else if (operators.length === 2
                   && stream_operands.length !== 0 && subject_operands.length !== 0) {
            reported_operators = 'stream and subject';
        } else {
            reported_operators = 'multiple';
        }

        mixpanel.track('narrow activated', {user: page_params.email,
                                            realm: page_params.domain,
                                            operators: reported_operators,
                                            trigger: event.trigger},
                       function (arg) {
                           if (arg !== undefined && arg.status !== 1) {
                               blueslip.warn(arg);
                           }
                       });
    });
});

return exports;
}());
<|endoftext|>


<commit_before>/**
 * Javascript interface for active suggestions while posting
 *
 * @author Shitiz Garg <mail@dragooon.net>
 * @copyright 2014 Shitiz Garg
 * @license Simplified BSD (2-Clause) License
 */

var mentionInit = function()
{
	$(function()
	{
		var config = {
			at: '@',
			data: [],
			show_the_at: true,
			callbacks: {
				remote_filter: function(query, callback)
				{
					if (query.length < 2)
						return;

					$.ajax({
						url: smf_scripturl + '?action=suggest;' + smf_sessvar + '=' + smf_sessid + ';xml',
						method: 'GET',
						data: {
							search: query,
							suggest_type: 'member'
						},
						success: function(data)
						{
							var members = $(data).find('smf > items > item');
							var callbackArray = [];
							$.each(members, function(index, item)
							{
								callbackArray[callbackArray.length] = {
									name: $(item).text()
								};
							});

							callback(callbackArray);
						}
					});
				}
			}
		};

		var iframe = $('#html_message');
		if (typeof iframe[0] != 'undefined')
			$(iframe[0].contentDocument.body).atwho(config);
		$('textarea[name=message]').atwho(config);
	});
};

var atWhoElement = document.createElement('script');
atWhoElement.src = atwho_url;
atWhoElement.type = 'text/javascript';
atWhoElement.onload = mentionInit;

if (typeof $ == 'undefined')
{
	var scriptElement = document.createElement('script');
	scriptElement.src = jquery_url;
	scriptElement.type = 'text/javascript';

	scriptElement.onload = function()
	{
		document.getElementsByTagName('head')[0].appendChild(atWhoElement);
	};

	document.getElementsByTagName('head')[0].appendChild(scriptElement);
}
else
	document.getElementsByTagName('head')[0].appendChild(atWhoElement);<commit_msg>Increase popup limit for active suggestions to 10 members<commit_after>/**
 * Javascript interface for active suggestions while posting
 *
 * @author Shitiz Garg <mail@dragooon.net>
 * @copyright 2014 Shitiz Garg
 * @license Simplified BSD (2-Clause) License
 */

var mentionInit = function()
{
	$(function()
	{
		var config = {
			at: '@',
			data: [],
			show_the_at: true,
			limit: 10,
			callbacks: {
				remote_filter: function(query, callback)
				{
					if (query.length < 2)
						return;

					$.ajax({
						url: smf_scripturl + '?action=suggest;' + smf_sessvar + '=' + smf_sessid + ';xml',
						method: 'GET',
						data: {
							search: query,
							suggest_type: 'member'
						},
						success: function(data)
						{
							var members = $(data).find('smf > items > item');
							var callbackArray = [];
							$.each(members, function(index, item)
							{
								callbackArray[callbackArray.length] = {
									name: $(item).text()
								};
							});

							callback(callbackArray);
						}
					});
				}
			}
		};

		var iframe = $('#html_message');
		if (typeof iframe[0] != 'undefined')
			$(iframe[0].contentDocument.body).atwho(config);
		$('textarea[name=message]').atwho(config);
	});
};

var atWhoElement = document.createElement('script');
atWhoElement.src = atwho_url;
atWhoElement.type = 'text/javascript';
atWhoElement.onload = mentionInit;

if (typeof $ == 'undefined')
{
	var scriptElement = document.createElement('script');
	scriptElement.src = jquery_url;
	scriptElement.type = 'text/javascript';

	scriptElement.onload = function()
	{
		document.getElementsByTagName('head')[0].appendChild(atWhoElement);
	};

	document.getElementsByTagName('head')[0].appendChild(scriptElement);
}
else
	document.getElementsByTagName('head')[0].appendChild(atWhoElement);<|endoftext|>


<commit_before>var PersonsController = function($rootScope, $scope, $state, $stateParams, ftrees, notification, $timeout, $uibModal, $window, $location) {

	var self = this,
		advanced_fields = ['birth_place', 'marriage_place', 'death_place', 'tree_number', 'birth_year', 'marriage_year', 'death_year'];
	this.individuals = [];
	this.search_params = {};
	this.query = '';

	this.search_modifiers = {
		first_name: 	'',
		last_name: 		'',
		birth_place: 	'',
		marriage_place: '',
		death_place: 	'',
		place:          ''
	};
	this.tree_number = parseInt($stateParams['tree_number']);

	this.$state = $state;
	this.$stateParams = $stateParams;
	this.$scope = $scope;
	this.$window = $window;
	this.$location = $location;
	this.ftrees = ftrees;
	this.notification = notification;
	this.persons_welcome_msg = ($window.sessionStorage.getItem('persons-welcome-msg') == 'dismissed') ||
		  					   ($window.localStorage.getItem('persons-welcome-msg') == 'dismissed');

	Object.defineProperty($scope, '$stateParams', {
		get: function() {
			return $stateParams;
		}
	});

   	$rootScope.$on('$stateChangeStart',
	    function(event, toState, toParams, fromState, fromParams) {
		  	var str = 'persons';
		  	var from_person_state = fromState.name.indexOf(str, fromState.name.length - str.length) !== -1;
		  	var to_person_state = toState.name.indexOf(str, toState.name.length - str.length) !== -1;

		  	if (to_person_state && from_person_state && fromParams.more != toParams.more) {
		  		event.preventDefault();
			  	self.$state.transitionTo(toState.name, toParams, {notify: false});
		  	}
	});

	if (!self.persons_welcome_msg) {

		var modalInstance = $uibModal.open({
		     	templateUrl: 'templates/main/ftrees/persons-welcome-message.html',
		     	controller: 'PersonsWelcomeCtrl',
		     	animation: true,
		     	size: 'ftree'
	    });

		modalInstance.result.then(function () {
		    $window.localStorage.setItem('persons-welcome-msg', 'dismissed');

		}, function () {
		    $window.sessionStorage.setItem('persons-welcome-msg', 'dismissed');
		});
	};

	//search
	var query = {},
	    parameters = [],
	    advanced = $stateParams['more']=='1';

	for (var key in $stateParams) {
		if ($stateParams[key]) {
			// read state params & update bound objects to update view accordingly
			var val = $stateParams[key];
			if (key !== 'more') {
				if (advanced || (advanced_fields.indexOf(key) == -1)) {
					parameters.push(val);
					query[key] = val;
				}

				// handle search modifiers & fudge factors in query string
				if ( val.indexOf(';') !== -1 ) {
					var parts = val.split(';');
					self.search_params[key] = parts[0];
					self.search_modifiers[key] = parts[1];
				}
				else {
					if(key !== 'tree_number') {
						self.search_params[key] = $stateParams[key];
					}
					else
						self.search_params[key] = parseInt($stateParams[key]);
				}
			}
		}
	};
	if (parameters.length > 0) {
		this.query = parameters.join(' + ');
		self.search(query);
	};
};

PersonsController.prototype = {
	search: function(search_params) {
		var self = this;
		this.notification.loading(true);
		this.ftrees.search(search_params).
			then(function(individuals) {
				self.individuals = individuals;

				self.notification.loading(false);
				if (individuals.total == 0)
					self.notification.put(3);

			}, function() {
				self.notification.put(500);
			});
	},

	fetch_more: function() {
		var self = this;
		this.notification.loading(true);
		var search_params = angular.copy(self.search_params);
		search_params.start = self.individuals.items.length;
		this.handle_search_modifiers(search_params);
		this.ftrees.search(search_params).
			then(function (r){
				self.individuals.items = self.individuals.items.concat(r.items);
				self.notification.loading(false);
			});
    },

    toggle_more: function() {
    	var param = this.$stateParams;
    	if (param.more == '0' || param.more == undefined) {
    		param.more = '1';
    		if(this.search_params.place) {
    			this.search_params.birth_place = this.search_params.place;
    			this.search_params.place = '';
    		}
    	}
    	else {
     		param.more = '0';
     		if(this.search_params.birth_place) {
     			this.search_params.place = this.search_params.birth_place;
     			this.search_params.birth_place = '';
     		}
    	}
    	this.$location.search(param);
    },

    handle_search_modifiers: function(search_params) {
		for (var modifier in this.search_modifiers) {
			var modifier_string = this.search_modifiers[modifier];
			if (this.search_params[modifier] !== undefined && modifier_string !== '') {
				search_params[modifier] += ';' + modifier_string;
			}
		}
    },

	update: function() {
		var self = this;
		var search_params = angular.copy(this.search_params);
		for (var param in search_params) {
			if (search_params[param] === '') {
				delete search_params[param];
			}
		}
		this.handle_search_modifiers(search_params);
		search_params.more = this.$stateParams.more;
		var prev_search = this.$window.sessionStorage.getItem('ftrees_search_params');
		if (JSON.stringify(search_params) === prev_search) {
			self.notification.put(20);
		}
		this.$state.go('persons', search_params, {inherit: false});
		this.$window.sessionStorage.setItem('ftrees_search_params', JSON.stringify(search_params));
	},

 	clear_filters: function() {
 		for (var parameter in this.search_params) {
 			this.search_params[parameter] = '';
 			this.$location.search(parameter, null);
 		}
 	},

	get_href: function (individual) {
		// TODO: support languages
		return this.$state.href('person-view', {tree_number: individual.GTN,
							   			   node_id: individual.II});
	},

	read_about_center: function (collection_name) {
        this.$state.go('about_center', {collection: collection_name});
    },
};

angular.module('main').controller('PersonsController', ['$rootScope', '$scope', '$state', '$stateParams', 'ftrees', 'notification', '$timeout', '$uibModal', '$window', '$location', PersonsController]);
<commit_msg>Persons Search: reset empty fields operators before next search<commit_after>var PersonsController = function($rootScope, $scope, $state, $stateParams, ftrees, notification, $timeout, $uibModal, $window, $location) {

	var self = this,
		advanced_fields = ['birth_place', 'marriage_place', 'death_place', 'tree_number', 'birth_year', 'marriage_year', 'death_year'];
	this.individuals = [];
	this.search_params = {};
	this.query = '';

	this.search_modifiers = {
		first_name: 	'',
		last_name: 		'',
		birth_place: 	'',
		marriage_place: '',
		death_place: 	'',
		place:          ''
	};
	this.tree_number = parseInt($stateParams['tree_number']);

	this.$state = $state;
	this.$stateParams = $stateParams;
	this.$scope = $scope;
	this.$window = $window;
	this.$location = $location;
	this.ftrees = ftrees;
	this.notification = notification;
	this.persons_welcome_msg = ($window.sessionStorage.getItem('persons-welcome-msg') == 'dismissed') ||
		  					   ($window.localStorage.getItem('persons-welcome-msg') == 'dismissed');

	Object.defineProperty($scope, '$stateParams', {
		get: function() {
			return $stateParams;
		}
	});

   	$rootScope.$on('$stateChangeStart',
	    function(event, toState, toParams, fromState, fromParams) {
		  	var str = 'persons';
		  	var from_person_state = fromState.name.indexOf(str, fromState.name.length - str.length) !== -1;
		  	var to_person_state = toState.name.indexOf(str, toState.name.length - str.length) !== -1;

		  	if (to_person_state && from_person_state && fromParams.more != toParams.more) {
		  		event.preventDefault();
			  	self.$state.transitionTo(toState.name, toParams, {notify: false});
		  	}
	});

	if (!self.persons_welcome_msg) {

		var modalInstance = $uibModal.open({
		     	templateUrl: 'templates/main/ftrees/persons-welcome-message.html',
		     	controller: 'PersonsWelcomeCtrl',
		     	animation: true,
		     	size: 'ftree'
	    });

		modalInstance.result.then(function () {
		    $window.localStorage.setItem('persons-welcome-msg', 'dismissed');

		}, function () {
		    $window.sessionStorage.setItem('persons-welcome-msg', 'dismissed');
		});
	};

	//search
	var query = {},
	    parameters = [],
	    advanced = $stateParams['more']=='1';

	for (var key in $stateParams) {
		if ($stateParams[key]) {
			// read state params & update bound objects to update view accordingly
			var val = $stateParams[key];
			if (key !== 'more') {
				if (advanced || (advanced_fields.indexOf(key) == -1)) {
					parameters.push(val);
					query[key] = val;
				}

				// handle search modifiers & fudge factors in query string
				if ( val.indexOf(';') !== -1 ) {
					var parts = val.split(';');
					self.search_params[key] = parts[0];
					self.search_modifiers[key] = parts[1];
				}
				else {
					if(key !== 'tree_number') {
						self.search_params[key] = $stateParams[key];
					}
					else
						self.search_params[key] = parseInt($stateParams[key]);
				}
			}
		}
	};
	if (parameters.length > 0) {
		this.query = parameters.join(' + ');
		self.search(query);
	};
};

PersonsController.prototype = {
	search: function(search_params) {
		var self = this;
		this.notification.loading(true);
		this.ftrees.search(search_params).
			then(function(individuals) {
				self.individuals = individuals;

				self.notification.loading(false);
				if (individuals.total == 0)
					self.notification.put(3);

			}, function() {
				self.notification.put(500);
			});
	},

	fetch_more: function() {
		var self = this;
		this.notification.loading(true);
		var search_params = angular.copy(self.search_params);
		search_params.start = self.individuals.items.length;
		this.handle_search_modifiers(search_params);
		this.ftrees.search(search_params).
			then(function (r){
				self.individuals.items = self.individuals.items.concat(r.items);
				self.notification.loading(false);
			});
    },

    toggle_more: function() {
    	var param = this.$stateParams;
    	if (param.more == '0' || param.more == undefined) {
    		param.more = '1';
    		if(this.search_params.place) {
    			this.search_params.birth_place = this.search_params.place;
    			this.search_params.place = '';
    		}
    	}
    	else {
     		param.more = '0';
     		if(this.search_params.birth_place) {
     			this.search_params.place = this.search_params.birth_place;
     			this.search_params.birth_place = '';
     		}
    	}
    	this.$location.search(param);
    },

    handle_search_modifiers: function(search_params) {
		for (var modifier in this.search_modifiers) {
			if (modifier in search_params) {
				var modifier_string = this.search_modifiers[modifier];
				if (this.search_params[modifier] !== undefined && modifier_string !== '') {
					search_params[modifier] += ';' + modifier_string;
				}
			}
		}
    },

	update: function() {
		var self = this;
		var search_params = angular.copy(this.search_params);
		for (var param in search_params) {
			if (search_params[param] === '' || search_params[param] == undefined) {
				delete search_params[param];
			}
		}
		this.handle_search_modifiers(search_params);
		search_params.more = this.$stateParams.more;
		var prev_search = this.$window.sessionStorage.getItem('ftrees_search_params');
		if (JSON.stringify(search_params) === prev_search) {
			self.notification.put(20);
		}
		this.$state.go('persons', search_params, {inherit: false});
		this.$window.sessionStorage.setItem('ftrees_search_params', JSON.stringify(search_params));
	},

 	clear_filters: function() {
 		for (var parameter in this.search_params) {
 			this.search_params[parameter] = '';
 			this.$location.search(parameter, null);
 		}
 	},

	get_href: function (individual) {
		// TODO: support languages
		return this.$state.href('person-view', {tree_number: individual.GTN,
							   			   node_id: individual.II});
	},

	read_about_center: function (collection_name) {
        this.$state.go('about_center', {collection: collection_name});
    },
};

angular.module('main').controller('PersonsController', ['$rootScope', '$scope', '$state', '$stateParams', 'ftrees', 'notification', '$timeout', '$uibModal', '$window', '$location', PersonsController]);
<|endoftext|>


<commit_before>async function(args) {
/*
created_timestamp(-1)
base_component_id("homepage")
is_app(true)
display_name("Homepage app")
description('Homepage app')
load_once_from_file(true)
logo_url("https://cdn0.tnwcdn.com/wp-content/blogs.dir/1/files/2017/05/Best-Homepages--796x563.jpg")
*/
    var introa = ['homepage_1','todo', 'test','vue','search']

    var mm = null

    Vue.component('homepage', {

      template: `

                                    <component :is='item'
                                        ></component>

{{item.display_name}}

Copy

                            Contact@AppShare.co   +45 2859 5405

Create amazing interactive forms for your website

Build forms in under 5 minutes in Javascript

Embed forms easily into your website

Many templates available to get started easily

Use online or host it in your Enterprise

                                    <form_subscribe_to_appshare/>

Choose a template and create a form now

{{item.display_name}}

Copy

                               Download Appshare for Windows

                               Download Appshare for Mac

                            This website was designed with Appshare

                            On Github:
                            https://github.com/zubairq/appshare

                            Website:
                            AppShare.co

       `
      ,

    data: function() {
        return {
                    apps: [],
                    intro_apps: introa,
                    loaded_app: new Object(),
                    refresh: 0
                }},

      mounted: async function() {
            mm = this
            for (var rt = 0; rt < 2; rt++) {
                await load(mm.intro_apps[rt])
                mm.loaded_app[mm.intro_apps[rt]] = true
            }

            mm.search()
      },
      methods: {
          search: async function() {
               this.apps = await callApp({   driver_name: "systemFunctions3",  method_name:"get_public_apps_list"}, { }) }

      }
    })
    setTimeout(async function() {
        for (var rt2 = 2; rt2 < mm.intro_apps.length; rt2 ++) {
            var appN = mm.intro_apps[rt2]
            await load(appN)
            mm.loaded_app[appN] = true
            mm.refresh++
        }
    },3000)
}
<commit_msg>made pinterest style rounded corners<commit_after>async function(args) {
/*
created_timestamp(-1)
base_component_id("homepage")
is_app(true)
display_name("Homepage app")
description('Homepage app')
load_once_from_file(true)
logo_url("https://cdn0.tnwcdn.com/wp-content/blogs.dir/1/files/2017/05/Best-Homepages--796x563.jpg")
*/
    var introa = ['homepage_1','todo', 'test','vue','search']

    var mm = null

    Vue.component('homepage', {

      template: `

                                    <component :is='item'
                                        ></component>

{{item.display_name}}

Copy

                            Contact@AppShare.co   +45 2859 5405

Create amazing interactive forms for your website

Build forms in under 5 minutes in Javascript

Embed forms easily into your website

Many templates available to get started easily

Use online or host it in your Enterprise

                                    <form_subscribe_to_appshare/>

Choose a template and create a form now

{{item.display_name}}

Copy

                               Download Appshare for Windows

                               Download Appshare for Mac

                            This website was designed with Appshare

                            On Github:
                            https://github.com/zubairq/appshare

                            Website:
                            AppShare.co

       `
      ,

    data: function() {
        return {
                    apps: [],
                    intro_apps: introa,
                    loaded_app: new Object(),
                    refresh: 0
                }},

      mounted: async function() {
            mm = this
            for (var rt = 0; rt < 2; rt++) {
                await load(mm.intro_apps[rt])
                mm.loaded_app[mm.intro_apps[rt]] = true
            }

            mm.search()
      },
      methods: {
          search: async function() {
               this.apps = await callApp({   driver_name: "systemFunctions3",  method_name:"get_public_apps_list"}, { }) }

      }
    })
    setTimeout(async function() {
        for (var rt2 = 2; rt2 < mm.intro_apps.length; rt2 ++) {
            var appN = mm.intro_apps[rt2]
            await load(appN)
            mm.loaded_app[appN] = true
            mm.refresh++
        }
    },3000)
}
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import arrayFindIndex from 'array-find-index';
import classNames from 'classnames';

import createAudioElementWithLoopEvent from './createAudioElementWithLoopEvent';
import getDisplayText from './utils/getDisplayText';
import getControlComponent from './utils/getControlComponent';

import './index.scss';

const log = console.log.bind(console);
const logError = console.error ? console.error.bind(console) : log;
const logWarning = console.warn ? console.warn.bind(console) : log;

/*
 * AudioPlayer
 *
 * Accepts 'playlist' prop of the form:
 *
 * [{ "url": "./path/to/file.mp3",
 *    "displayText": "ArtistA - Track 1" },
 *  { "url": "https://domain.com/track2.ogg",
 *    "displayText": "ArtistB - Track 2" }]
 *
 * Accepts 'autoplay' prop (true/[false]).
 *
 * Accepts 'autoplayDelayInSeconds' prop (default 0).
 *
 * Accepts 'gapLengthInSeconds' prop (default 0).
 * Specifies gap at end of one track before next
 * track begins (ignored for manual skip).
 *
 * Accepts 'cycle' prop (default true,
 * starts playing at the beginning of the playlist
 * when finished if true).
 *
 * Accepts 'pauseOnSeekPreview' prop (default true,
 * pauses audio while user is selecting new time
 * for playback)
 *
 * Accepts 'disableSeek' prop (default false,
 * disables seeking through the audio if true).
 *
 * Accepts 'stayOnBackSkipThreshold' prop, default 5,
 * is number of seconds to progress until pressing back skip
 * restarts the current track.
 *
 * Accepts 'style' prop, object, is applied to
 * outermost div (React styles).
 *
 * Accepts 'onMediaEvent' prop, an object used for
 * listening to media events on the underlying audio element.
 *
 * Accepts 'audioElementRef' prop, a function called after
 * the component mounts and before it unmounts with the
 * internally-referenced HTML audio element as its only parameter.
 * Similar to: https://facebook.github.io/react/docs/refs-and-the-dom.html
 */
class AudioPlayer extends Component {

  constructor (props) {
    super(props);

    this.defaultState = {
      /* activeTrackIndex will change to match
       * this.currentTrackIndex once metadata has loaded
       */
      activeTrackIndex: -1,
      // indicates whether audio player should be paused
      paused: true,
      // elapsed time for current track, in seconds
      currentTime: 0,
      // The most recent targeted time, in seconds, for seek preview
      seekPreviewTime: 0,
      /* true if the user is currently dragging the mouse
       * to seek a new track position
       */
      seekInProgress: false,
      /* true if audio was playing when seek previewing began,
       * it was paused, and it should be resumed on seek
       * complete
       */
      awaitingResumeOnSeekComplete: false,
      // the latest volume of the audio, between 0 and 1.
      volume: 1.0,
      // true if the audio has been muted
      muted: false,
      // the duration in seconds of the loaded track
      duration: 0,
      /* the TimeRanges object representing the buffered sections of the
       * loaded track
       */
      buffered: null,
      /* the TimeRanges object representing the played sections of the
       * loaded track
       */
      played: null,
      // whether to loop the current track
      loop: false,
      // Rate at which audio should be played. 1.0 is normal speed.
      playbackRate: 1.0
    };

    this.state = this.defaultState;

    // index matching requested track (whether track has loaded or not)
    this.currentTrackIndex = 0;

    // html audio element used for playback
    this.audio = null;

    // bind methods fired on React events
    this.togglePause = this.togglePause.bind(this);
    this.skipToNextTrack = this.skipToNextTrack.bind(this);
    this.backSkip = this.backSkip.bind(this);
    this.seekPreview = this.seekPreview.bind(this);
    this.seekComplete = this.seekComplete.bind(this);

    // bind audio event listeners to add on mount and remove on unmount
    this.handleAudioPlay = this.handleAudioPlay.bind(this);
    this.handleAudioPause = this.handleAudioPause.bind(this);
    this.handleAudioEnded = this.handleAudioEnded.bind(this);
    this.handleAudioStalled = this.handleAudioStalled.bind(this);
    this.handleAudioTimeupdate = this.handleAudioTimeupdate.bind(this);
    this.handleAudioLoadedmetadata = this.handleAudioLoadedmetadata.bind(this);
    this.handleAudioVolumechange = this.handleAudioVolumechange.bind(this);
    this.handleAudioDurationchange = this.handleAudioDurationchange.bind(this);
    this.handleAudioProgress = this.handleAudioProgress.bind(this);
    this.handleAudioLoopchange = this.handleAudioLoopchange.bind(this);
    this.handleAudioRatechange = this.handleAudioRatechange.bind(this);
  }

  componentDidMount () {
    const audio = this.audio = createAudioElementWithLoopEvent();

    // initialize audio properties
    audio.volume = this.state.volume;
    audio.muted = this.state.muted;
    audio.loop = this.state.loop;
    audio.playbackRate = this.state.playbackRate;

    // add event listeners on the audio element
    audio.preload = 'metadata';
    audio.addEventListener('play', this.handleAudioPlay);
    audio.addEventListener('pause', this.handleAudioPause);
    audio.addEventListener('ended', this.handleAudioEnded);
    audio.addEventListener('stalled', this.handleAudioStalled);
    audio.addEventListener('timeupdate', this.handleAudioTimeupdate);
    audio.addEventListener('loadedmetadata', this.handleAudioLoadedmetadata);
    audio.addEventListener('volumechange', this.handleAudioVolumechange);
    audio.addEventListener('durationchange', this.handleAudioDurationchange);
    audio.addEventListener('progress', this.handleAudioProgress);
    audio.addEventListener('loopchange', this.handleAudioLoopchange);
    audio.addEventListener('ratechange', this.handleAudioRatechange);
    this.addMediaEventListeners(this.props.onMediaEvent);

    if (this.props.playlist && this.props.playlist.length) {
      this.updateSource();
      if (this.props.autoplay) {
        clearTimeout(this.delayTimeout);
        this.delayTimeout = setTimeout(() => {
          this.togglePause(false);
        }, this.props.autoplayDelayInSeconds * 1000);
      }
    }

    if (this.props.audioElementRef) {
      this.props.audioElementRef(audio);
    }
  }

  componentWillReceiveProps (nextProps) {
    // Update media event listeners that may have changed
    this.removeMediaEventListeners(this.props.onMediaEvent);
    this.addMediaEventListeners(nextProps.onMediaEvent);

    const newPlaylist = nextProps.playlist;
    if (!newPlaylist || !newPlaylist.length) {
      this.audio.src = '';
      this.currentTrackIndex = 0;
      return this.setState(this.defaultState);
    }

    const oldPlaylist = this.props.playlist;

    const currentTrackUrl = ((oldPlaylist || [])[this.currentTrackIndex] || {}).url;
    this.currentTrackIndex = arrayFindIndex(newPlaylist, track => {
      return track.url && currentTrackUrl === track.url;
    });
    /* if the track we're already playing is in the new playlist, update the
     * activeTrackIndex.
     */
    if (this.currentTrackIndex !== -1) {
      this.setState({
        activeTrackIndex: this.currentTrackIndex
      });
    }
  }

  componentDidUpdate () {
    /* if we loaded a new playlist and reset the current track marker, we
     * should load up the first one.
     */
    if (this.currentTrackIndex === -1) {
      this.skipToNextTrack(false);
    }
  }

  componentWillUnmount () {
    const { audio } = this;
    // remove event listeners on the audio element
    audio.removeEventListener('play', this.handleAudioPlay);
    audio.removeEventListener('pause', this.handleAudioPause);
    audio.removeEventListener('ended', this.handleAudioEnded);
    audio.removeEventListener('stalled', this.handleAudioStalled);
    audio.removeEventListener('timeupdate', this.handleAudioTimeupdate);
    audio.removeEventListener('loadedmetadata', this.handleAudioLoadedmetadata);
    audio.removeEventListener('volumechange', this.handleAudioVolumechange);
    audio.removeEventListener('durationchange', this.handleAudioDurationchange);
    audio.removeEventListener('progress', this.handleAudioProgress);
    audio.removeEventListener('loopchange', this.handleAudioLoopchange);
    audio.removeEventListener('ratechange', this.handleAudioRatechange);
    removeMediaEventListeners(this.props.onMediaEvent);

    clearTimeout(this.gapLengthTimeout);
    clearTimeout(this.delayTimeout);

    // pause the audio element before we unmount
    audio.pause();

    if (this.props.audioElementRef) {
      this.props.audioElementRef(audio);
    }
  }

  addMediaEventListeners (mediaEvents) {
    if (!mediaEvents) {
      return;
    }
    Object.keys(mediaEvents).forEach((type) => {
      if (typeof mediaEvents[type] !== 'function') {
        return;
      }
      this.audio.addEventListener(type, mediaEvents[type]);
    });
  }

  removeMediaEventListeners (mediaEvents) {
    if (!mediaEvents) {
      return;
    }
    Object.keys(mediaEvents).forEach((type) => {
      if (typeof mediaEvents[type] !== 'function') {
        return;
      }
      this.audio.removeEventListener(type, mediaEvents[type]);
    });
  }

  handleAudioPlay () {
    this.setState({ paused: false });
  }

  handleAudioPause () {
    this.setState({ paused: true });
  }

  handleAudioEnded () {
    clearTimeout(this.gapLengthTimeout);
    this.gapLengthTimeout = setTimeout(
      this.skipToNextTrack,
      this.props.gapLengthInSeconds * 1000
    );
  }

  handleAudioStalled () {
    this.togglePause(true);
  }

  handleAudioTimeupdate () {
    const { currentTime, played } = this.audio;
    this.setState({ currentTime, played });
  }

  handleAudioLoadedmetadata () {
    this.setState({
      activeTrackIndex: this.currentTrackIndex
    });
  }

  handleAudioVolumechange () {
    const { volume, muted } = this.audio;
    this.setState({ volume, muted });
  }

  handleAudioDurationchange () {
    const { duration } = this.audio;
    this.setState({ duration });
  }

  handleAudioProgress () {
    const { buffered } = this.audio;
    this.setState({ buffered });
  }

  handleAudioLoopchange () {
    const { loop } = this.audio;
    this.setState({ loop });
  }

  handleAudioRatechange () {
    const { playbackRate } = this.audio;
    this.setState({ playbackRate });
  }

  updateSource () {
    this.audio.src = this.props.playlist[this.currentTrackIndex].url;
  }

  togglePause (value) {
    const pause = typeof value === 'boolean' ? value : !this.state.paused;
    if (pause) {
      this.audio.pause();
      return;
    }
    const { playlist } = this.props;
    if (!playlist || !playlist.length) {
      return;
    }
    try {
      this.audio.play();
    } catch (error) {
      logError(error);
      const warningMessage =
        'Audio playback failed at ' +
        new Date().toLocaleTimeString() +
        '! (Perhaps autoplay is disabled in this browser.)';
      logWarning(warningMessage);
    }
  }

  skipToNextTrack (shouldPlay) {
    this.audio.pause();
    const { playlist, cycle } = this.props;
    if (!playlist || !playlist.length) {
      return;
    }
    let i = this.currentTrackIndex + 1;
    if (i >= playlist.length) {
      i = 0;
    }
    this.currentTrackIndex = i;
    this.setState({
      activeTrackIndex: -1,
      currentTime: 0
    }, () => {
      this.updateSource();
      const shouldPauseOnCycle = (!cycle && i === 0);
      const shouldPause = shouldPauseOnCycle || (typeof shouldPlay === 'boolean' ? !shouldPlay : false);
      this.togglePause(shouldPause);
    });
  }

  backSkip () {
    const { playlist, stayOnBackSkipThreshold } = this.props;
    const { audio } = this;
    if (!playlist || !playlist.length) {
      return;
    }
    if (audio.currentTime >= stayOnBackSkipThreshold) {
      return audio.currentTime = 0;
    }
    let i = this.currentTrackIndex - 1;
    if (i < 0) {
      i = playlist.length - 1;
    }
    this.currentTrackIndex = i - 1;
    this.skipToNextTrack();
  }

  seekPreview (progress) {
    const { paused, awaitingResumeOnSeekComplete } = this.state;
    if (this.isSeekUnavailable()) {
      return;
    }
    if (!paused && this.props.pauseOnSeekPreview && !awaitingResumeOnSeekComplete) {
      this.setState({
        awaitingResumeOnSeekComplete: true
      });
      this.togglePause(true);
    }
    const progressInBounds = Math.max(0, Math.min(progress, 1));
    this.setState({
      seekPreviewTime: progressInBounds * this.audio.duration,
      seekInProgress: true
    });
  }

  seekComplete () {
    const { seekPreviewTime, awaitingResumeOnSeekComplete } = this.state;
    this.setState({
      seekInProgress: false,
    });
    if (isNaN(seekPreviewTime)) {
      return;
    }
    this.setState({
      /* we'll update currentTime on the audio listener hook anyway,
       * but the optimistic update helps avoid a visual glitch in
       * the progress bar, if seekInProgress changes before currentTime.
       */
      currentTime: seekPreviewTime
    });
    const { audio } = this;
    audio.currentTime = seekPreviewTime;
    if (awaitingResumeOnSeekComplete) {
      audio.play();
      this.setState({
        awaitingResumeOnSeekComplete: false
      });
    }
  }

  isSeekUnavailable () {
    return Boolean(
      !this.props.playlist ||
      !this.props.playlist.length ||
      this.props.disableSeek
    );
  }

  render () {
    const { playlist, controls, style } = this.props;
    return (
      <div
        className="audio_player"
        title={getDisplayText(playlist, this.state.activeTrackIndex)}
        style={style}
      >
        {controls.map((control, index) => {
          const ControlComponent = getControlComponent(control);
          if (!ControlComponent) {
            return null;
          }
          return (
            <ControlComponent
              key={index}
              playlist={playlist}
              seekUnavailable={this.isSeekUnavailable()}
              onTogglePause={this.togglePause}
              onBackSkip={this.backSkip}
              onForwardSkip={this.skipToNextTrack}
              onSeekPreview={this.seekPreview}
              onSeekComplete={this.seekComplete}
              {...this.state}
            />
          );
        })}
      </div>
    );
  }

}

AudioPlayer.propTypes = {
  playlist: PropTypes.arrayOf(PropTypes.shape({
    url: PropTypes.string.isRequired,
    displayText: PropTypes.string.isRequired
  }).isRequired),
  controls: PropTypes.arrayOf(PropTypes.oneOfType([
    PropTypes.element,
    PropTypes.oneOf([
      'playpause',
      'backskip',
      'forwardskip',
      'progress',
      'spacer'
    ])
  ]).isRequired),
  autoplay: PropTypes.bool,
  autoplayDelayInSeconds: PropTypes.number,
  gapLengthInSeconds: PropTypes.number,
  cycle: PropTypes.bool,
  pauseOnSeekPreview: PropTypes.bool,
  disableSeek: PropTypes.bool,
  stayOnBackSkipThreshold: PropTypes.number,
  style: PropTypes.object,
  onMediaEvent: PropTypes.objectOf(PropTypes.func.isRequired),
  audioElementRef: PropTypes.func
};

AudioPlayer.defaultProps = {
  controls: [
    'spacer',
    'backskip',
    'playpause',
    'forwardskip',
    'spacer',
    'progress'
  ],
  autoplay: false,
  autoplayDelayInSeconds: 0,
  gapLengthInSeconds: 0,
  cycle: true,
  pauseOnSeekPreview: false,
  disableSeek: false,
  stayOnBackSkipThreshold: 5
};

module.exports = AudioPlayer;
<commit_msg>seekUnavailable() and play ability depend on audio src, not playlist.<commit_after>import React, { Component, PropTypes } from 'react';
import arrayFindIndex from 'array-find-index';
import classNames from 'classnames';

import createAudioElementWithLoopEvent from './createAudioElementWithLoopEvent';
import getDisplayText from './utils/getDisplayText';
import getControlComponent from './utils/getControlComponent';

import './index.scss';

const log = console.log.bind(console);
const logError = console.error ? console.error.bind(console) : log;
const logWarning = console.warn ? console.warn.bind(console) : log;

/*
 * AudioPlayer
 *
 * Accepts 'playlist' prop of the form:
 *
 * [{ "url": "./path/to/file.mp3",
 *    "displayText": "ArtistA - Track 1" },
 *  { "url": "https://domain.com/track2.ogg",
 *    "displayText": "ArtistB - Track 2" }]
 *
 * Accepts 'autoplay' prop (true/[false]).
 *
 * Accepts 'autoplayDelayInSeconds' prop (default 0).
 *
 * Accepts 'gapLengthInSeconds' prop (default 0).
 * Specifies gap at end of one track before next
 * track begins (ignored for manual skip).
 *
 * Accepts 'cycle' prop (default true,
 * starts playing at the beginning of the playlist
 * when finished if true).
 *
 * Accepts 'pauseOnSeekPreview' prop (default true,
 * pauses audio while user is selecting new time
 * for playback)
 *
 * Accepts 'disableSeek' prop (default false,
 * disables seeking through the audio if true).
 *
 * Accepts 'stayOnBackSkipThreshold' prop, default 5,
 * is number of seconds to progress until pressing back skip
 * restarts the current track.
 *
 * Accepts 'style' prop, object, is applied to
 * outermost div (React styles).
 *
 * Accepts 'onMediaEvent' prop, an object used for
 * listening to media events on the underlying audio element.
 *
 * Accepts 'audioElementRef' prop, a function called after
 * the component mounts and before it unmounts with the
 * internally-referenced HTML audio element as its only parameter.
 * Similar to: https://facebook.github.io/react/docs/refs-and-the-dom.html
 */
class AudioPlayer extends Component {

  constructor (props) {
    super(props);

    this.defaultState = {
      /* activeTrackIndex will change to match
       * this.currentTrackIndex once metadata has loaded
       */
      activeTrackIndex: -1,
      // indicates whether audio player should be paused
      paused: true,
      // elapsed time for current track, in seconds
      currentTime: 0,
      // The most recent targeted time, in seconds, for seek preview
      seekPreviewTime: 0,
      /* true if the user is currently dragging the mouse
       * to seek a new track position
       */
      seekInProgress: false,
      /* true if audio was playing when seek previewing began,
       * it was paused, and it should be resumed on seek
       * complete
       */
      awaitingResumeOnSeekComplete: false,
      // the latest volume of the audio, between 0 and 1.
      volume: 1.0,
      // true if the audio has been muted
      muted: false,
      // the duration in seconds of the loaded track
      duration: 0,
      /* the TimeRanges object representing the buffered sections of the
       * loaded track
       */
      buffered: null,
      /* the TimeRanges object representing the played sections of the
       * loaded track
       */
      played: null,
      // whether to loop the current track
      loop: false,
      // Rate at which audio should be played. 1.0 is normal speed.
      playbackRate: 1.0
    };

    this.state = this.defaultState;

    // index matching requested track (whether track has loaded or not)
    this.currentTrackIndex = 0;

    // html audio element used for playback
    this.audio = null;

    // bind methods fired on React events
    this.togglePause = this.togglePause.bind(this);
    this.skipToNextTrack = this.skipToNextTrack.bind(this);
    this.backSkip = this.backSkip.bind(this);
    this.seekPreview = this.seekPreview.bind(this);
    this.seekComplete = this.seekComplete.bind(this);

    // bind audio event listeners to add on mount and remove on unmount
    this.handleAudioPlay = this.handleAudioPlay.bind(this);
    this.handleAudioPause = this.handleAudioPause.bind(this);
    this.handleAudioEnded = this.handleAudioEnded.bind(this);
    this.handleAudioStalled = this.handleAudioStalled.bind(this);
    this.handleAudioTimeupdate = this.handleAudioTimeupdate.bind(this);
    this.handleAudioLoadedmetadata = this.handleAudioLoadedmetadata.bind(this);
    this.handleAudioVolumechange = this.handleAudioVolumechange.bind(this);
    this.handleAudioDurationchange = this.handleAudioDurationchange.bind(this);
    this.handleAudioProgress = this.handleAudioProgress.bind(this);
    this.handleAudioLoopchange = this.handleAudioLoopchange.bind(this);
    this.handleAudioRatechange = this.handleAudioRatechange.bind(this);
  }

  componentDidMount () {
    const audio = this.audio = createAudioElementWithLoopEvent();

    // initialize audio properties
    audio.volume = this.state.volume;
    audio.muted = this.state.muted;
    audio.loop = this.state.loop;
    audio.playbackRate = this.state.playbackRate;

    // add event listeners on the audio element
    audio.preload = 'metadata';
    audio.addEventListener('play', this.handleAudioPlay);
    audio.addEventListener('pause', this.handleAudioPause);
    audio.addEventListener('ended', this.handleAudioEnded);
    audio.addEventListener('stalled', this.handleAudioStalled);
    audio.addEventListener('timeupdate', this.handleAudioTimeupdate);
    audio.addEventListener('loadedmetadata', this.handleAudioLoadedmetadata);
    audio.addEventListener('volumechange', this.handleAudioVolumechange);
    audio.addEventListener('durationchange', this.handleAudioDurationchange);
    audio.addEventListener('progress', this.handleAudioProgress);
    audio.addEventListener('loopchange', this.handleAudioLoopchange);
    audio.addEventListener('ratechange', this.handleAudioRatechange);
    this.addMediaEventListeners(this.props.onMediaEvent);

    if (this.props.playlist && this.props.playlist.length) {
      this.updateSource();
      if (this.props.autoplay) {
        clearTimeout(this.delayTimeout);
        this.delayTimeout = setTimeout(() => {
          this.togglePause(false);
        }, this.props.autoplayDelayInSeconds * 1000);
      }
    }

    if (this.props.audioElementRef) {
      this.props.audioElementRef(audio);
    }
  }

  componentWillReceiveProps (nextProps) {
    // Update media event listeners that may have changed
    this.removeMediaEventListeners(this.props.onMediaEvent);
    this.addMediaEventListeners(nextProps.onMediaEvent);

    const newPlaylist = nextProps.playlist;
    if (!newPlaylist || !newPlaylist.length) {
      this.audio.src = '';
      this.currentTrackIndex = 0;
      return this.setState(this.defaultState);
    }

    const oldPlaylist = this.props.playlist;

    const currentTrackUrl = ((oldPlaylist || [])[this.currentTrackIndex] || {}).url;
    this.currentTrackIndex = arrayFindIndex(newPlaylist, track => {
      return track.url && currentTrackUrl === track.url;
    });
    /* if the track we're already playing is in the new playlist, update the
     * activeTrackIndex.
     */
    if (this.currentTrackIndex !== -1) {
      this.setState({
        activeTrackIndex: this.currentTrackIndex
      });
    }
  }

  componentDidUpdate () {
    /* if we loaded a new playlist and reset the current track marker, we
     * should load up the first one.
     */
    if (this.currentTrackIndex === -1) {
      this.skipToNextTrack(false);
    }
  }

  componentWillUnmount () {
    const { audio } = this;
    // remove event listeners on the audio element
    audio.removeEventListener('play', this.handleAudioPlay);
    audio.removeEventListener('pause', this.handleAudioPause);
    audio.removeEventListener('ended', this.handleAudioEnded);
    audio.removeEventListener('stalled', this.handleAudioStalled);
    audio.removeEventListener('timeupdate', this.handleAudioTimeupdate);
    audio.removeEventListener('loadedmetadata', this.handleAudioLoadedmetadata);
    audio.removeEventListener('volumechange', this.handleAudioVolumechange);
    audio.removeEventListener('durationchange', this.handleAudioDurationchange);
    audio.removeEventListener('progress', this.handleAudioProgress);
    audio.removeEventListener('loopchange', this.handleAudioLoopchange);
    audio.removeEventListener('ratechange', this.handleAudioRatechange);
    removeMediaEventListeners(this.props.onMediaEvent);

    clearTimeout(this.gapLengthTimeout);
    clearTimeout(this.delayTimeout);

    // pause the audio element before we unmount
    audio.pause();

    if (this.props.audioElementRef) {
      this.props.audioElementRef(audio);
    }
  }

  addMediaEventListeners (mediaEvents) {
    if (!mediaEvents) {
      return;
    }
    Object.keys(mediaEvents).forEach((type) => {
      if (typeof mediaEvents[type] !== 'function') {
        return;
      }
      this.audio.addEventListener(type, mediaEvents[type]);
    });
  }

  removeMediaEventListeners (mediaEvents) {
    if (!mediaEvents) {
      return;
    }
    Object.keys(mediaEvents).forEach((type) => {
      if (typeof mediaEvents[type] !== 'function') {
        return;
      }
      this.audio.removeEventListener(type, mediaEvents[type]);
    });
  }

  handleAudioPlay () {
    this.setState({ paused: false });
  }

  handleAudioPause () {
    this.setState({ paused: true });
  }

  handleAudioEnded () {
    clearTimeout(this.gapLengthTimeout);
    this.gapLengthTimeout = setTimeout(
      this.skipToNextTrack,
      this.props.gapLengthInSeconds * 1000
    );
  }

  handleAudioStalled () {
    this.togglePause(true);
  }

  handleAudioTimeupdate () {
    const { currentTime, played } = this.audio;
    this.setState({ currentTime, played });
  }

  handleAudioLoadedmetadata () {
    this.setState({
      activeTrackIndex: this.currentTrackIndex
    });
  }

  handleAudioVolumechange () {
    const { volume, muted } = this.audio;
    this.setState({ volume, muted });
  }

  handleAudioDurationchange () {
    const { duration } = this.audio;
    this.setState({ duration });
  }

  handleAudioProgress () {
    const { buffered } = this.audio;
    this.setState({ buffered });
  }

  handleAudioLoopchange () {
    const { loop } = this.audio;
    this.setState({ loop });
  }

  handleAudioRatechange () {
    const { playbackRate } = this.audio;
    this.setState({ playbackRate });
  }

  updateSource () {
    this.audio.src = this.props.playlist[this.currentTrackIndex].url;
  }

  togglePause (value) {
    const pause = typeof value === 'boolean' ? value : !this.state.paused;
    if (pause) {
      this.audio.pause();
      return;
    }
    if (!this.audio.src) {
      return;
    }
    try {
      this.audio.play();
    } catch (error) {
      logError(error);
      const warningMessage =
        'Audio playback failed at ' +
        new Date().toLocaleTimeString() +
        '! (Perhaps autoplay is disabled in this browser.)';
      logWarning(warningMessage);
    }
  }

  skipToNextTrack (shouldPlay) {
    this.audio.pause();
    const { playlist, cycle } = this.props;
    if (!playlist || !playlist.length) {
      return;
    }
    let i = this.currentTrackIndex + 1;
    if (i >= playlist.length) {
      i = 0;
    }
    this.currentTrackIndex = i;
    this.setState({
      activeTrackIndex: -1,
      currentTime: 0
    }, () => {
      this.updateSource();
      const shouldPauseOnCycle = (!cycle && i === 0);
      const shouldPause = shouldPauseOnCycle || (typeof shouldPlay === 'boolean' ? !shouldPlay : false);
      this.togglePause(shouldPause);
    });
  }

  backSkip () {
    const { playlist, stayOnBackSkipThreshold } = this.props;
    const { audio } = this;
    if (!playlist || !playlist.length) {
      return;
    }
    if (audio.currentTime >= stayOnBackSkipThreshold) {
      return audio.currentTime = 0;
    }
    let i = this.currentTrackIndex - 1;
    if (i < 0) {
      i = playlist.length - 1;
    }
    this.currentTrackIndex = i - 1;
    this.skipToNextTrack();
  }

  seekPreview (progress) {
    const { paused, awaitingResumeOnSeekComplete } = this.state;
    if (this.isSeekUnavailable()) {
      return;
    }
    if (!paused && this.props.pauseOnSeekPreview && !awaitingResumeOnSeekComplete) {
      this.setState({
        awaitingResumeOnSeekComplete: true
      });
      this.togglePause(true);
    }
    const progressInBounds = Math.max(0, Math.min(progress, 1));
    this.setState({
      seekPreviewTime: progressInBounds * this.audio.duration,
      seekInProgress: true
    });
  }

  seekComplete () {
    const { seekPreviewTime, awaitingResumeOnSeekComplete } = this.state;
    this.setState({
      seekInProgress: false,
    });
    if (isNaN(seekPreviewTime)) {
      return;
    }
    this.setState({
      /* we'll update currentTime on the audio listener hook anyway,
       * but the optimistic update helps avoid a visual glitch in
       * the progress bar, if seekInProgress changes before currentTime.
       */
      currentTime: seekPreviewTime
    });
    const { audio } = this;
    audio.currentTime = seekPreviewTime;
    if (awaitingResumeOnSeekComplete) {
      audio.play();
      this.setState({
        awaitingResumeOnSeekComplete: false
      });
    }
  }

  isSeekUnavailable () {
    const { audio } = this;
    return Boolean(!(audio && audio.src) || this.props.disableSeek);
  }

  render () {
    const { playlist, controls, style } = this.props;
    return (
      <div
        className="audio_player"
        title={getDisplayText(playlist, this.state.activeTrackIndex)}
        style={style}
      >
        {controls.map((control, index) => {
          const ControlComponent = getControlComponent(control);
          if (!ControlComponent) {
            return null;
          }
          return (
            <ControlComponent
              key={index}
              playlist={playlist}
              seekUnavailable={this.isSeekUnavailable()}
              onTogglePause={this.togglePause}
              onBackSkip={this.backSkip}
              onForwardSkip={this.skipToNextTrack}
              onSeekPreview={this.seekPreview}
              onSeekComplete={this.seekComplete}
              {...this.state}
            />
          );
        })}
      </div>
    );
  }

}

AudioPlayer.propTypes = {
  playlist: PropTypes.arrayOf(PropTypes.shape({
    url: PropTypes.string.isRequired,
    displayText: PropTypes.string.isRequired
  }).isRequired),
  controls: PropTypes.arrayOf(PropTypes.oneOfType([
    PropTypes.element,
    PropTypes.oneOf([
      'playpause',
      'backskip',
      'forwardskip',
      'progress',
      'spacer'
    ])
  ]).isRequired),
  autoplay: PropTypes.bool,
  autoplayDelayInSeconds: PropTypes.number,
  gapLengthInSeconds: PropTypes.number,
  cycle: PropTypes.bool,
  pauseOnSeekPreview: PropTypes.bool,
  disableSeek: PropTypes.bool,
  stayOnBackSkipThreshold: PropTypes.number,
  style: PropTypes.object,
  onMediaEvent: PropTypes.objectOf(PropTypes.func.isRequired),
  audioElementRef: PropTypes.func
};

AudioPlayer.defaultProps = {
  controls: [
    'spacer',
    'backskip',
    'playpause',
    'forwardskip',
    'spacer',
    'progress'
  ],
  autoplay: false,
  autoplayDelayInSeconds: 0,
  gapLengthInSeconds: 0,
  cycle: true,
  pauseOnSeekPreview: false,
  disableSeek: false,
  stayOnBackSkipThreshold: 5
};

module.exports = AudioPlayer;
<|endoftext|>


<commit_before>import Selection from "./Selection.js";

let modelist = ace.require("ace/ext/modelist");

export default class BasicTextEditor
{
  constructor(element, session)
  {
    this.element = element;
    this.element.className = "ace-editor-container"

    this.session = session;

    this.aceEditor = this.createEditor();

    this.selections = new Map();

    this.infoBar = this.createInfoBar();
    this.selectionInfo = this.createSelectionInfo();
    this.updateSelectionInfo();

    this.selection = this.aceEditor.getSelection();
    this.selection.on("changeCursor", () => this.updateSelectionInfo());
    this.selection.on("changeCursor", () => this.updateSelections());
    this.selection.on("changeSelection", () => this.updateSelections());
    this.updateSelections();
  }

  createEditor()
  {
    let editorElement = document.createElement("div");

    // create text editor
    let aceEditor = ace.edit(editorElement);
    aceEditor.$blockScrolling = Infinity;
    aceEditor.session.selection.clearSelection();

    let settings = this.session.settingsMenu.sections["Text Editor"];
    settings.subscribeEditor(aceEditor);

    this.element.appendChild(editorElement);

    return aceEditor;
  }

  createInfoBar()
  {
    let bar = document.createElement("div");
    bar.className = "bottom-bar";

    this.element.appendChild(bar);

    return bar;
  }

  createSelectionInfo()
  {
    let selectionInfoElement = document.createElement("span");
    selectionInfoElement.className = "selection-info";

    this.infoBar.appendChild(selectionInfoElement);

    return selectionInfoElement;
  }

  setSelections(userId, ranges)
  {
    this.removeSelections(userId);

    let selections = [];

    for(let range of ranges) {
      let selection = new Selection(userId, range, this.aceEditor, this.session);

      if(userId == this.session.id && range.isEmpty())
        selection.enableBlink();

      selections.push(selection);
    }

    this.selections.set(userId, selections);
  }

  removeSelections(userId) {
    let selections = this.selections.get(userId);

    if(!selections)
      return;

    for(let selection of selections) {
      selection.destroy();
    }

    this.selections.delete(userId);
  }

  updateSelections()
  {
    let ranges = this.selection.getAllRanges();

    this.setSelections(this.session.id, ranges);
  }

  updateHighlighter(path)
  {
    let mode = modelist.getModeForPath(path).mode;
    this.aceEditor.session.setMode(mode);
  }

  updateSelectionInfo()
  {
    let pos = this.aceEditor.getCursorPosition();
    this.selectionInfo.innerText = `Ln ${pos.row + 1}, Col ${pos.column + 1}`;
  }

  clearHistory()
  {
    this.aceEditor.session.setUndoManager(new ace.UndoManager());
  }

  destroy()
  {
    for(let [userId] of this.selections) {
      this.removeSelections(userId);
    }

    let settings = this.session.settingsMenu.sections["Text Editor"];
    settings.unsubscribeEditor(this.aceEditor);

    this.aceEditor.destroy();
  }
}
<commit_msg>Fix missing semicolon<commit_after>import Selection from "./Selection.js";

let modelist = ace.require("ace/ext/modelist");

export default class BasicTextEditor
{
  constructor(element, session)
  {
    this.element = element;
    this.element.className = "ace-editor-container";

    this.session = session;

    this.aceEditor = this.createEditor();

    this.selections = new Map();

    this.infoBar = this.createInfoBar();
    this.selectionInfo = this.createSelectionInfo();
    this.updateSelectionInfo();

    this.selection = this.aceEditor.getSelection();
    this.selection.on("changeCursor", () => this.updateSelectionInfo());
    this.selection.on("changeCursor", () => this.updateSelections());
    this.selection.on("changeSelection", () => this.updateSelections());
    this.updateSelections();
  }

  createEditor()
  {
    let editorElement = document.createElement("div");

    // create text editor
    let aceEditor = ace.edit(editorElement);
    aceEditor.$blockScrolling = Infinity;
    aceEditor.session.selection.clearSelection();

    let settings = this.session.settingsMenu.sections["Text Editor"];
    settings.subscribeEditor(aceEditor);

    this.element.appendChild(editorElement);

    return aceEditor;
  }

  createInfoBar()
  {
    let bar = document.createElement("div");
    bar.className = "bottom-bar";

    this.element.appendChild(bar);

    return bar;
  }

  createSelectionInfo()
  {
    let selectionInfoElement = document.createElement("span");
    selectionInfoElement.className = "selection-info";

    this.infoBar.appendChild(selectionInfoElement);

    return selectionInfoElement;
  }

  setSelections(userId, ranges)
  {
    this.removeSelections(userId);

    let selections = [];

    for(let range of ranges) {
      let selection = new Selection(userId, range, this.aceEditor, this.session);

      if(userId == this.session.id && range.isEmpty())
        selection.enableBlink();

      selections.push(selection);
    }

    this.selections.set(userId, selections);
  }

  removeSelections(userId) {
    let selections = this.selections.get(userId);

    if(!selections)
      return;

    for(let selection of selections) {
      selection.destroy();
    }

    this.selections.delete(userId);
  }

  updateSelections()
  {
    let ranges = this.selection.getAllRanges();

    this.setSelections(this.session.id, ranges);
  }

  updateHighlighter(path)
  {
    let mode = modelist.getModeForPath(path).mode;
    this.aceEditor.session.setMode(mode);
  }

  updateSelectionInfo()
  {
    let pos = this.aceEditor.getCursorPosition();
    this.selectionInfo.innerText = `Ln ${pos.row + 1}, Col ${pos.column + 1}`;
  }

  clearHistory()
  {
    this.aceEditor.session.setUndoManager(new ace.UndoManager());
  }

  destroy()
  {
    for(let [userId] of this.selections) {
      this.removeSelections(userId);
    }

    let settings = this.session.settingsMenu.sections["Text Editor"];
    settings.unsubscribeEditor(this.aceEditor);

    this.aceEditor.destroy();
  }
}
<|endoftext|>