<commit_before>/* Custom topojson class courtesy of Ryan Clark */
L.TopoJSON = L.GeoJSON.extend({
    addData: function(jsonData) {
        if (jsonData.type === "Topology") {
            for (var key in jsonData.objects) {
                geojson = topojson.feature(jsonData, jsonData.objects[key]);
                L.GeoJSON.prototype.addData.call(this, geojson);
            }
        } else {
            L.GeoJSON.prototype.addData.call(this, jsonData);
        }
    }
});

function initMap() {
    map.setView([51.5, -0.5], 8);
    mapLink = 'OpenStreetMap';
    // load a tile layer
    L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', {
        attribution: '© OpenStreetMap contributors © CartoDB, CartoDB attributions',
        maxZoom: 20,
        minZoom: 0
    }).addTo(map);
    $.getJSON('./data/topo_lad.json')
        .done(addTopoData);
}

function recenter() {
    map.fitBounds(topoLayer.getBounds());
}

function loadData() {
    $.ajax({
        url: "./data/lad_complexity.json",
        dataType: 'json'
    }).done(function(data) {
        complexityData = data;
        initMap();
    });
}

function addTopoData(data) {
    topoLayer.addData(data);
    topoLayer.addTo(map);
    topoLayer.eachLayer(handleLayer);
}

function handleLayer(layer) {
    var fillColor = colorScale(complexityData[layer.feature.id].value).hex();
    layer.setStyle({
        fillColor: fillColor,
        fillOpacity: 0.6,
        weight: 0.5,
        opacity: 1,
        color: 'white',
        dashArray: '1',
        smoothFactor: 0.5
    });

    layer.on({
        mouseover: enterLayer,
        mouseout: leaveLayer,

    });
}

function enterLayer() {
    this.bringToFront();
    this.setStyle({
        weight: 3,
        opacity: 1,
        fillOpacity: 1,
        color: '#666666'
    });
}

function leaveLayer() {
    this.bringToBack();
    this.setStyle({
        weight: 1,
        opacity: 0.5,
        fillOpacity: 0.7,
        color: 'white'
    });
}

// Initialise global map and layer variables
var map = new L.map('map');
var topoLayer = new L.TopoJSON();
var colorScale = chroma
    .scale(chroma.brewer.YlOrRd)
    .domain([-2.1227372118474177, 2.397453389303414]);

/* Dynamic resizing */
$(window).on("resize", function() {
    var h = $(window).height();
    $("#map").height(h - (h * 0.2));
    map.invalidateSize();
}).trigger("resize");

loadData();

/*--------------------------------------------------------------*/
/* Charts */

var chart;

function currentSelectedIndustry() {
    return $("#indList").val();
}

function parseData(data, prob, prod_complexity) {
    r = [];
    for (var key in data.S12000033) {
        if (key !== "growth" && key !== "LAD14NM") {
            r.push({
                x: [data.S12000033[key]],
                y: [prob.S12000033[key]],
                mode: 'markers',
                marker: {
                    sizemode: 'area',
                    size: 20,
                    sizeref: '2em',
                    colorscale: 'YlOrRd',
                    cmin: -3,
                    cmax: 3,
                    color: [prod_complexity[key].score],
                    colorbar: {
                        titleside: 'right',
                        outlinecolor: 'rgba(68,68,68,0)',
                        ticks: 'none',
                        ticklen: 0,
                        tickfont: 'Arial',
                        fontsize: 4
                    }
                },
            });
        }
    }
    return r;
}

function analyze(error, business_data, prod_complexity, prob) {
    if (error) {
        console.log(error);
    }

    /*business_data.forEach(function(d) {
        d.growth = +d.growth;
        d.construction = +d.construction;
        d.manufacture_advanced = +d.manufacture_advanced;
        d.manufacture_agrochemical = +d.manufacture_agrochemical;
        d.manufacture_automotive = +d.manufacture_automotive;
        d.manufacture_chemical_pharma = +d.manufacture_chemical_pharma;
        d.manufacture_construction = +d.manufacture_construction;
        d.manufacture_electrical = +d.manufacture_electrical;
        d.manufacture_food = +d.manufacture_food;
        d.manufacture_food_2 = +d.manufacture_food_2;
        d.manufacture_footwear_leather = +d.manufacture_footwear_leather;
        d.manufacture_furniture = +d.manufacture_furniture;
        d.manufacture_light_misc = +d.manufacture_light_misc;
        d.manufacture_machinery = +d.manufacture_machinery;
        d.manufacture_metallurgy = +d.manufacture_metallurgy;
        d.manufacture_metallurgy_misc = +d.manufacture_metallurgy;
        d.manufacture_metallurgy_tools = +d.manufacture_metallurgy_tools;
        d.manufacture_optics = +d.manufacture_optics;
        d.manufacture_paper = +d.manufacture_paper;
        d.manufacture_plastic = +d.manufacture_plastic;
        d.manufacture_rubber = +d.manufacture_rubber;
        d.manufacture_shipbuilding = +d.manufacture_shipbuilding;
        d.manufacture_textiles = +d.manufacture_textiles;
        d.manufacturing_aerospace = +d.manufacturing_aerospace;
        d.manufacturing_misc = +d.manufacturing_misc;
        d.mining_mining = +d.mining_mining;
        d.mining_oil_gas = +d.mining_oil_gas;
        d.primary_farming = +d.primary_farming;
        d.primary_fishing = +d.primary_fishing;
        d.primary_forestry = +d.primary_forestry;
        d.primary_livestock = +d.primary_livestock;
        d.services_agriculture_rural = +d.services_agriculture_rural;
        d.services_automotive = +d.services_automotive;
        d.services_business_support = +d.services_business_support;
        d.services_cleaning_facilities = +d.services_cleaning_facilities;
        d.services_computing = +d.services_computing;
        d.services_consumer_retail = +d.services_consumer_retail;
        d.services_creative = +d.services_creative;
        d.services_creative_content = +d.services_creative_content;
        d.services_cultural = +d.services_cultural;
        d.services_education = +d.services_education;
        d.services_energy = +d.services_energy;
        d.services_finance_insurance = +d.services_finance_insurance;
        d.services_finance_real_state = +d.services_finance_real_state;
        d.services_freight_transport = +d.services_freight_transport;
        d.services_head_offices = +d.services_head_offices;
        d.services_health = +d.services_health;
        d.services_human_resources = +d.services_human_resources;
        d.services_kibs = +d.services_kibs;
        d.services_leisure = +d.services_leisure;
        d.services_professional_public = +d.services_professional_public;
        d.services_publishing = +d.services_publishing;
        d.services_r_and_d = +d.services_r_and_d;
        d.services_real_estate = +d.services_real_estate;
        d.services_repair_transport = +d.services_repair_transport;
        d.services_residential_social_work = +d.services_residential_social_work;
        d.services_retail_misc = +d.services_retail_misc;
        d.services_technical_engineering = +d.services_technical_engineering;
        d.services_telecommunications = +d.services_telecommunications;
        d.services_transport_storage = +d.services_transport_storage;
        d.services_travelling_tourism = +d.services_travelling_tourism;
        d.services_urban_transport = +d.services_urban_transport;
        d.services_waste_recycling = +d.services_waste_recycling;
        d.wholesale_repair_machinery = +d.wholesale_repair_machinery;
        d.wholesale_retail_misc = +d.wholesale_retail_misc;
    });*/

        var data = parseData(business_data, prob, prod_complexity);

        var layout = {
            showlegend: false,
            xaxis: {
                title: 'Number of Businesses - 2015',
                type: 'log',
                autorange: true
            },
            yaxis: {
                title: 'Predicted Business Growth',
            },
            margin: {
                t: 10
            },
            hovermode: 'closest',
        };
        Plotly.newPlot('chart', data, layout, {
            showLink: false
        });
}

queue()
    .defer(d3.json, "/data/business_data.json")
    .defer(d3.json, "/data/product_complexity_2015.json")
    .defer(d3.json, "/data/probabilities.json")
    .await(analyze);
<commit_msg>Change array construction to work better with marker customisation<commit_after>/* Custom topojson class courtesy of Ryan Clark */
L.TopoJSON = L.GeoJSON.extend({
    addData: function(jsonData) {
        if (jsonData.type === "Topology") {
            for (var key in jsonData.objects) {
                geojson = topojson.feature(jsonData, jsonData.objects[key]);
                L.GeoJSON.prototype.addData.call(this, geojson);
            }
        } else {
            L.GeoJSON.prototype.addData.call(this, jsonData);
        }
    }
});

function initMap() {
    map.setView([51.5, -0.5], 8);
    mapLink = 'OpenStreetMap';
    // load a tile layer
    L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', {
        attribution: '© OpenStreetMap contributors © CartoDB, CartoDB attributions',
        maxZoom: 20,
        minZoom: 0
    }).addTo(map);
    $.getJSON('./data/topo_lad.json')
        .done(addTopoData);
}

function recenter() {
    map.fitBounds(topoLayer.getBounds());
}

function loadData() {
    $.ajax({
        url: "./data/lad_complexity.json",
        dataType: 'json'
    }).done(function(data) {
        complexityData = data;
        initMap();
    });
}

function addTopoData(data) {
    topoLayer.addData(data);
    topoLayer.addTo(map);
    topoLayer.eachLayer(handleLayer);
}

function handleLayer(layer) {
    var fillColor = colorScale(complexityData[layer.feature.id].value).hex();
    layer.setStyle({
        fillColor: fillColor,
        fillOpacity: 0.6,
        weight: 0.5,
        opacity: 1,
        color: 'white',
        dashArray: '1',
        smoothFactor: 0.5
    });

    layer.on({
        mouseover: enterLayer,
        mouseout: leaveLayer,

    });
}

function enterLayer() {
    this.bringToFront();
    this.setStyle({
        weight: 3,
        opacity: 1,
        fillOpacity: 1,
        color: '#666666'
    });
}

function leaveLayer() {
    this.bringToBack();
    this.setStyle({
        weight: 1,
        opacity: 0.5,
        fillOpacity: 0.7,
        color: 'white'
    });
}

// Initialise global map and layer variables
var map = new L.map('map');
var topoLayer = new L.TopoJSON();
var colorScale = chroma
    .scale(chroma.brewer.YlOrRd)
    .domain([-2.1227372118474177, 2.397453389303414]);

/* Dynamic resizing */
$(window).on("resize", function() {
    var h = $(window).height();
    $("#map").height(h - (h * 0.2));
    map.invalidateSize();
}).trigger("resize");

loadData();

/*--------------------------------------------------------------*/
/* Charts */

var chart;

function currentSelectedIndustry() {
    return $("#indList").val();
}

function parseData(data, prob, prod_complexity) {
    var marker_data = [];
    var x = [];
    var y = [];
    var color = [];
    for (var key in data.S12000033) {
        if (key !== "growth" && key !== "LAD14NM") {
            x.push(data.S12000033[key]);
            y.push(prob.S12000033[key]);
            color.push(prod_complexity[key].score);
        }
    }
    marker_data = [{
        x: x,
        y: y,
        mode: 'markers',
        marker: {
            sizemode: 'area',
            size: 20,
            sizeref: '2em',
            colorscale: 'YlOrRd',
            cmin: -3,
            cmax: 3,
            color: color,
            colorbar: {
                titleside: 'right',
                outlinecolor: 'rgba(68,68,68,0)',
                ticks: 'none',
                ticklen: 0,
                tickfont: 'Arial',
                fontsize: 4
            }
        }
    }];
    console.log(marker_data);
    return marker_data;
}

function analyze(error, business_data, prod_complexity, prob) {
    if (error) {
        console.log(error);
    }

    /*business_data.forEach(function(d) {
        d.growth = +d.growth;
        d.construction = +d.construction;
        d.manufacture_advanced = +d.manufacture_advanced;
        d.manufacture_agrochemical = +d.manufacture_agrochemical;
        d.manufacture_automotive = +d.manufacture_automotive;
        d.manufacture_chemical_pharma = +d.manufacture_chemical_pharma;
        d.manufacture_construction = +d.manufacture_construction;
        d.manufacture_electrical = +d.manufacture_electrical;
        d.manufacture_food = +d.manufacture_food;
        d.manufacture_food_2 = +d.manufacture_food_2;
        d.manufacture_footwear_leather = +d.manufacture_footwear_leather;
        d.manufacture_furniture = +d.manufacture_furniture;
        d.manufacture_light_misc = +d.manufacture_light_misc;
        d.manufacture_machinery = +d.manufacture_machinery;
        d.manufacture_metallurgy = +d.manufacture_metallurgy;
        d.manufacture_metallurgy_misc = +d.manufacture_metallurgy;
        d.manufacture_metallurgy_tools = +d.manufacture_metallurgy_tools;
        d.manufacture_optics = +d.manufacture_optics;
        d.manufacture_paper = +d.manufacture_paper;
        d.manufacture_plastic = +d.manufacture_plastic;
        d.manufacture_rubber = +d.manufacture_rubber;
        d.manufacture_shipbuilding = +d.manufacture_shipbuilding;
        d.manufacture_textiles = +d.manufacture_textiles;
        d.manufacturing_aerospace = +d.manufacturing_aerospace;
        d.manufacturing_misc = +d.manufacturing_misc;
        d.mining_mining = +d.mining_mining;
        d.mining_oil_gas = +d.mining_oil_gas;
        d.primary_farming = +d.primary_farming;
        d.primary_fishing = +d.primary_fishing;
        d.primary_forestry = +d.primary_forestry;
        d.primary_livestock = +d.primary_livestock;
        d.services_agriculture_rural = +d.services_agriculture_rural;
        d.services_automotive = +d.services_automotive;
        d.services_business_support = +d.services_business_support;
        d.services_cleaning_facilities = +d.services_cleaning_facilities;
        d.services_computing = +d.services_computing;
        d.services_consumer_retail = +d.services_consumer_retail;
        d.services_creative = +d.services_creative;
        d.services_creative_content = +d.services_creative_content;
        d.services_cultural = +d.services_cultural;
        d.services_education = +d.services_education;
        d.services_energy = +d.services_energy;
        d.services_finance_insurance = +d.services_finance_insurance;
        d.services_finance_real_state = +d.services_finance_real_state;
        d.services_freight_transport = +d.services_freight_transport;
        d.services_head_offices = +d.services_head_offices;
        d.services_health = +d.services_health;
        d.services_human_resources = +d.services_human_resources;
        d.services_kibs = +d.services_kibs;
        d.services_leisure = +d.services_leisure;
        d.services_professional_public = +d.services_professional_public;
        d.services_publishing = +d.services_publishing;
        d.services_r_and_d = +d.services_r_and_d;
        d.services_real_estate = +d.services_real_estate;
        d.services_repair_transport = +d.services_repair_transport;
        d.services_residential_social_work = +d.services_residential_social_work;
        d.services_retail_misc = +d.services_retail_misc;
        d.services_technical_engineering = +d.services_technical_engineering;
        d.services_telecommunications = +d.services_telecommunications;
        d.services_transport_storage = +d.services_transport_storage;
        d.services_travelling_tourism = +d.services_travelling_tourism;
        d.services_urban_transport = +d.services_urban_transport;
        d.services_waste_recycling = +d.services_waste_recycling;
        d.wholesale_repair_machinery = +d.wholesale_repair_machinery;
        d.wholesale_retail_misc = +d.wholesale_retail_misc;
    });*/

        var data = parseData(business_data, prob, prod_complexity);

        var layout = {
            showlegend: false,
            xaxis: {
                title: 'Number of Businesses - 2015',
                type: 'log',
                autorange: true
            },
            yaxis: {
                title: 'Probability of Growth in Sector in Area',
            },
            margin: {
                t: 10
            },
            hovermode: 'closest',
        };
        Plotly.newPlot('chart', data, layout, {
            showLink: false
        });
}

queue()
    .defer(d3.json, "/data/business_data.json")
    .defer(d3.json, "/data/product_complexity_2015.json")
    .defer(d3.json, "/data/probabilities.json")
    .await(analyze);
<|endoftext|>


<commit_before>var route = function(state) {
  if (state === null) {
    $('#container').hide();
    $('#image').hide();
    $('#home').show();

    $.ajax({
      'url': '/images',
      'type': 'GET',
      'dataType': 'json',
      'success': renderImages
    });

    $.ajax({
      'url': '/containers',
      'type': 'GET',
      'dataType': 'json',
      'success': renderContainers
    });

    return;
  }

  var state = state.split(':');

  if (state[0] === 'image') {
    $('#container').hide();
    $('#image').show();
    $('#home').hide();

    $.ajax({
      'url': ['/image/', state[1]].join(''),
      'type': 'GET',
      'dataType': 'json',
      'success': renderImage
    });

  } else if (state[0] === 'container') {
    $('#container').show();
    $('#image').hide();
    $('#home').hide();

    $.ajax({
      'url': ['/container/', state[1]].join(''),
      'type': 'GET',
      'dataType': 'json',
      'success': renderContainer
    });
  }
};<commit_msg>Make more ajax calls in router.js to get more info<commit_after>var route = function(state) {
  if (state === null) {
    $('#container').hide();
    $('#image').hide();
    $('#home').show();

    $.ajax({
      'url': '/images',
      'type': 'GET',
      'dataType': 'json',
      'success': renderImages
    });

    $.ajax({
      'url': '/containers',
      'type': 'GET',
      'dataType': 'json',
      'success': renderContainers
    });

    $.ajax({
      'url': '/info',
      'type': 'GET',
      'dataType': 'json',
      'success': renderDockerInfo
    });

    return;
  }

  var state = state.split(':');

  if (state[0] === 'image') {
    $('#container').hide();
    $('#image').show();
    $('#home').hide();

    $.ajax({
      'url': ['/image', state[1], 'history'].join('/'),
      'type': 'GET',
      'success': renderImageHistory
    });

    $.ajax({
      'url': ['/image/', state[1]].join(''),
      'type': 'GET',
      'dataType': 'json',
      'success': renderImage
    });

  } else if (state[0] === 'container') {
    $('#container').show();
    $('#image').hide();
    $('#home').hide();

    $.ajax({
      'url': ['/container', state[1], 'logs'].join('/'),
      'type': 'GET',
      'success': renderLogs
    });

    $.ajax({
      'url': ['/container/', state[1]].join(''),
      'type': 'GET',
      'dataType': 'json',
      'success': renderContainer
    });
  }
};<|endoftext|>


<commit_before>var summaryTemplate = null;
var singleResultTemplate = null;
var multiResultTemplate = null;
var resultStatsTemplate = null;

$(function() {
    summaryTemplate = _.template($("#summary-template").html());
    singleResultTemplate = _.template($("#single-result-template").html());
    multiResultTemplate = _.template($("#multi-result-template").html());
    resultStatsTemplate = _.template($("#result-stats-sidebar").html());
    $('.example-search').click(function() {
        $('#search-input').val($(this).text());
        $('#search-submit').click();
        return false;
    });
    buildDateUI();
});

var appendResult = function(obj) {
    var statusClass;
    switch (obj.docs[0].matter.status){
        case 'Adopted':
        case 'Approved':
        case 'Passed':
        case 'Recommended for Passage':
            statusClass = 'success';
            break;

        case 'Deferred and Published':
        case 'Direct Introduction':
        case 'In Committee':
        case 'Introduced':
        case 'Placed on File':
        case 'Recommended for Re-referral':
        case 'Re-referred':
            statusClass = 'warning';
            break;

        case 'Failed to Pass':
        case 'Recommended Do Not Pass':
        case 'Tabled':
        case 'Vetoed':
        case 'Void':
        case 'Withdrawn':
            statusClass = 'alert';
            break;
    }
    if(obj.docs.length > 1) {
        $(multiResultTemplate({docs: obj.docs, statusClass: statusClass})).appendTo('#search-results');
    } else {
        $(singleResultTemplate({doc: obj.docs[0], statusClass: statusClass})).appendTo('#search-results');
    }
};

//Need to store the results of the AJAX call somewhere so we can sort without hitting the database
//var documents
var resultData = {
  'documents': [],
  'dateGroups': {},
  'rankGroups': {},
  'sidebarData': {} //statsData
}

var doSearch = function(searchUrl, subscribeUrl) {
    showLoadingState();
    //documents = [];
    var query = $('#search-input').val();
    var dateRangeType = $('#date-range-type').val();
    var ignoreRoutine = $('#ignore-routine').is(':checked');
    var queryQualifier = '';
    var isRanked = false;
    // only rank if not the default query
    if (subscribeUrl != null) {
      var isRanked = true;
    }

    //build up summary stats for sidebar
    function buildResultStats(doc, statsData){
        var statTypes = {
            'Statuses': 'status',
            'Matter Types': 'type'
        };
        $.each(statTypes, function(key, matter_field) {
            if (!(key in statsData)){
                statsData[key] = {};
            }
            if(doc.matter[matter_field] in statsData[key]) {
                statsData[key][doc.matter[matter_field]] += 1;
            } else {
                statsData[key][doc.matter[matter_field]] = 1;
            }
        });
        return statsData;
    };

    function appendSummaryAndStats(total, qualifier, statsData){
        $('#results-summary').html($(summaryTemplate({
            total: total,
            query: $('#search-input').val(),
            qualifier: qualifier
        })));
        if (statsData) {
            $('#results-stats').html(resultStatsTemplate({statsData: statsData}));
        }
    };

    // group documents with their related matters
    function sortByDate(data){
        if(data.documents.length > 0) {
            var dates = [];
            //TODO: create statsData global so we don't have to rebuild sidebar on sort-by-rank?
            $.each(data.documents, function(i, doc) {
                // dt is datetime
                var dt = doc.sort_date;
                // if the datetime hasn't already been added to the dates array
                if(dates.indexOf(dt) < 0) {
                    dates.push(dt);
                    resultData.dateGroups[dt] = {};
                }
                // if the datetime already exists in `groups`, it means the matter has multiple documents: add docs to existing matter
                // otherwise just create the `docs` property
                if(doc.matter.id in resultData.dateGroups[dt]) {
                    resultData.dateGroups[dt][doc.matter.id]['docs'].push(doc);
                } else {
                    resultData.dateGroups[dt][doc.matter.id] = {
                        'docs': [doc]
                    };
                }
                buildResultStats(doc, resultData.sidebarData);
            });

            appendSummaryAndStats(data.documents.length, queryQualifier, resultData.sidebarData);

            dates.sort(function(a,b) { return (b < a) ? -1 : 1 });
            for (i=0; i<dates.length; i++) {
                var resultGroups = resultData.dateGroups[dates[i]];
                $.each(resultGroups, function(j, g) {
                    appendResult(g);
                });
            }
        }
    };
    function sortByRank(data){
        if(data.documents.length > 0) {
            var ranks = [];
            $.each(data.documents, function(i, doc) {
                var rank = doc.rank;
                if(ranks.indexOf(rank) < 0) {
                    ranks.push(rank);
                    resultData.rankGroups[rank] = {};
                }
                if(doc.matter.id in resultData.rankGroups[rank]) {
                    resultData.rankGroups[rank][doc.matter.id]['docs'].push(doc);
                } else {
                    resultData.rankGroups[rank][doc.matter.id] = {
                        'docs': [doc]
                    };
                }
                buildResultStats(doc, resultData.sidebarData);
            });
            for (i=0; i<ranks.length; i++) {
                var resultGroups = resultData.rankGroups[ranks[i]];
                $.each(resultGroups, function(j, g) {
                    appendResult(g);
                });
            }
        }
    }
    function showEmailForm(){
      $('#search-subscribe-form, #sort-by').show();
          $('#search-subscribe-form').submit(function(event) {
              handle_subscribe(event, subscribeUrl);
              return false;
          });
    };

    //used to determine language to display and time period to return
    switch (dateRangeType) {
        case 'past-year':
            queryQualifier = ' in the past year';
            break;

        case 'past-month':
            queryQualifier = ' in the last 30 days';
            break;

        case 'any':
            break;
    }

    console.log('EXECUTING QUERY:: ' + query + ", is_ranked: " + isRanked );
    $.ajax({
        url: searchUrl,
        data: {
            query: query,
            date_range: dateRangeType,
            ignore_routine: ignoreRoutine,
            is_ranked: isRanked
        }
    })
    .success(function(data) {
        console.log(data);

        documents = data.documents;
        if (data.is_ranked) {
          //NOTE: are results in ranked order already? If so, we just need to create meta.
          sortByRank(data);
        } else {
          sortByDate(data);
        }

        // don't let people subscribe to the default query
        if (subscribeUrl != null) {
          showEmailForm(subscribeUrl);
        }
        $("#search-results").foundation('reveal', 'reflow');

    })
    .error(function(xhr, status, errMsg) {
        alert(errMsg);
    })
    .complete(function(xhr, status) {
        hideLoadingState();
    });
}; // doSearch

var sortByDate = function(){
  $('#sort-chron').on('click', function(){
    console.log(documents);
  });
}
<commit_msg>abstract out result display:<commit_after>var summaryTemplate = null;
var singleResultTemplate = null;
var multiResultTemplate = null;
var resultStatsTemplate = null;

$(function() {
    summaryTemplate = _.template($("#summary-template").html());
    singleResultTemplate = _.template($("#single-result-template").html());
    multiResultTemplate = _.template($("#multi-result-template").html());
    resultStatsTemplate = _.template($("#result-stats-sidebar").html());
    $('.example-search').click(function() {
        $('#search-input').val($(this).text());
        $('#search-submit').click();
        return false;
    });
    buildDateUI();
});

var appendResult = function(obj) {
    var statusClass;
    switch (obj.docs[0].matter.status){
        case 'Adopted':
        case 'Approved':
        case 'Passed':
        case 'Recommended for Passage':
            statusClass = 'success';
            break;

        case 'Deferred and Published':
        case 'Direct Introduction':
        case 'In Committee':
        case 'Introduced':
        case 'Placed on File':
        case 'Recommended for Re-referral':
        case 'Re-referred':
            statusClass = 'warning';
            break;

        case 'Failed to Pass':
        case 'Recommended Do Not Pass':
        case 'Tabled':
        case 'Vetoed':
        case 'Void':
        case 'Withdrawn':
            statusClass = 'alert';
            break;
    }
    if(obj.docs.length > 1) {
        $(multiResultTemplate({docs: obj.docs, statusClass: statusClass})).appendTo('#search-results');
    } else {
        $(singleResultTemplate({doc: obj.docs[0], statusClass: statusClass})).appendTo('#search-results');
    }
};

//Need to store the results of the AJAX call somewhere so we can sort without hitting the database
//var documents
var resultData = {
  'documents': [],
  'dateGroups': {},
  'rankGroups': {},
  'sidebarData': {}, //statsData
  'query': $('#search-input').val(),
  'dateRangeType': $('#date-range-type').val(),
  'ignoreRoutine': $('#ignore-routine').is(':checked'),
  'queryQualifier': '',
  'isRanked': false
}

var doSearch = function(searchUrl, subscribeUrl) {
    showLoadingState();
    // only rank if not the default query
    if (subscribeUrl != null) {
      var isRanked = true;
    }

    //build up summary stats for sidebar
    function buildResultStats(doc, statsData){
        var statTypes = {
            'Statuses': 'status',
            'Matter Types': 'type'
        };
        $.each(statTypes, function(key, matter_field) {
            if (!(key in statsData)){
                statsData[key] = {};
            }
            if(doc.matter[matter_field] in statsData[key]) {
                statsData[key][doc.matter[matter_field]] += 1;
            } else {
                statsData[key][doc.matter[matter_field]] = 1;
            }
        });
        return statsData;
    };

    function populateResults(sortType){
        typeKeys = Object.keys(resultData[sortType]);
        if (sortType == "dateGroups"){
          typeKeys.sort(function(a,b) { return (b < a) ? -1 : 1 });
        } //Do we also need to sort ranks?
        for (i=0; i<typeKeys.length; i++) {
            var resultGroups = resultData[sortType][typeKeys[i]];
            $.each(resultGroups, function(j, g) {
                appendResult(g);
            });
        }
    }

    function appendSummaryAndStats(total, qualifier, statsData){
        $('#results-summary').html($(summaryTemplate({
            total: total,
            query: $('#search-input').val(),
            qualifier: qualifier
        })));
        if (statsData) {
            $('#results-stats').html(resultStatsTemplate({statsData: statsData}));
        }
    }

    // group documents with their related matters
    function sortByDate(data){
        if(data.documents.length > 0) {
            var dates = [];
            //TODO: create statsData global so we don't have to rebuild sidebar on sort-by-rank?
            $.each(data.documents, function(i, doc) {
                // dt is datetime
                var dt = doc.sort_date;
                // if the datetime hasn't already been added to the dates array
                if(dates.indexOf(dt) < 0) {
                    dates.push(dt);
                    resultData.dateGroups[dt] = {};
                }
                // if the datetime already exists in `groups`, it means the matter has multiple documents: add docs to existing matter
                // otherwise just create the `docs` property
                if(doc.matter.id in resultData.dateGroups[dt]) {
                    resultData.dateGroups[dt][doc.matter.id]['docs'].push(doc);
                } else {
                    resultData.dateGroups[dt][doc.matter.id] = {
                        'docs': [doc]
                    };
                }
                buildResultStats(doc, resultData.sidebarData);
            });

            appendSummaryAndStats(data.documents.length, queryQualifier, resultData.sidebarData);

            //dates.sort(function(a,b) { return (b < a) ? -1 : 1 });
            populateResults("dateGroups");
            //for (i=0; i<dates.length; i++) {
                //var resultGroups = resultData.dateGroups[dates[i]];
                //$.each(resultGroups, function(j, g) {
                    //appendResult(g);
                //});
            //}
        }
    }

    function sortByRank(data){
        if(data.documents.length > 0) {
            var ranks = [];
            $.each(data.documents, function(i, doc) {
                var rank = doc.rank;
                if(ranks.indexOf(rank) < 0) {
                    ranks.push(rank);
                    resultData.rankGroups[rank] = {};
                }
                if(doc.matter.id in resultData.rankGroups[rank]) {
                    resultData.rankGroups[rank][doc.matter.id]['docs'].push(doc);
                } else {
                    resultData.rankGroups[rank][doc.matter.id] = {
                        'docs': [doc]
                    };
                }
                buildResultStats(doc, resultData.sidebarData);
            });

        }
        populateResults("rankGroups");
    }

    function showEmailForm(){
      $('#search-subscribe-form, #sort-by').show();
          $('#search-subscribe-form').submit(function(event) {
              handle_subscribe(event, subscribeUrl);
              return false;
          });
    };

    //used to determine language to display and time period to return
    switch (dateRangeType) {
        case 'past-year':
            queryQualifier = ' in the past year';
            break;

        case 'past-month':
            queryQualifier = ' in the last 30 days';
            break;

        case 'any':
            break;
    }

    console.log('EXECUTING QUERY:: ' + query + ", is_ranked: " + isRanked );
    $.ajax({
        url: searchUrl,
        data: {
            query: query,
            date_range: dateRangeType,
            ignore_routine: ignoreRoutine,
            is_ranked: isRanked
        }
    })
    .success(function(data) {
        console.log(data);

        documents = data.documents;
        if (data.is_ranked) {
          //NOTE: are results in ranked order already? If so, we just need to create meta.
          sortByRank(data);
        } else {
          sortByDate(data);
        }

        // don't let people subscribe to the default query
        if (subscribeUrl != null) {
          showEmailForm(subscribeUrl);
        }
        $("#search-results").foundation('reveal', 'reflow');

    })
    .error(function(xhr, status, errMsg) {
        alert(errMsg);
    })
    .complete(function(xhr, status) {
        hideLoadingState();
    });
}; // doSearch

var sortByDate = function(){
  $('#sort-chron').on('click', function(){
    console.log(documents);
  });
}
<|endoftext|>


<commit_before>(function() {
  function displaySearchResults(results, store) {
    var searchResults = document.getElementById('search-results');

    if (results.length) { // Are there any results?
      var appendString = '';

      for (var i = 0; i < results.length; i++) {  // Iterate over the results
        var item = store[results[i].ref];
        appendString += '

' + item.title + '

';
      }

      searchResults.innerHTML = appendString;
    } else {
      searchResults.innerHTML = '
Tidak ada yang hasil yang sesuai
';
    }
  }

  function getQueryVariable(variable) {
    var query = window.location.search.substring(1);
    var vars = query.split('&');

    for (var i = 0; i < vars.length; i++) {
      var pair = vars[i].split('=');

      if (pair[0] === variable) {
        return decodeURIComponent(pair[1].replace(/\+/g, '%20'));
      }
    }
  }

  var searchTerm = getQueryVariable('query');

  if (searchTerm) {
    document.getElementById('search-box').setAttribute("value", searchTerm);

    // Initalize lunr with the fields it will be searching on. I've given title
    // a boost of 10 to indicate matches on this field are more important.
    var idx = lunr(function () {
      this.field('id');
      this.field('title', { boost: 10 });
      this.field('author');
      this.field('category');
      this.field('content');
    });

    for (var key in window.store) { // Add the data to lunr
      idx.add({
        'id': key,
        'title': window.store[key].title,
        'author': window.store[key].author,
        'category': window.store[key].category,
        'content': window.store[key].content
      });

      var results = idx.search(searchTerm); // Get lunr to perform a search
      displaySearchResults(results, window.store); // We'll write this in the next section
    }
  }
})();
<commit_msg>Update search.js<commit_after>(function() {
  function displaySearchResults(results, store) {
    var searchResults = document.getElementById('search-results');

    if (results.length) { // Are there any results?
      var appendString = '';

      for (var i = 0; i < results.length; i++) {  // Iterate over the results
        var item = store[results[i].ref];
        appendString += '

' + item.title + '

';
      }

      searchResults.innerHTML = appendString;
    } else {
      searchResults.innerHTML = '
Tidak ada yang hasil yang sesuai
';
    }
  }

  function getQueryVariable(variable) {
    var query = window.location.search.substring(1);
    var vars = query.split('&');

    for (var i = 0; i < vars.length; i++) {
      var pair = vars[i].split('=');

      if (pair[0] === variable) {
        return decodeURIComponent(pair[1].replace(/\+/g, '%20'));
      }
    }
  }

  var searchTerm = getQueryVariable('query');

  if (searchTerm) {
    document.getElementById('search-box').setAttribute("value", searchTerm);

    // Initalize lunr with the fields it will be searching on. I've given title
    // a boost of 10 to indicate matches on this field are more important.
    var idx = lunr(function () {
      this.field('id');
      this.field('title', { boost: 10 });
      this.field('author');
      this.field('category');
    });

    for (var key in window.store) { // Add the data to lunr
      idx.add({
        'id': key,
        'title': window.store[key].title,
        'author': window.store[key].author,
        'category': window.store[key].category,
      });

      var results = idx.search(searchTerm); // Get lunr to perform a search
      displaySearchResults(results, window.store); // We'll write this in the next section
    }
  }
})();
<|endoftext|>


<commit_before>if (typeof SH === 'undefined' || !SH) {
  var SH = {};
}

(function($) {
  SH.map = function () {
    var map,
        infoWindow = new google.maps.InfoWindow(),
        geocoder = new google.maps.Geocoder(),
        youMarkerCollection = new Array(),
        needMarkerCollection = new Array(),
        userLocation,
        _self;

    var markerShadow = new google.maps.MarkerImage('/img/marker_shadow.png',
      new google.maps.Size(51, 37),
      new google.maps.Point(0, 0),
      new google.maps.Point(16, 37));

    _self = {
      create: function (options) {
        var settings = {
              mapId: 'map',
              zoom: 14,
              center: new google.maps.LatLng(39.95240, -75.16362),
              mapTypeId: google.maps.MapTypeId.ROADMAP
            };

        if (options) {
          $.extend(settings, options);
        }
        map = new google.maps.Map(document.getElementById(settings.mapId), settings);
      },

      getShelters: function (userLocation, plot) {
        plot = true || plot;

        var lat = parseFloat(userLocation.lat());
        var lng = parseFloat(userLocation.lng());

        $.ajax({
          url: '_map?lat=' + lat + '&long=' + lng,
          success: function(data) {

            SH.state.needs = data;
            if (plot === true) {
              _self.addSheltersToMap(data);
            }
            SH.needs.list(SH.state.needs);
          }
        });

      },

      addSheltersToMap: function (shelters) {
        var i,
            lat,
            lng,
            latlng,
            options,
            icon,
            sheltersLength = shelters.result.length;

        _self.removeMarkers(needMarkerCollection);

        for (i=0; i<sheltersLength; i++) {
          lat = shelters.result[i].Latitude;
          lng = shelters.result[i].Longitude;
          latlng = new google.maps.LatLng(lat, lng);

          icon = _self.selectMarkerIcon(shelters.result[i]);

          options = {icon: icon, shadow: markerShadow};

          _self.createMarker(latlng, shelters.result[i].Name + "
" + shelters.result[i].Address1, options, shelters.result[i].id);
        }
      },

      selectMarkerIcon: function(need) {

        var icon

        if (need.HasMeals === "Y") {
          icon = '/img/food.png';
        }
        if (need.IsShelter === "Y") {
          icon = '/img/shelter.png';
        }
        if (need.IsShelter === "Y" && need.HasMeals === "Y") {
          icon = '/img/shelter_food.png';
        }
        if (need.IsIntake === "Y") {
          icon = '/img/intake.png';
        } else {
          icon = '/img/shelter.png'; //Mill Creek Baptist Church currently doesn't meet any of these conditions. This will give it the shelter icon (I'm assuming its a shelter).
        }

        return icon;

      },

      createMarker: function (latlng, description, options, id) {
        var settings = {
              position: latlng,
              map: map,
          };

        if (options) {
          $.extend(settings, options);
        }

        var marker = new google.maps.Marker(settings);

        google.maps.event.addListener(marker, 'click', function() {
          infoWindow.close();
          infoWindow.setContent(description);
          infoWindow.open(map,marker);
        });

        if (description === 'Your Location') { //TODO: this is fragile.
          google.maps.event.addListener(marker, 'dragend', function () {
            _self.getShelters(marker.getPosition(),false);
            _self.updateMapCenter(marker.getPosition());
          });

          _self.removeMarkers(youMarkerCollection)
          youMarkerCollection.push({"id": 'you', "marker": marker});
        } else {
          needMarkerCollection.push({"id": id, "marker": marker});
        }
      },

      orientUser: function () {
        //var userLocation = null;

        // Try W3C Geolocation
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(function(position) {
            userLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
            _self.create({ center: userLocation });
            _self.getShelters(userLocation,true);
            _self.updateMapCenter(userLocation);
            _self.createMarker(userLocation, 'Your Location', {
              animation: google.maps.Animation.DROP,
              draggable: true,
              icon: '/img/you.png',
              shadow: markerShadow
            });
          }, function() {
            _self.create();
          });
        }

        return userLocation;
      },

      updateMapCenter: function (latLongObj) {
        map.setCenter(latLongObj);
      },

      geocode: function (addr, description) {
        var lat,
            lng,
            latlng;

        geocoder.geocode({
          'address': addr + 'Philadelphia, PA',
        }, function (results, status) {
          if (status === google.maps.GeocoderStatus.OK) {
            _self.createMarker(results[0].geometry.location, 'Your Location',  {
              animation: google.maps.Animation.DROP,
              draggable: true,
              icon: '/img/you.png',
              shadow: markerShadow
            });

            _self.updateMapCenter(results[0].geometry.location);
            _self.getShelters(results[0].geometry.location,false);
          }
        });
      },

      removeMarkers: function(markerArray) {
        var i;

        if (markerArray) {
          for (i in markerArray) {
            markerArray[i].marker.setMap(null);
          }
          markerArray.length = 0;
        }
      },

      zoomToNeedMarker: function(needID) {

        var i,
        needsLength = needMarkerCollection.length;

        for (i = 0; i < needsLength; i++) {
          if(needID == needMarkerCollection[i].id) {
            map.setCenter(needMarkerCollection[i].marker.getPosition());
            map.setZoom(18)
            break;
          }
        }

      }

    };

    return _self;
  };

})(jQuery);
<commit_msg>Made zoomToNeedMarker a public function<commit_after>if (typeof SH === 'undefined' || !SH) {
  var SH = {};
}

(function($) {
  SH.map = function () {
    var map,
        infoWindow = new google.maps.InfoWindow(),
        geocoder = new google.maps.Geocoder(),
        youMarkerCollection = new Array(),
        needMarkerCollection = new Array(),
        userLocation,
        _self;

    var markerShadow = new google.maps.MarkerImage('/img/marker_shadow.png',
      new google.maps.Size(51, 37),
      new google.maps.Point(0, 0),
      new google.maps.Point(16, 37));

    _self = {
      create: function (options) {
        var settings = {
              mapId: 'map',
              zoom: 14,
              center: new google.maps.LatLng(39.95240, -75.16362),
              mapTypeId: google.maps.MapTypeId.ROADMAP
            };

        if (options) {
          $.extend(settings, options);
        }
        map = new google.maps.Map(document.getElementById(settings.mapId), settings);
      },

      getShelters: function (userLocation, plot) {
        plot = true || plot;

        var lat = parseFloat(userLocation.lat());
        var lng = parseFloat(userLocation.lng());

        $.ajax({
          url: '_map?lat=' + lat + '&long=' + lng,
          success: function(data) {

            SH.state.needs = data;
            if (plot === true) {
              _self.addSheltersToMap(data);
            }
            SH.needs.list(SH.state.needs);
          }
        });

      },

      addSheltersToMap: function (shelters) {
        var i,
            lat,
            lng,
            latlng,
            options,
            icon,
            sheltersLength = shelters.result.length;

        _self.removeMarkers(needMarkerCollection);

        for (i=0; i<sheltersLength; i++) {
          lat = shelters.result[i].Latitude;
          lng = shelters.result[i].Longitude;
          latlng = new google.maps.LatLng(lat, lng);

          icon = _self.selectMarkerIcon(shelters.result[i]);

          options = {icon: icon, shadow: markerShadow};

          _self.createMarker(latlng, shelters.result[i].Name + "
" + shelters.result[i].Address1, options, shelters.result[i].id);
        }
      },

      selectMarkerIcon: function(need) {

        var icon

        if (need.HasMeals === "Y") {
          icon = '/img/food.png';
        }
        if (need.IsShelter === "Y") {
          icon = '/img/shelter.png';
        }
        if (need.IsShelter === "Y" && need.HasMeals === "Y") {
          icon = '/img/shelter_food.png';
        }
        if (need.IsIntake === "Y") {
          icon = '/img/intake.png';
        } else {
          icon = '/img/shelter.png'; //Mill Creek Baptist Church currently doesn't meet any of these conditions. This will give it the shelter icon (I'm assuming its a shelter).
        }

        return icon;

      },

      createMarker: function (latlng, description, options, id) {
        var settings = {
              position: latlng,
              map: map,
          };

        if (options) {
          $.extend(settings, options);
        }

        var marker = new google.maps.Marker(settings);

        google.maps.event.addListener(marker, 'click', function() {
          infoWindow.close();
          infoWindow.setContent(description);
          infoWindow.open(map,marker);
        });

        if (description === 'Your Location') { //TODO: this is fragile.
          google.maps.event.addListener(marker, 'dragend', function () {
            _self.getShelters(marker.getPosition(),false);
            _self.updateMapCenter(marker.getPosition());
          });

          _self.removeMarkers(youMarkerCollection)
          youMarkerCollection.push({"id": 'you', "marker": marker});
        } else {
          needMarkerCollection.push({"id": id, "marker": marker});
        }
      },

      orientUser: function () {
        //var userLocation = null;

        // Try W3C Geolocation
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(function(position) {
            userLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
            _self.create({ center: userLocation });
            _self.getShelters(userLocation,true);
            _self.updateMapCenter(userLocation);
            _self.createMarker(userLocation, 'Your Location', {
              animation: google.maps.Animation.DROP,
              draggable: true,
              icon: '/img/you.png',
              shadow: markerShadow
            });
          }, function() {
            _self.create();
          });
        }

        return userLocation;
      },

      updateMapCenter: function (latLongObj) {
        map.setCenter(latLongObj);
      },

      geocode: function (addr, description) {
        var lat,
            lng,
            latlng;

        geocoder.geocode({
          'address': addr + 'Philadelphia, PA',
        }, function (results, status) {
          if (status === google.maps.GeocoderStatus.OK) {
            _self.createMarker(results[0].geometry.location, 'Your Location',  {
              animation: google.maps.Animation.DROP,
              draggable: true,
              icon: '/img/you.png',
              shadow: markerShadow
            });

            _self.updateMapCenter(results[0].geometry.location);
            _self.getShelters(results[0].geometry.location,false);
          }
        });
      },

      removeMarkers: function(markerArray) {
        var i;

        if (markerArray) {
          for (i in markerArray) {
            markerArray[i].marker.setMap(null);
          }
          markerArray.length = 0;
        }
      },

    };

    SH.map.prototype.zoomToNeedMarker = function(needID) {

        var i,
        needsLength = needMarkerCollection.length;

        for (i = 0; i < needsLength; i++) {
          if(needID == needMarkerCollection[i].id) {
            map.setCenter(needMarkerCollection[i].marker.getPosition());
            map.setZoom(18)
            break;
          }
        }

    }

    return _self;
  };

})(jQuery);
<|endoftext|>


<commit_before>'use strict';

var Status = (function() {
	var websites_service;
	var selected_website_id;

	//status
	var states_grid;

	var states_columns = [
		{label : 'Name', data: 'name', type : Grid.DataType.STRING, width : 120, render : render_name},
		{label : 'Last check', data: 'update', type : Grid.DataType.DATE, width : 150, render : render_date},
		{label : 'Downtime', data : 'downtime', type : Grid.DataType.NUMBER, width : 250, render : render_time},
		{label : 'Uptime', data : 'uptime', type : Grid.DataType.NUMBER, width : 250, render : render_time},
		{label : 'Availability', data : 'availability', type : Grid.DataType.NUMBER, width : 80, render : render_online},
		{label : 'Actions', width : 100, unsortable : true, render : render_actions}
	];

	function render_date(value) {
		return value ? value.toFullDisplay() : 'NA';
	}

	function render_name(value, record) {
		var element = document.createFullElement('span');
		if(record.disabled) {
			element.appendChild(document.createFullElement('img', {src : '/images/bullet_black.png', alt : 'Disabled', title : 'Disabled', style : 'vertical-align: bottom;'}));
		}
		element.appendChild(document.createTextNode(value));
		return element;
	}

	function render_time(value, record) {
		return value ? Date.getDurationLiteral(value) : record.online ? '0' : 'NA';
	}

	function render_online(value) {
		return value ? value + '%' : 'NA';
	}

	function render_actions(value, record) {
		return document.createFullElement('a', {href : '#section=status&details=' + record.name, title : 'View website details'}, 'Details');
	}

	function draw_websites(websites) {
		//calculate availability for each website
		websites.forEach(function(website) {
			var availability;
			if(website.uptime || website.downtime) {
				availability = website.uptime / (website.downtime + website.uptime) * 100;
				availability = Math.round(availability * 10) / 10;
			}
			website.availability = availability;
		});
		//update states grid
		states_grid.render(new Grid.Datasource({data : websites}));
	}

	//details
	var details_grid;

	var details_columns = [
		{label : 'Start', data: 'start', type : Grid.DataType.DATE, width : 150, render : render_date},
		{label : 'Stop', data: 'stop', type : Grid.DataType.DATE, width : 150, render : render_date},
		{label : 'Duration', data: 'duration', type : Grid.DataType.NUMBER, width : 200, render : render_duration},
		{label : 'Rationale', data : 'rationale', type : Grid.DataType.STRING},
		{label : 'Action', width : 100, unsortable : true, render : render_details_action}
	];

	function render_duration(value) {
		return value ? Date.getDurationLiteral(value) : 'NA';
	}

	function render_details_action(value, record) {
		if(Authentication.IsAuthenticated()) {
			var element = document.createFullElement(
				'a',
				{href : '#', title : 'Delete downtime'},
				'Delete',
				{
					click : function(event) {
						Event.stop(event);
						var xhr = new XMLHttpRequest();
						xhr.addEventListener(
							'load',
							function(xhr_event) {
								if(xhr_event.target.status === 200) {
									details_grid.datasource.data.removeElement(record);
									details_grid.render(details_grid.datasource);
									UI.Notify('Downtime has been deleted successfully');
								}
								else {
									UI.Notify('Unable to delete downtime');
								}
							}
						);
						xhr.open('DELETE', '/api/websites/' + selected_website_id + '/downtime/' + record.id, true);
						xhr.send();
					}
				}
			);
			return element;
		}
		return document.createElement('span');
	}

	function update_website_details_age(date) {
		var age;
		if(date) {
			age = 'Last checked ' + date.getAgeLiteral();
		}
		else {
			age = 'Not checked yet';
		}
		document.getElementById('website_details_age').textContent = age;
	}

	return {
		Show : function update_states() {
			websites_service.list(draw_websites);
		},
		Detail : function(website_id) {
			selected_website_id = website_id;
			var xhr = new XMLHttpRequest();
			xhr.addEventListener(
				'load',
				function(event) {
					var details = JSON.parse(event.target.responseText);
					//update link
					var website_details_link = document.getElementById('website_details_link');
					website_details_link.setAttribute('href', details.url);
					website_details_link.textContent = details.name;
					//update age
					update_website_details_age(details.update ? new Date(details.update) : undefined);
					//update check link
					var website_details_check = document.getElementById('website_details_check');
					website_details_check.clear();
					website_details_check.appendChild(document.createFullElement(
						'button',
						{},
						'Check now',
						{
							click : function(event) {
								Event.stop(event);
								var that = this;
								this.setAttribute('disabled', 'disabled');
								this.classList.add('loading');
								var xhr = new XMLHttpRequest();
								xhr.addEventListener(
									'load',
									function(xhr_event) {
										that.removeAttribute('disabled');
										that.classList.remove('loading');
										if(xhr_event.target.status === 200) {
											update_website_details_age(new Date());
											UI.Notify('Website has been checked successfully');
										}
										else {
											UI.Notify('Unable to check website');
										}
									}
								);
								xhr.open('GET', '/api/check/' + details.name, true);
								xhr.send();
							}
						}
					));
				}
			);
			xhr.open('GET', '/api/websites/' + website_id, true);
			xhr.send();
			var downtimes_xhr = new XMLHttpRequest();
			downtimes_xhr.addEventListener(
				'load',
				function(event) {
					var downtimes = JSON.parse(event.target.responseText);
					//calculate duration for each downtime
					downtimes.forEach(function(downtime) {
						var duration;
						if(downtime.start && downtime.stop) {
							//TODO improve this as grid does the same job
							duration = Math.round((new Date(downtime.stop).getTime() - new Date(downtime.start).getTime()) / 1000);
						}
						downtime.duration = duration;
					});
					//update downtimes grid
					details_grid.render(new Grid.Datasource({data : downtimes}));
				}
			);
			downtimes_xhr.open('GET', '/api/websites/' + website_id + '/downtimes', true);
			downtimes_xhr.send();
			UI.OpenModal(document.getElementById('website_details'));
		},
		Init : function(Websites) {
			websites_service = Websites;

			//status
			states_grid = new Grid({
				container : document.getElementById('states'),
				columns : states_columns,
				path : '/js/grid/',
				rowPerPage : undefined,
				rowClass : function(record) {
					return record.online === null ? 'na' : record.online ? 'ok' : 'nok';
				}
			});

			document.getElementById('status_check_now').addEventListener(
				'click',
				function(event) {
					Event.stop(event);
					var that = this;
					this.setAttribute('disabled', 'disabled');
					this.classList.add('loading');
					var xhr = new XMLHttpRequest();
					xhr.addEventListener(
						'load',
						function(xhr_event) {
							that.removeAttribute('disabled');
							that.classList.remove('loading');
							if(xhr_event.target.status === 200) {
								//TODO improve this as only enabled websites are checked
								draw_websites(JSON.parse(xhr_event.target.responseText));
								UI.Notify('Websites have been checked successfully');
							}
							else {
								UI.Notify('Unable to check websites');
							}
						}
					);
					xhr.open('GET', '/api/check', true);
					xhr.send();
				}
			);

			//details
			details_grid = new Grid({
				container : document.getElementById('website_details_downtimes'),
				columns : details_columns,
				path : '/js/grid/',
				rowPerPage : 10
			});
		}
	};
})();
<commit_msg>Fix deletion of a downtime<commit_after>'use strict';

var Status = (function() {
	var websites_service;
	var selected_website_id;

	//status
	var states_grid;

	var states_columns = [
		{label : 'Name', data: 'name', type : Grid.DataType.STRING, width : 120, render : render_name},
		{label : 'Last check', data: 'update', type : Grid.DataType.DATE, width : 150, render : render_date},
		{label : 'Downtime', data : 'downtime', type : Grid.DataType.NUMBER, width : 250, render : render_time},
		{label : 'Uptime', data : 'uptime', type : Grid.DataType.NUMBER, width : 250, render : render_time},
		{label : 'Availability', data : 'availability', type : Grid.DataType.NUMBER, width : 80, render : render_online},
		{label : 'Actions', width : 100, unsortable : true, render : render_actions}
	];

	function render_date(value) {
		return value ? value.toFullDisplay() : 'NA';
	}

	function render_name(value, record) {
		var element = document.createFullElement('span');
		if(record.disabled) {
			element.appendChild(document.createFullElement('img', {src : '/images/bullet_black.png', alt : 'Disabled', title : 'Disabled', style : 'vertical-align: bottom;'}));
		}
		element.appendChild(document.createTextNode(value));
		return element;
	}

	function render_time(value, record) {
		return value ? Date.getDurationLiteral(value) : record.online ? '0' : 'NA';
	}

	function render_online(value) {
		return value ? value + '%' : 'NA';
	}

	function render_actions(value, record) {
		return document.createFullElement('a', {href : '#section=status&details=' + record.name, title : 'View website details'}, 'Details');
	}

	function draw_websites(websites) {
		//calculate availability for each website
		websites.forEach(function(website) {
			var availability;
			if(website.uptime || website.downtime) {
				availability = website.uptime / (website.downtime + website.uptime) * 100;
				availability = Math.round(availability * 10) / 10;
			}
			website.availability = availability;
		});
		//update states grid
		states_grid.render(new Grid.Datasource({data : websites}));
	}

	//details
	var details_grid;

	var details_columns = [
		{label : 'Start', data: 'start', type : Grid.DataType.DATE, width : 150, render : render_date},
		{label : 'Stop', data: 'stop', type : Grid.DataType.DATE, width : 150, render : render_date},
		{label : 'Duration', data: 'duration', type : Grid.DataType.NUMBER, width : 200, render : render_duration},
		{label : 'Rationale', data : 'rationale', type : Grid.DataType.STRING},
		{label : 'Action', width : 100, unsortable : true, render : render_details_action}
	];

	function render_duration(value) {
		return value ? Date.getDurationLiteral(value) : 'NA';
	}

	function render_details_action(value, record) {
		if(Authentication.IsAuthenticated()) {
			var element = document.createFullElement(
				'a',
				{href : '#', title : 'Delete downtime'},
				'Delete',
				{
					click : function(event) {
						Event.stop(event);
						var xhr = new XMLHttpRequest();
						xhr.addEventListener(
							'load',
							function(xhr_event) {
								if(xhr_event.target.status === 200) {
									details_grid.datasource.data.removeElement(record);
									details_grid.render(details_grid.datasource);
									UI.Notify('Downtime has been deleted successfully');
								}
								else {
									UI.Notify('Unable to delete downtime');
								}
							}
						);
						xhr.open('DELETE', '/api/websites/' + selected_website_id + '/downtimes/' + record.id, true);
						xhr.send();
					}
				}
			);
			return element;
		}
		return document.createElement('span');
	}

	function update_website_details_age(date) {
		var age;
		if(date) {
			age = 'Last checked ' + date.getAgeLiteral();
		}
		else {
			age = 'Not checked yet';
		}
		document.getElementById('website_details_age').textContent = age;
	}

	return {
		Show : function update_states() {
			websites_service.list(draw_websites);
		},
		Detail : function(website_id) {
			selected_website_id = website_id;
			var xhr = new XMLHttpRequest();
			xhr.addEventListener(
				'load',
				function(event) {
					var details = JSON.parse(event.target.responseText);
					//update link
					var website_details_link = document.getElementById('website_details_link');
					website_details_link.setAttribute('href', details.url);
					website_details_link.textContent = details.name;
					//update age
					update_website_details_age(details.update ? new Date(details.update) : undefined);
					//update check link
					var website_details_check = document.getElementById('website_details_check');
					website_details_check.clear();
					website_details_check.appendChild(document.createFullElement(
						'button',
						{},
						'Check now',
						{
							click : function(event) {
								Event.stop(event);
								var that = this;
								this.setAttribute('disabled', 'disabled');
								this.classList.add('loading');
								var xhr = new XMLHttpRequest();
								xhr.addEventListener(
									'load',
									function(xhr_event) {
										that.removeAttribute('disabled');
										that.classList.remove('loading');
										if(xhr_event.target.status === 200) {
											update_website_details_age(new Date());
											UI.Notify('Website has been checked successfully');
										}
										else {
											UI.Notify('Unable to check website');
										}
									}
								);
								xhr.open('GET', '/api/check/' + details.name, true);
								xhr.send();
							}
						}
					));
				}
			);
			xhr.open('GET', '/api/websites/' + website_id, true);
			xhr.send();
			var downtimes_xhr = new XMLHttpRequest();
			downtimes_xhr.addEventListener(
				'load',
				function(event) {
					var downtimes = JSON.parse(event.target.responseText);
					//calculate duration for each downtime
					downtimes.forEach(function(downtime) {
						var duration;
						if(downtime.start && downtime.stop) {
							//TODO improve this as grid does the same job
							duration = Math.round((new Date(downtime.stop).getTime() - new Date(downtime.start).getTime()) / 1000);
						}
						downtime.duration = duration;
					});
					//update downtimes grid
					details_grid.render(new Grid.Datasource({data : downtimes}));
				}
			);
			downtimes_xhr.open('GET', '/api/websites/' + website_id + '/downtimes', true);
			downtimes_xhr.send();
			UI.OpenModal(document.getElementById('website_details'));
		},
		Init : function(Websites) {
			websites_service = Websites;

			//status
			states_grid = new Grid({
				container : document.getElementById('states'),
				columns : states_columns,
				path : '/js/grid/',
				rowPerPage : undefined,
				rowClass : function(record) {
					return record.online === null ? 'na' : record.online ? 'ok' : 'nok';
				}
			});

			document.getElementById('status_check_now').addEventListener(
				'click',
				function(event) {
					Event.stop(event);
					var that = this;
					this.setAttribute('disabled', 'disabled');
					this.classList.add('loading');
					var xhr = new XMLHttpRequest();
					xhr.addEventListener(
						'load',
						function(xhr_event) {
							that.removeAttribute('disabled');
							that.classList.remove('loading');
							if(xhr_event.target.status === 200) {
								//TODO improve this as only enabled websites are checked
								draw_websites(JSON.parse(xhr_event.target.responseText));
								UI.Notify('Websites have been checked successfully');
							}
							else {
								UI.Notify('Unable to check websites');
							}
						}
					);
					xhr.open('GET', '/api/check', true);
					xhr.send();
				}
			);

			//details
			details_grid = new Grid({
				container : document.getElementById('website_details_downtimes'),
				columns : details_columns,
				path : '/js/grid/',
				rowPerPage : 10
			});
		}
	};
})();
<|endoftext|>


<commit_before>/* global StateMachine, Backbone, _,
          mozRTCPeerConnection, mozRTCSessionDescription */

(function(exports) {
  "use strict";

  var defaultConstraints = {video: false, audio: false, fake: false};

  /**
   * WebRTC object constructor.
   *
   * Options are:
   * - {Object}  iceServers: Peer connection ICE servers (default: {})
   * - {Boolean} forceFake:  Forces fake media streams (default: false)
   *
   * @param {Object}                   options  Options
   */
  function WebRTC(options) {
    this._constraints = {};
    this.options = options || {};

    this.state = StateMachine.create({
      initial: 'ready',
      events: [
        {name: 'initiate',  from: 'ready',     to: 'pending'},
        {name: 'establish', from: 'pending',   to: 'ongoing'},
        {name: 'upgrade',   from: 'ongoing',   to: 'pending'},
        {name: 'answer',    from: ['ready',
                                   'ongoing'], to: 'ongoing'},
        {name: 'terminate', from: '*',         to: 'terminated'},
        {name: 'reset',     from: '*',         to: 'ready'}
      ],
      callbacks: {
        onenterstate: function(event, from, to) {
          this.trigger("change:state", to, from, event);
          this.trigger("state:" + event);
          this.trigger("state:to:" + to);
        }.bind(this)
      }
    });

    this._setupPeerConnection();
  }
  exports.WebRTC = WebRTC;

  _.extend(WebRTC.prototype, Backbone.Events);

  // public API

  /* jshint camelcase:false */
  WebRTC.prototype.__defineGetter__('constraints', function() {
    var _constraints = this._constraints || defaultConstraints;

    if (!!this.options.forceFake)
      _constraints.fake = true;

    return _constraints;
  });

  WebRTC.prototype.__defineSetter__('constraints', function(constraints) {
    this._constraints = constraints || defaultConstraints;

    if (!!this.options.forceFake)
      this._constraints.fake = true;
  });
  /* jshint camelcase:true */

  /**
   * Initiates an outgoing connection.
   * @param  {Object}  constraints  Media constraints
   * @return {WebRTC}
   * @event  `local-stream:ready` {LocalMediaStream}
   * @public
   */
  WebRTC.prototype.initiate = function(constraints) {
    this.state.initiate();
    this.constraints = constraints;

    if (!this.constraints.video && !this.constraints.audio)
      return this._createOffer();

    return this._getMedia(function(localStream) {
      this.trigger('local-stream:ready', localStream)
          ._addLocalStream(localStream)
          ._createOffer();
    });
  };

  /**
   * Upgrades an established peer connection with new constraints.
   * @param  {Object} constraints User media constraints
   * @return {WebRTC}
   */
  WebRTC.prototype.upgrade = function(constraints) {
    this.state.upgrade();

    if (!constraints || typeof constraints !== 'object')
      throw new Error('upgrading needs new media constraints');
    this.constraints = constraints;

    // XXX: renegotiate connection once supported by the WebRTC API;
    //      right now, reinitialize the current peer connection.
    this.once('connection-terminated', function() {
      this._setupPeerConnection();
      this.once('ice:connected', function() {
        this.trigger('connection-upgraded');
      }).initiate(constraints);
    }).terminate();

    // force state to pending as we're actually waiting for pc reinitiation
    this.state.current = "pending";

    return this;
  };

  /**
   * Establishes the connection using the received answer.
   * @param  {Object} answer
   * @return {WebRTC}
   * @event  `connection-established`
   * @public
   */
  WebRTC.prototype.establish = function(answer) {
    this.state.establish();

    this.pc.setRemoteDescription(
      new mozRTCSessionDescription(answer),
      this._prepareEstablishment.bind(this),
      this._handleError.bind(this, 'Unable to set remote answer description')
    );

    return this;
  };

  /**
   * Answers an incoming initial or upgraded connection offer.
   * @param  {Object}  offer  Connection offer
   * @return {WebRTC}
   * @public
   */
  WebRTC.prototype.answer = function(offer) {
    this.state.answer();
    this.constraints = this._parseOfferConstraints(offer);

    if (!this.constraints.video && !this.constraints.audio)
      return this._prepareAnswer(offer);

    return this._getMedia(function(localStream) {
      this.trigger('local-stream:ready', localStream)
          ._addLocalStream(localStream)
          ._prepareAnswer(offer);
    });
  };

  /**
   * Resets the peer connection.
   * @return {WebRTC}
   */
  WebRTC.prototype.reset = function() {
    this.state.reset();
    this._setupPeerConnection();

    return this;
  };

  /**
   * Sends data over data channel.
   * @param  {Object} data
   * @return {WebRTC}
   * @event  `dc:message-out` {Object}
   * @public
   */
  WebRTC.prototype.send = function(data) {
    if (this.state.current !== 'ongoing')
      return this._handleError("Not connected, can't send data");

    try {
      this.dc.send(data);
    } catch(err) {
      return this._handleError("Couldn't send data", err);
    }

    return this.trigger('dc:message-out', data);
  };

  /**
   * Terminates an ongoing communication.
   * @return {WebRTC}
   * @event  `connection-terminated`
   * @public
   */
  WebRTC.prototype.terminate = function() {
    this.state.terminate();

    if (this.pc.signalingState === 'closed')
      return this;

    this.once('ice:closed', this.trigger.bind(this, 'connection-terminated'));
    this.pc.close();

    return this;
  };

  // "private" methods

  /**
   * Adds a local media stream to the peer connection.
   * @param  {LocalMediaStream} localStream
   * @return {WebRTC}
   */
  WebRTC.prototype._addLocalStream = function(localStream) {
    try {
      this.pc.addStream(localStream);
    } catch (err) {
      return this._handleError('Unable to add local stream', err);
    }

    return this;
  };

  /**
   * Creates an answer.
   * @return {WebRTC}
   * @private
   */
  WebRTC.prototype._createAnswer = function() {
    this.pc.createAnswer(
      this._setAnswerDescription.bind(this),
      this._handleError.bind(this, 'Unable to create answer')
    );

    return this;
  };

  /**
   * Creates an offer.
   * @return {WebRTC}
   * @private
   */
  WebRTC.prototype._createOffer = function() {
    this.pc.createOffer(
      this._setOfferDescription.bind(this),
      this._handleError.bind(this, 'Unable to create offer')
    );

    return this;
  };

  /**
   * gUM proxy.
   * @param  {Function} callback    Callback
   * @return {WebRTC}
   * @private
   */
  WebRTC.prototype._getMedia = function(callback) {
    navigator.mozGetUserMedia(
      this.constraints,
      callback.bind(this),
      this._handleError.bind(this, 'Unable to get user media, constraints=' +
                                   this.constraints.toSource())
    );

    return this;
  };

  /**
   * Handles a WebRTC error.
   * @param  {String} message
   * @param  {Object} err
   * @return {WebRTC}
   * @private
   */
  WebRTC.prototype._handleError = function(description, err) {
    var messageParts = [description];

    if (err && typeof err === "object")
      messageParts = messageParts.concat([':', err.name, err.message]);

    return this.trigger('error', messageParts.join(' '));
  };

  /**
   * Executed when a remote media stream is added to current peer connection.
   * @param  {Event} event
   */
  WebRTC.prototype._onAddStream = function(event) {
    this.trigger('remote-stream:ready', event.stream);
  };

  /**
   * Executed on incoming data channel.
   * @param  {RTCDataChannelEvent} event
   */
  WebRTC.prototype._onDataChannel = function(event) {
    var eventsMap = {
      onopen: 'dc:open',
      onmessage: 'dc:message-in',
      onerror: 'dc:error',
      onclose: 'dc:close'
    };

    for (var handler in eventsMap)
      event.channel[handler] = this.trigger.bind(this, eventsMap[handler]);

    this.trigger('dc:ready', event.channel);
  };

  /**
   * Executed when the ICE connection state changes.
   * @return {Event} event
   */
  WebRTC.prototype._onIceConnectionStateChange = function() {
    this.trigger('ice:' + this.pc.iceConnectionState);
    this.trigger('ice:change', this.pc.iceConnectionState);
  };

  /**
   * Executed when a remote media stream is removed from the current peer
   * connection.
   * @param  {Event} event
   */
  WebRTC.prototype._onRemoveStream = function(event) {
    this.trigger('remote-stream:remove', event.stream);
  };

  /**
   * Executed when the current signaling state changes.
   * @param  {Event} event
   */
  WebRTC.prototype._onSignalingStateChange = function(event) {
    this.trigger('signaling:' + event);
    this.trigger('signaling:change', event);
  };

  /**
   * Extracts media constraints from an offer SDP.
   * @param  {Object}  offer  Offer object
   * @return {Object}         Media constraints object
   * @private
   */
  WebRTC.prototype._parseOfferConstraints = function(offer) {
    var sdp = offer && offer.sdp;

    if (!sdp)
      return this._handleError('No SDP offer to parse');

    return {
      video: sdp.contains('\nm=video '),
      audio: sdp.contains('\nm=audio ')
    };
  };

  /**
   * Prepares connection establishment.
   */
  WebRTC.prototype._prepareEstablishment = function() {
    this.on('ice:connected', this.trigger.bind(this, 'connection-established'));
  };

  /**
   * Prepares an answer based on passed offer.
   * @param  {Object} offer
   * @return {WebRTC}
   */
  WebRTC.prototype._prepareAnswer = function(offer) {
    this.pc.setRemoteDescription(
      new mozRTCSessionDescription(offer),
      this._createAnswer.bind(this),
      this._handleError.bind(this, 'Unable to set remote offer description')
    );

    return this;
  };

  /**
   * Sets local session description from a connection answer object.
   * @param  {Object} answer
   * @return {WebRTC}
   * @event  `answer-ready` {Object}
   * @private
   */
  WebRTC.prototype._setAnswerDescription = function(answer) {
    this.pc.setLocalDescription(
      answer,
      this.trigger.bind(this, 'answer-ready', answer),
      this._handleError.bind(this, 'Unable to set local answer description')
    );

    return this;
  };

  /**
   * Sets local session description from a connection offer object.
   * @param  {Object} offer
   * @return {WebRTC}
   * @event  `offer-ready` {Object}
   * @private
   */
  WebRTC.prototype._setOfferDescription = function(offer) {
    this.pc.setLocalDescription(
      offer,
      this.trigger.bind(this, 'offer-ready', offer),
      this._handleError.bind(this, 'Unable to set local offer description')
    );

    return this;
  };

  /**
   * Configures a peer connection, registering local event listeners.
   *
   * @param {RTCPeerConnection} pc
   */
  WebRTC.prototype._setupPeerConnection = function() {
    var pc = new mozRTCPeerConnection(this.options.iceServers);
    var dc = pc.createDataChannel('dc', {});

    pc.onaddstream = this._onAddStream.bind(this);
    pc.ondatachannel = this._onDataChannel.bind(this);
    pc.oniceconnectionstatechange = this._onIceConnectionStateChange.bind(this);
    pc.onremovestream = this._onRemoveStream.bind(this);
    pc.onsignalingstatechange = this._onSignalingStateChange.bind(this);

    this.pc = pc;
    this.dc = dc;

    this.state.current = "ready";
  };
})(this);
<commit_msg>sync with master<commit_after>/* global StateMachine, Backbone, _,
          mozRTCPeerConnection, mozRTCSessionDescription */

(function(exports) {
  "use strict";

  var defaultConstraints = {video: false, audio: false, fake: false};

  /**
   * WebRTC object constructor.
   *
   * Options are:
   * - {Object}  iceServers: Peer connection ICE servers (default: {})
   * - {Boolean} forceFake:  Forces fake media streams (default: false)
   *
   * @param {Object}                   options  Options
   */
  function WebRTC(options) {
    this._constraints = {};
    this.options = options || {};

    this.state = StateMachine.create({
      initial: 'ready',
      events: [
        {name: 'initiate',  from: 'ready',     to: 'pending'},
        {name: 'establish', from: 'pending',   to: 'ongoing'},
        {name: 'upgrade',   from: 'ongoing',   to: 'pending'},
        {name: 'answer',    from: ['ready',
                                   'ongoing'], to: 'ongoing'},
        {name: 'terminate', from: '*',         to: 'terminated'},
        {name: 'reset',     from: '*',         to: 'ready'}
      ],
      callbacks: {
        onenterstate: function(event, from, to) {
          this.trigger("change:state", to, from, event);
          this.trigger("state:" + event);
          this.trigger("state:to:" + to);
        }.bind(this)
      }
    });

    this._setupPeerConnection();
  }
  exports.WebRTC = WebRTC;

  _.extend(WebRTC.prototype, Backbone.Events);

  // public API

  /* jshint camelcase:false */
  WebRTC.prototype.__defineGetter__('constraints', function() {
    var _constraints = this._constraints || defaultConstraints;

    if (!!this.options.forceFake)
      _constraints.fake = true;

    return _constraints;
  });

  WebRTC.prototype.__defineSetter__('constraints', function(constraints) {
    this._constraints = constraints || defaultConstraints;

    if (!!this.options.forceFake)
      this._constraints.fake = true;
  });
  /* jshint camelcase:true */

  /**
   * Initiates an outgoing connection.
   * @param  {Object}  constraints  Media constraints
   * @return {WebRTC}
   * @event  `local-stream:ready` {LocalMediaStream}
   * @public
   */
  WebRTC.prototype.initiate = function(constraints) {
    this.state.initiate();
    this.constraints = constraints;

    if (!this.constraints.video && !this.constraints.audio)
      return this._createOffer();

    return this._getMedia(function(localStream) {
      this.trigger('local-stream:ready', localStream)
          ._addLocalStream(localStream)
          ._createOffer();
    });
  };

  /**
   * Upgrades an established peer connection with new constraints.
   * @param  {Object} constraints User media constraints
   * @return {WebRTC}
   */
  WebRTC.prototype.upgrade = function(constraints) {
    this.state.upgrade();

    if (!constraints || typeof constraints !== 'object')
      throw new Error('upgrading needs new media constraints');
    this.constraints = constraints;

    // XXX: renegotiate connection once supported by the WebRTC API;
    //      right now, reinitialize the current peer connection.
    this.once('connection-terminated', function() {
      this._setupPeerConnection();
      this.once('ice:connected', function() {
        this.trigger('connection-upgraded');
      }).initiate(constraints);
    }).terminate();

    // force state to pending as we're actually waiting for pc reinitiation
    this.state.current = "pending";

    return this;
  };

  /**
   * Establishes the connection using the received answer.
   * @param  {Object} answer
   * @return {WebRTC}
   * @event  `connection-established`
   * @public
   */
  WebRTC.prototype.establish = function(answer) {
    this.state.establish();
    this._prepareEstablishment();

    this.pc.setRemoteDescription(
      new mozRTCSessionDescription(answer),
      function() {},
      this._handleError.bind(this, 'Unable to set remote answer description')
    );

    return this;
  };

  /**
   * Answers an incoming initial or upgraded connection offer.
   * @param  {Object}  offer  Connection offer
   * @return {WebRTC}
   * @public
   */
  WebRTC.prototype.answer = function(offer) {
    this.state.answer();
    this.constraints = this._parseOfferConstraints(offer);

    if (!this.constraints.video && !this.constraints.audio)
      return this._prepareAnswer(offer);

    return this._getMedia(function(localStream) {
      this.trigger('local-stream:ready', localStream)
          ._addLocalStream(localStream)
          ._prepareAnswer(offer);
    });
  };

  /**
   * Resets the peer connection.
   * @return {WebRTC}
   */
  WebRTC.prototype.reset = function() {
    this.state.reset();
    this._setupPeerConnection();

    return this;
  };

  /**
   * Sends data over data channel.
   * @param  {Object} data
   * @return {WebRTC}
   * @event  `dc:message-out` {Object}
   * @public
   */
  WebRTC.prototype.send = function(data) {
    if (this.state.current !== 'ongoing')
      return this._handleError("Not connected, can't send data");

    try {
      this.dc.send(data);
    } catch(err) {
      return this._handleError("Couldn't send data", err);
    }

    return this.trigger('dc:message-out', data);
  };

  /**
   * Terminates an ongoing communication.
   * @return {WebRTC}
   * @event  `connection-terminated`
   * @public
   */
  WebRTC.prototype.terminate = function() {
    this.state.terminate();

    if (this.pc.signalingState === 'closed')
      return this;

    this.once('ice:closed', this.trigger.bind(this, 'connection-terminated'));
    this.pc.close();

    return this;
  };

  // "private" methods

  /**
   * Adds a local media stream to the peer connection.
   * @param  {LocalMediaStream} localStream
   * @return {WebRTC}
   */
  WebRTC.prototype._addLocalStream = function(localStream) {
    try {
      this.pc.addStream(localStream);
    } catch (err) {
      return this._handleError('Unable to add local stream', err);
    }

    return this;
  };

  /**
   * Creates an answer.
   * @return {WebRTC}
   * @private
   */
  WebRTC.prototype._createAnswer = function() {
    this.pc.createAnswer(
      this._setAnswerDescription.bind(this),
      this._handleError.bind(this, 'Unable to create answer')
    );

    return this;
  };

  /**
   * Creates an offer.
   * @return {WebRTC}
   * @private
   */
  WebRTC.prototype._createOffer = function() {
    this.pc.createOffer(
      this._setOfferDescription.bind(this),
      this._handleError.bind(this, 'Unable to create offer')
    );

    return this;
  };

  /**
   * gUM proxy.
   * @param  {Function} callback    Callback
   * @return {WebRTC}
   * @private
   */
  WebRTC.prototype._getMedia = function(callback) {
    navigator.mozGetUserMedia(
      this.constraints,
      callback.bind(this),
      this._handleError.bind(this, 'Unable to get user media, constraints=' +
                                   this.constraints.toSource())
    );

    return this;
  };

  /**
   * Handles a WebRTC error.
   * @param  {String} message
   * @param  {Object} err
   * @return {WebRTC}
   * @private
   */
  WebRTC.prototype._handleError = function(description, err) {
    var messageParts = [description];

    if (err && typeof err === "object")
      messageParts = messageParts.concat([':', err.name, err.message]);

    return this.trigger('error', messageParts.join(' '));
  };

  /**
   * Executed when a remote media stream is added to current peer connection.
   * @param  {Event} event
   */
  WebRTC.prototype._onAddStream = function(event) {
    this.trigger('remote-stream:ready', event.stream);
  };

  /**
   * Executed on incoming data channel.
   * @param  {RTCDataChannelEvent} event
   */
  WebRTC.prototype._onDataChannel = function(event) {
    var eventsMap = {
      onopen: 'dc:open',
      onmessage: 'dc:message-in',
      onerror: 'dc:error',
      onclose: 'dc:close'
    };

    for (var handler in eventsMap)
      event.channel[handler] = this.trigger.bind(this, eventsMap[handler]);

    this.trigger('dc:ready', event.channel);
  };

  /**
   * Executed when the ICE connection state changes.
   * @return {Event} event
   */
  WebRTC.prototype._onIceConnectionStateChange = function() {
    this.trigger('ice:' + this.pc.iceConnectionState);
    this.trigger('ice:change', this.pc.iceConnectionState);
  };

  /**
   * Executed when a remote media stream is removed from the current peer
   * connection.
   * @param  {Event} event
   */
  WebRTC.prototype._onRemoveStream = function(event) {
    this.trigger('remote-stream:remove', event.stream);
  };

  /**
   * Executed when the current signaling state changes.
   * @param  {Event} event
   */
  WebRTC.prototype._onSignalingStateChange = function(event) {
    this.trigger('signaling:' + event);
    this.trigger('signaling:change', event);
  };

  /**
   * Extracts media constraints from an offer SDP.
   * @param  {Object}  offer  Offer object
   * @return {Object}         Media constraints object
   * @private
   */
  WebRTC.prototype._parseOfferConstraints = function(offer) {
    var sdp = offer && offer.sdp;

    if (!sdp)
      return this._handleError('No SDP offer to parse');

    return {
      video: sdp.contains('\nm=video '),
      audio: sdp.contains('\nm=audio ')
    };
  };

  /**
   * Prepares connection establishment.
   */
  WebRTC.prototype._prepareEstablishment = function() {
    this.on('ice:connected', this.trigger.bind(this, 'connection-established'));
  };

  /**
   * Prepares an answer based on passed offer.
   * @param  {Object} offer
   * @return {WebRTC}
   */
  WebRTC.prototype._prepareAnswer = function(offer) {
    this.pc.setRemoteDescription(
      new mozRTCSessionDescription(offer),
      this._createAnswer.bind(this),
      this._handleError.bind(this, 'Unable to set remote offer description')
    );

    return this;
  };

  /**
   * Sets local session description from a connection answer object.
   * @param  {Object} answer
   * @return {WebRTC}
   * @event  `answer-ready` {Object}
   * @private
   */
  WebRTC.prototype._setAnswerDescription = function(answer) {
    this.pc.setLocalDescription(
      answer,
      this.trigger.bind(this, 'answer-ready', answer),
      this._handleError.bind(this, 'Unable to set local answer description')
    );

    return this;
  };

  /**
   * Sets local session description from a connection offer object.
   * @param  {Object} offer
   * @return {WebRTC}
   * @event  `offer-ready` {Object}
   * @private
   */
  WebRTC.prototype._setOfferDescription = function(offer) {
    this.pc.setLocalDescription(
      offer,
      this.trigger.bind(this, 'offer-ready', offer),
      this._handleError.bind(this, 'Unable to set local offer description')
    );

    return this;
  };

  /**
   * Configures a peer connection, registering local event listeners.
   *
   * @param {RTCPeerConnection} pc
   */
  WebRTC.prototype._setupPeerConnection = function() {
    var pc = new mozRTCPeerConnection(this.options.iceServers);
    var dc = pc.createDataChannel('dc', {});

    pc.onaddstream = this._onAddStream.bind(this);
    pc.ondatachannel = this._onDataChannel.bind(this);
    pc.oniceconnectionstatechange = this._onIceConnectionStateChange.bind(this);
    pc.onremovestream = this._onRemoveStream.bind(this);
    pc.onsignalingstatechange = this._onSignalingStateChange.bind(this);

    this.pc = pc;
    this.dc = dc;

    this.state.current = "ready";
  };
})(this);
<|endoftext|>


<commit_before>export const loadState = () => {
  try {
    const state = localStorage.getItem('state')
    if (state) {
      return JSON.parse(state)
    }

    return undefined
  } catch (error) {
    return undefined
  }
}

export const saveState = (state) => {
  if (!state) {
    localStorage.setItem('state', undefined)
  }

  try {
    localStorage.setItem('state', JSON.stringify(state))
  } catch (error) {
  }
}
<commit_msg>:sparkles: Add cache breaker localstorage data<commit_after>const cacheBreakerVersion = process.env.VERSION || 2

export const loadState = () => {
  try {
    const jsonState = localStorage.getItem('state')
    if (jsonState) {
      const state = JSON.parse(jsonState)
      if (state.version !== cacheBreakerVersion) {
        return undefined
      }
      return state
    }

    return undefined
  } catch (error) {
    return undefined
  }
}

export const saveState = (state) => {
  if (!state) {
    localStorage.setItem('state', undefined)
  }

  try {
    state.version = cacheBreakerVersion
    localStorage.setItem('state', JSON.stringify(state))
  } catch (error) {
  }
}
<|endoftext|>