<commit_before>$(document).ready(function() {
  $.each($(".burndownchart"), function(i, burndownChartContainer) {
    constructBurndownChart(burndownChartContainer);
  });
});

function constructBurndownChart(burndownChartContainer) {
  var projectJSONURL = $(burndownChartContainer).attr("data-url");

  d3.json(projectJSONURL, function(projectJSON) {
    var projectChartData = dataForProjectJSON(projectJSON);

    nv.addGraph(function() {
      var projectProgressChart = nv.models.lineChart();

      projectProgressChart.xAxis // chart sub-models (ie. xAxis, yAxis, etc) when accessed directly, return themselves, not the partent chart, so need to chain separately
          .axisLabel('Week (w)')
          .tickFormat(d3.format('d'));

      projectProgressChart.yAxis
          .axisLabel('Tasks (t)')
          .tickFormat(d3.format(',.2f'));

      d3.select($(burndownChartContainer).children("svg")[0])
          .datum(projectChartData)
        .transition().duration(500)
          .call(projectProgressChart);

      return projectProgressChart;
    });
  });
}

function dataForProjectJSON(projectJSON) {

  // TODO: Get rid of this constant once the project
  // weight is being properly set
  var TASK_WEIGHT = 2;

  // Get the project's template
  var templateProject = projectJSON.project_template;

  // Get the project's start and end date and wrap them as 'moment' objects
  var projectStartDate = moment(templateProject.start_date);
  var projectCompletionDate = moment(new Date(templateProject.end_date));

  var projectTasks = projectJSON.tasks;
  var taskCount = projectTasks.length

  // Determine the starting weight for the project based
  // on the number of tasks in the project and the task
  // weight constant
  var weightCountdown = taskCount * TASK_WEIGHT;
  var remainingWeight = weightCountdown;

  // Set an initial point for the week prior to starting,
  // when no tasks have yet been completed
  var recommendedTaskCompletion = [
    {x: 0, y: weightCountdown}
  ];

  var actualTaskCompletion = [
    {x: 0, y: weightCountdown}
  ];

  $.each(projectTasks, function(i, task){
    // Determine the remaining weight value (i.e. y value for the chart)
    weightCountdown = weightCountdown - TASK_WEIGHT;

    // Determine the week at which the task is to be completed at by comparing
    // the 'due date' to the current
    var taskDueWeek         = moment(task.task_template.recommended_completion_date).diff(projectStartDate, 'weeks');

    if (task.task_status_id == 3) { // 3 = Complete; TODO: Fix

      remainingWeight = remainingWeight - TASK_WEIGHT;
      var taskCompletionDate = null;

      if (task.completion_date != null) {
        var momentedTaskCompletionDate = moment(task.completion_date);
        taskCompletionDate = momentedTaskCompletionDate.diff(projectStartDate) < 0 ? projectStartDate : momentedTaskCompletionDate;
      } else {
        taskCompletionDate = moment(new Date());
      }

      var taskCompletionWeek  = taskCompletionDate.diff(projectStartDate, 'weeks');
      actualTaskCompletion.push({x: taskCompletionWeek, y: remainingWeight});
    }

    recommendedTaskCompletion.push({x: taskDueWeek, y: weightCountdown});
  });

  recommendedTaskCompletion.push({x: projectCompletionDate.diff(projectStartDate, 'weeks'), y: 0});

  return [
    {
      values: recommendedTaskCompletion,
      key: "Recommended",
      color: "#999999"
    },
    {
      values: actualTaskCompletion,
      key: "Completed",
      color: colourForProjectProgress(projectJSON.relative_progress)
    }
  ];
}

function colourForProjectProgress(progress) {
  var colour = null;

  switch (progress)
  {
    case "ahead":
      colour = "#48842c"; // Twitter Bootstrap @green
      break;
    case "on_track":
      colour = "#049cdb"; // Twitter Bootstrap @blue
      break;
    case "behind":
      colour = "#f89406"; // Twitter Bootstrap @orange
      break;
    case "danger":
      colour = "#9d261d"; // Twitter Bootstrap @red
      break;
    case "doomed":
      colour = "#ffffff";
      break;
  }

  return colour;
}<commit_msg>Changed burndown chart to use project start date as the completion time if no completion time explicitly set, so as to avoid having it show a negative x-axis value if the project has not started<commit_after>$(document).ready(function() {
  $.each($(".burndownchart"), function(i, burndownChartContainer) {
    constructBurndownChart(burndownChartContainer);
  });
});

function constructBurndownChart(burndownChartContainer) {
  var projectJSONURL = $(burndownChartContainer).attr("data-url");

  d3.json(projectJSONURL, function(projectJSON) {
    var projectChartData = dataForProjectJSON(projectJSON);

    nv.addGraph(function() {
      var projectProgressChart = nv.models.lineChart();

      projectProgressChart.xAxis // chart sub-models (ie. xAxis, yAxis, etc) when accessed directly, return themselves, not the partent chart, so need to chain separately
          .axisLabel('Week (w)')
          .tickFormat(d3.format('d'));

      projectProgressChart.yAxis
          .axisLabel('Tasks (t)')
          .tickFormat(d3.format(',.2f'));

      d3.select($(burndownChartContainer).children("svg")[0])
          .datum(projectChartData)
        .transition().duration(500)
          .call(projectProgressChart);

      nv.utils.windowResize(function() { d3.select(".burndownchart").call(chart) });

      return projectProgressChart;
    });
  });
}

function dataForProjectJSON(projectJSON) {

  // TODO: Get rid of this constant once the project
  // weight is being properly set
  var TASK_WEIGHT = 2;

  // Get the project's template
  var templateProject = projectJSON.project_template;

  // Get the project's start and end date and wrap them as 'moment' objects
  var projectStartDate = moment(templateProject.start_date);
  var projectCompletionDate = moment(new Date(templateProject.end_date));

  var projectTasks = projectJSON.tasks;
  var taskCount = projectTasks.length

  // Determine the starting weight for the project based
  // on the number of tasks in the project and the task
  // weight constant
  var weightCountdown = taskCount * TASK_WEIGHT;
  var remainingWeight = weightCountdown;

  // Set an initial point for the week prior to starting,
  // when no tasks have yet been completed
  var recommendedTaskCompletion = [
    {x: 0, y: weightCountdown}
  ];

  var actualTaskCompletion = [
    {x: 0, y: weightCountdown}
  ];

  $.each(projectTasks, function(i, task){
    // Determine the remaining weight value (i.e. y value for the chart)
    weightCountdown = weightCountdown - TASK_WEIGHT;

    // Determine the week at which the task is to be completed at by comparing
    // the 'due date' to the current
    var taskDueWeek         = moment(task.task_template.recommended_completion_date).diff(projectStartDate, 'weeks');

    if (task.task_status_id == 3) { // 3 = Complete; TODO: Fix

      remainingWeight = remainingWeight - TASK_WEIGHT;
      var taskCompletionDate = null;

      if (task.completion_date != null) {
        var momentedTaskCompletionDate = moment(task.completion_date);
        taskCompletionDate = momentedTaskCompletionDate.diff(projectStartDate) < 0 ? projectStartDate : momentedTaskCompletionDate;
      } else {
        taskCompletionDate = moment(new Date());
      }

      var taskCompletionWeek  = taskCompletionDate.diff(projectStartDate, 'weeks');
      actualTaskCompletion.push({x: taskCompletionWeek, y: remainingWeight});
    }

    recommendedTaskCompletion.push({x: taskDueWeek, y: weightCountdown});
  });

  recommendedTaskCompletion.push({x: projectCompletionDate.diff(projectStartDate, 'weeks'), y: 0});

  return [
    {
      values: recommendedTaskCompletion,
      key: "Recommended",
      color: "#999999"
    },
    {
      values: actualTaskCompletion,
      key: "Completed",
      color: colourForProjectProgress(projectJSON.relative_progress)
    }
  ];
}

function colourForProjectProgress(progress) {
  var colour = null;

  switch (progress)
  {
    case "ahead":
      colour = "#48842c"; // Twitter Bootstrap @green
      break;
    case "on_track":
      colour = "#049cdb"; // Twitter Bootstrap @blue
      break;
    case "behind":
      colour = "#f89406"; // Twitter Bootstrap @orange
      break;
    case "danger":
      colour = "#9d261d"; // Twitter Bootstrap @red
      break;
    case "doomed":
      colour = "#ffffff";
      break;
  }

  return colour;
}<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Object.extend({
  bytes: 0,

  kb: Ember.computed('bytes', function () {
    return this.get('bytes') / 1000;
  }),
  mb: Ember.computed('bytes', function () {
    return this.get('bytes') / Math.pow(1000, 2);
  }),
  gb: Ember.computed('bytes', function () {
    return this.get('bytes') / Math.pow(1000, 3);
  }),
  tb: Ember.computed('bytes', function () {
    return this.get('bytes') / Math.pow(1000, 4);
  }),
  pb: Ember.computed('bytes', function () {
    return this.get('bytes') / Math.pow(1000, 5);
  }),

  kbWithUnits: Ember.computed('bytes', function () {
    return this.get('kb') + ' KB';
  }),
  mbWithUnits: Ember.computed('bytes', function () {
    return this.get('mb') + ' MB';
  }),
  gbWithUnits: Ember.computed('bytes', function () {
    return this.get('gb') + ' GB';
  }),
  tbWithUnits: Ember.computed('bytes', function () {
    return this.get('tb') + ' TB';
  }),
  pbWithUnits: Ember.computed('bytes', function () {
    return this.get('pb') + ' PB';
  })

});
<commit_msg>Added humanReadable call to (attempt) to pick the most sane representation of the byte value<commit_after>import Ember from 'ember';

export default Ember.Object.extend({
  bytes: 0,

  kb: Ember.computed('bytes', function () {
    return this.get('bytes') / 1000;
  }),
  mb: Ember.computed('bytes', function () {
    return this.get('bytes') / Math.pow(1000, 2);
  }),
  gb: Ember.computed('bytes', function () {
    return this.get('bytes') / Math.pow(1000, 3);
  }),
  tb: Ember.computed('bytes', function () {
    return this.get('bytes') / Math.pow(1000, 4);
  }),
  pb: Ember.computed('bytes', function () {
    return this.get('bytes') / Math.pow(1000, 5);
  }),

  bytesWithUnits: Ember.computed('bytes', function () {
    return this.get('kb') + ' B';
  }),
  kbWithUnits: Ember.computed('bytes', function () {
    return this.get('kb') + ' KB';
  }),
  mbWithUnits: Ember.computed('bytes', function () {
    return this.get('mb') + ' MB';
  }),
  gbWithUnits: Ember.computed('bytes', function () {
    return this.get('gb') + ' GB';
  }),
  tbWithUnits: Ember.computed('bytes', function () {
    return this.get('tb') + ' TB';
  }),
  pbWithUnits: Ember.computed('bytes', function () {
    return this.get('pb') + ' PB';
  }),

  humanReadable: Ember.computed('bytes', function () {
    var sizes = ['bytes', 'kb', 'gb', 'tb', 'pb'];
    var that = this;
    var result;

    sizes.some(function (size) {
      if (that.get(size) < 1000) {
        result = size;
        return true;
      }

      return false;
    });

    return this.get(result + 'WithUnits');
  })

});
<|endoftext|>


<commit_before>"use strict";

$.extend($.fn.select2.defaults, {
    formatNoMatches: function () { return "Inga träffar"; },
    formatInputTooShort: function (input, min) { var n = min - input.length; return "Var god skriv in " + n + (n>1 ? " till tecken" : " tecken till"); },
    formatInputTooLong: function (input, max) { var n = input.length - max; return "Var god sudda ut " + n + " tecken"; },
    formatSelectionTooBig: function (limit) { return "Du kan max välja " + limit + " element"; },
    formatLoadMore: function (pageNumber) { return "Laddar fler resultat..."; },
    formatSearching: function () { return "Söker..."; }
});

// Datatables defaults
//
// Bootstrap layout

/* API method to get paging information */
$.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
{
    return {
        "iStart":         oSettings._iDisplayStart,
        "iEnd":           oSettings.fnDisplayEnd(),
        "iLength":        oSettings._iDisplayLength,
        "iTotal":         oSettings.fnRecordsTotal(),
        "iFilteredTotal": oSettings.fnRecordsDisplay(),
        "iPage":          Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ),
        "iTotalPages":    Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength )
    };
}

/* Bootstrap style pagination control */
$.extend($.fn.dataTableExt.oPagination, {
    "bootstrap": {
        "fnInit": function( oSettings, nPaging, fnDraw ) {
            var oLang = oSettings.oLanguage.oPaginate;
            var fnClickHandler = function ( e ) {
                e.preventDefault();
                if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) {
                    fnDraw( oSettings );
                }
            };

            $(nPaging).addClass('pagination').append(
                '
'+
                '
← '+oLang.sPrevious+'
'+
                '
'+oLang.sNext+' →
'+
                '
'
            );
            var els = $('a', nPaging);
            $(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler );
            $(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler );
        },

        "fnUpdate": function ( oSettings, fnDraw ) {
            var iListLength = 5;
            var oPaging = oSettings.oInstance.fnPagingInfo();
            var an = oSettings.aanFeatures.p;
            var i, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2);

            if ( oPaging.iTotalPages < iListLength) {
                iStart = 1;
                iEnd = oPaging.iTotalPages;
            }
            else if ( oPaging.iPage <= iHalf ) {
                iStart = 1;
                iEnd = iListLength;
            } else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) {
                iStart = oPaging.iTotalPages - iListLength + 1;
                iEnd = oPaging.iTotalPages;
            } else {
                iStart = oPaging.iPage - iHalf + 1;
                iEnd = iStart + iListLength - 1;
            }

            for ( i=0 ; i<an.length ; i++ ) {
                // Remove the middle elements
                $('li:gt(0)', an[i]).filter(':not(:last)').remove();

                // Add the new list items and their event handlers
                for ( j=iStart ; j<=iEnd ; j++ ) {
                    sClass = (j==oPaging.iPage+1) ? 'class="active"' : '';
                    $('
'+j+'
')
                    .insertBefore( $('li:last', an[i])[0] )
                    .bind('click', function (e) {
                        e.preventDefault();
                        oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength;
                        fnDraw( oSettings );
                    } );
                }

                // Add / remove disabled classes from the static elements
                if ( oPaging.iPage === 0 ) {
                    $('li:first', an[i]).addClass('disabled');
                } else {
                    $('li:first', an[i]).removeClass('disabled');
                }

                if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
                    $('li:last', an[i]).addClass('disabled');
                } else {
                    $('li:last', an[i]).removeClass('disabled');
                }
            }
        }
    }
});

$.extend($.fn.dataTable.defaults, {
    "sDom": "R<'row'<'span8'l><'span8'f>r>t<'row'<'span8'i><'span8'p>>",
    "sPaginationType": "bootstrap",
    "oLanguage": {
        "sEmptyTable": "Tabellen innehåller ingen data",
        "sInfo": "Visar _START_ till _END_ av totalt _TOTAL_ rader",
        "sInfoEmpty": "Visar 0 till 0 av totalt 0 rader",
        "sInfoFiltered": "(filtrerade från totalt _MAX_ rader)",
        "sInfoPostFix": "",
        "sInfoThousands": ",",
        "sLengthMenu": "Visa _MENU_ rader",
        "sLoadingRecords": "Laddar...",
        "sProcessing": "Bearbetar...",
        "sSearch": "Sök:",
        "sUrl": "",
        "sZeroRecords": "Hittade inga matchande resultat",
        "oPaginate": {
            "sFirst": "Första",
            "sLast": "Sista",
            "sNext": "Nästa",
            "sPrevious": "Föregående"
        },
        "oAria": {
            "sSortAscending": ": aktivera för att sortera kolumnen i stigande ordning",
            "sSortDescending": ": aktivera för att sortera kolumnen i fallande ordning"
        }
    }
});

$.extend($.fn.dataTableExt.oStdClasses, {
    "sSortAsc": "header headerSortDown",
    "sSortDesc": "header headerSortUp",
    "sSortable": "header"
});
<commit_msg>Changed the default layout of dataTables<commit_after>"use strict";

$.extend($.fn.select2.defaults, {
    formatNoMatches: function () { return "Inga träffar"; },
    formatInputTooShort: function (input, min) { var n = min - input.length; return "Var god skriv in " + n + (n>1 ? " till tecken" : " tecken till"); },
    formatInputTooLong: function (input, max) { var n = input.length - max; return "Var god sudda ut " + n + " tecken"; },
    formatSelectionTooBig: function (limit) { return "Du kan max välja " + limit + " element"; },
    formatLoadMore: function (pageNumber) { return "Laddar fler resultat..."; },
    formatSearching: function () { return "Söker..."; }
});

// Datatables defaults
//
// Bootstrap layout

/* API method to get paging information */
$.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
{
    return {
        "iStart":         oSettings._iDisplayStart,
        "iEnd":           oSettings.fnDisplayEnd(),
        "iLength":        oSettings._iDisplayLength,
        "iTotal":         oSettings.fnRecordsTotal(),
        "iFilteredTotal": oSettings.fnRecordsDisplay(),
        "iPage":          Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ),
        "iTotalPages":    Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength )
    };
}

/* Bootstrap style pagination control */
$.extend($.fn.dataTableExt.oPagination, {
    "bootstrap": {
        "fnInit": function( oSettings, nPaging, fnDraw ) {
            var oLang = oSettings.oLanguage.oPaginate;
            var fnClickHandler = function ( e ) {
                e.preventDefault();
                if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) {
                    fnDraw( oSettings );
                }
            };

            $(nPaging).addClass('pagination').append(
                '
'+
                '
← '+oLang.sPrevious+'
'+
                '
'+oLang.sNext+' →
'+
                '
'
            );
            var els = $('a', nPaging);
            $(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler );
            $(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler );
        },

        "fnUpdate": function ( oSettings, fnDraw ) {
            var iListLength = 5;
            var oPaging = oSettings.oInstance.fnPagingInfo();
            var an = oSettings.aanFeatures.p;
            var i, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2);

            if ( oPaging.iTotalPages < iListLength) {
                iStart = 1;
                iEnd = oPaging.iTotalPages;
            }
            else if ( oPaging.iPage <= iHalf ) {
                iStart = 1;
                iEnd = iListLength;
            } else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) {
                iStart = oPaging.iTotalPages - iListLength + 1;
                iEnd = oPaging.iTotalPages;
            } else {
                iStart = oPaging.iPage - iHalf + 1;
                iEnd = iStart + iListLength - 1;
            }

            for ( i=0 ; i<an.length ; i++ ) {
                // Remove the middle elements
                $('li:gt(0)', an[i]).filter(':not(:last)').remove();

                // Add the new list items and their event handlers
                for ( j=iStart ; j<=iEnd ; j++ ) {
                    sClass = (j==oPaging.iPage+1) ? 'class="active"' : '';
                    $('
'+j+'
')
                    .insertBefore( $('li:last', an[i])[0] )
                    .bind('click', function (e) {
                        e.preventDefault();
                        oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength;
                        fnDraw( oSettings );
                    } );
                }

                // Add / remove disabled classes from the static elements
                if ( oPaging.iPage === 0 ) {
                    $('li:first', an[i]).addClass('disabled');
                } else {
                    $('li:first', an[i]).removeClass('disabled');
                }

                if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
                    $('li:last', an[i]).addClass('disabled');
                } else {
                    $('li:last', an[i]).removeClass('disabled');
                }
            }
        }
    }
});

$.extend($.fn.dataTable.defaults, {
    "sDom": "R<'row'<'span6'f><'span6'l>r>t<'row'<'span6'i><'span6'p>>",
    "sPaginationType": "bootstrap",
    "oLanguage": {
        "sEmptyTable": "Tabellen innehåller ingen data",
        "sInfo": "Visar _START_ till _END_ av totalt _TOTAL_ rader",
        "sInfoEmpty": "Visar 0 till 0 av totalt 0 rader",
        "sInfoFiltered": "(filtrerade från totalt _MAX_ rader)",
        "sInfoPostFix": "",
        "sInfoThousands": ",",
        "sLengthMenu": "Visa _MENU_ rader",
        "sLoadingRecords": "Laddar...",
        "sProcessing": "Bearbetar...",
        "sSearch": "Sök:",
        "sUrl": "",
        "sZeroRecords": "Hittade inga matchande resultat",
        "oPaginate": {
            "sFirst": "Första",
            "sLast": "Sista",
            "sNext": "Nästa",
            "sPrevious": "Föregående"
        },
        "oAria": {
            "sSortAscending": ": aktivera för att sortera kolumnen i stigande ordning",
            "sSortDescending": ": aktivera för att sortera kolumnen i fallande ordning"
        }
    }
});

$.extend($.fn.dataTableExt.oStdClasses, {
    "sSortAsc": "header headerSortDown",
    "sSortDesc": "header headerSortUp",
    "sSortable": "header"
});
<|endoftext|>


<commit_before>import Ember from 'ember';

export default function(cb) {
  return function(params, hash) {
    if (!params || params && params.length === 0) {
      throw new TypeError('ember-moment: Invalid Number of arguments, expected at least 1');
    }

    const datetime = params[0];

    let allowEmpty = hash.allowEmpty || hash['allow-empty'];

    if(allowEmpty === undefined || allowEmpty === null) {
      allowEmpty = !!this.get('globalAllowEmpty');
    }

    if (allowEmpty && [null, '', undefined].indexOf(datetime) > -1) {
      Ember.Logger.warn('ember-moment: an empty value (null, undefined, or "") was passed to moment-format');
      return;
    }

    return cb.apply(this, arguments);
  };
}
<commit_msg>Do not warn about missing date when `allowEmpty`<commit_after>import Ember from 'ember';

const { isBlank } = Ember;

export default function(cb) {
  return function(params, hash) {
    if (!params || params && params.length === 0) {
      throw new TypeError('ember-moment: Invalid Number of arguments, expected at least 1');
    }

    const datetime = params[0];

    let allowEmpty = hash.allowEmpty || hash['allow-empty'];
    if (allowEmpty === undefined || allowEmpty === null) {
      allowEmpty = !!this.get('globalAllowEmpty');
    }

    if (isBlank(datetime)) {
      if (allowEmpty) {
        return;
      } else {
        Ember.Logger.warn('ember-moment: an empty value (null, undefined, or "") was passed to moment-format');
      }
    }

    return cb.apply(this, arguments);
  };
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

window.AudioContext = window.AudioContext||window.webkitAudioContext;
var gAudioContext = new AudioContext();

// --------------------------------------------------------------------- //

class Toggle extends React.Component {
  constructor(props) {
    super(props)

    this.state = {isToggleOn: true};
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
    this.props.onStateChange(this.state.isToggleOn);
  }

  render() {
    return (
      <button onClick={this.handleClick} disabled={this.props.disabled} >
        {this.state.isToggleOn ? (
          ON
        ) : (
          OFF
        )}
      </button>
    )
  }
}

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    console.log('A name was submitted: ' + this.state.value);
    event.preventDefault();
    this.props.onSubmit(this.state.value);
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>

          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />

        <input type="submit" value="Submit" />
      </form>
    );
  }
}

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (

{this.state.date.toLocaleString()}

    );
  }
}

// ----------------------------------------- LIFTED STATE ------------------------------- //

class FreesoundSearch extends React.Component {
  constructor(props) {
    super(props);
    this.state = { searches: ['surf'] };
    this.searchFreesound = this.searchFreesound.bind(this);
    this.handleRemoveSearch = this.handleRemoveSearch.bind(this);
  }

  searchFreesound(inTerm) {
    this.setState(function(prevState) {
      return {
        searches: prevState.searches.concat([inTerm])
      };
    });
  }

  handleRemoveSearch(event) {
    let aSearchTerm = event.target.getAttribute('data-freesound-search');
    this.setState(function(prevState) {
      return {
        searches: prevState.searches.filter(item => (item != aSearchTerm))
      }
    })
  }

  render() {
    return (

        <NameForm onSubmit={this.searchFreesound} />

{this.state.searches.length} searches

        {this.state.searches.map(aSearch => <FreesoundList onRemoveSearch={this.handleRemoveSearch} key={aSearch} term={aSearch} />)}

    )
  }
}

class AudioBufferLoader extends React.Component {
  constructor(props) {
    super(props);
    this.componentDidMount = this.componentDidMount.bind(this);
    this.bufferDecoded = this.bufferDecoded.bind(this);
  }

  bufferDecoded(buffer) {
    console.log('decoded', buffer);
    this.props.onBufferDecoded(buffer);
  }

  bufferError(error) {
    console.log('error', error);
  }

  componentDidMount() {
    fetch(this.props.data['preview-hq-mp3'])
    .then(result => result.blob())
    .then(function(blob) {
      let reader = new FileReader();

      reader.onload = function(event) {
        console.log('file read blob', event.target.result);
        gAudioContext.decodeAudioData(event.target.result, this.bufferDecoded, this.bufferError);
      }.bind(this);

      reader.readAsArrayBuffer(blob);
    }.bind(this));
  }

  componentWillUnmount() {
  }

  render() {
    return (LOADER)
  }
}

class Freesound extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      details: {},
      bufferSource: null
    };
    this.onBufferDecoded = this.onBufferDecoded.bind(this);
    this.startSound = this.startSound.bind(this);
    this.stopSound = this.stopSound.bind(this);
  }

  componentDidMount() {
    fetch('http://localhost:3001/apiv2/sounds/' + this.props.data.id + '?format=json')
    .then(result=>result.json())
    .then(data=>this.setState({details: data}))
  }

  componentWillUnmount() {
    console.log('sound unmount', this.props.data.id);
  }

  onBufferDecoded(buffer) {
    console.log('onBufferDecoded', buffer);
    this.setState({ buffer: buffer });
  }

  startSound() {
    let aBufferSource = gAudioContext.createBufferSource();
    aBufferSource.buffer = this.state.buffer;
    aBufferSource.connect(gAudioContext.destination);
    aBufferSource.start();
    this.setState({ bufferSource: aBufferSource });

    aBufferSource.addEventListener('ended', function(e) {
      console.log('buffer ended', e)
      this.setState({ bufferSource: null });
    }.bind(this));
  }

  stopSound() {
    this.state.bufferSource.stop();
  }

  render() {
    return (

        Sound "{this.props.data.name}" (#{this.props.data.id})
        {this.state.details.previews && (! this.state.buffer) && (<AudioBufferLoader onBufferDecoded={this.onBufferDecoded} data={this.state.details.previews} />)}

          <button data-freesound-id={this.props.data.id} disabled={! this.state.buffer} onClick={this.startSound}>start</button>
          <button data-freesound-id={this.props.data.id} disabled={! this.state.bufferSource} onClick={this.stopSound}>stop</button>

          {this.state.buffer && Math.round(this.state.buffer.duration)}s

        <button data-freesound-id={this.props.data.id} disabled={! this.state.buffer} onClick={this.props.handleRemove}>remove</button>

    )
  }
}

class FreesoundList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {listItems: []};
    this.handleRemove = this.handleRemove.bind(this);
  }

  componentDidMount() {
    fetch('http://localhost:3001/apiv2/search/text?format=json&query=' + this.props.term + '&filter=duration:[1 TO 90]')
    .then(result=>result.json())
    .then(data=>this.setState({listItems: data.results}))
  }

  componentWillUnmount() {
    console.log('freesound list unmount', this.props.term);
  }

  handleRemove(event) {
    let freesoundID = event.target.getAttribute('data-freesound-id');
    console.log('handleRemove', freesoundID);
    this.setState(prevState => ({
      listItems: prevState.listItems.filter(item => item.id != freesoundID)
    }));
  }

  render() {
    return (

{this.props.term}, {this.state.listItems.length} items <button data-freesound-search={this.props.term} onClick={this.props.onRemoveSearch}>remove</button>

          {this.state.listItems.map(item => <Freesound key={item.id} data={item} handleRemove={this.handleRemove} />)}

    )
  }
}

// ---------------------------------------------------------------------------

class App extends Component {
  render() {
    return (

          <Clock />

        <FreesoundSearch />

    );
  }
}

export default App;
<commit_msg>remove dead code<commit_after>import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

window.AudioContext = window.AudioContext||window.webkitAudioContext;
var gAudioContext = new AudioContext();

// --------------------------------------------------------------------- //

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    console.log('A name was submitted: ' + this.state.value);
    event.preventDefault();
    this.props.onSubmit(this.state.value);
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>

          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />

        <input type="submit" value="Submit" />
      </form>
    );
  }
}

// ----------------------------------------- LIFTED STATE ------------------------------- //

class FreesoundSearch extends React.Component {
  constructor(props) {
    super(props);
    this.state = { searches: ['surf'] };
    this.searchFreesound = this.searchFreesound.bind(this);
    this.handleRemoveSearch = this.handleRemoveSearch.bind(this);
  }

  searchFreesound(inTerm) {
    this.setState(function(prevState) {
      return {
        searches: prevState.searches.concat([inTerm])
      };
    });
  }

  handleRemoveSearch(event) {
    let aSearchTerm = event.target.getAttribute('data-freesound-search');
    this.setState(function(prevState) {
      return {
        searches: prevState.searches.filter(item => (item != aSearchTerm))
      }
    })
  }

  render() {
    return (

        <NameForm onSubmit={this.searchFreesound} />

{this.state.searches.length} searches

        {this.state.searches.map(aSearch => <FreesoundList onRemoveSearch={this.handleRemoveSearch} key={aSearch} term={aSearch} />)}

    )
  }
}

class AudioBufferLoader extends React.Component {
  constructor(props) {
    super(props);
    this.componentDidMount = this.componentDidMount.bind(this);
    this.bufferDecoded = this.bufferDecoded.bind(this);
  }

  bufferDecoded(buffer) {
    console.log('decoded', buffer);
    this.props.onBufferDecoded(buffer);
  }

  bufferError(error) {
    console.log('error', error);
  }

  componentDidMount() {
    fetch(this.props.data['preview-hq-mp3'])
    .then(result => result.blob())
    .then(function(blob) {
      let reader = new FileReader();

      reader.onload = function(event) {
        console.log('file read blob', event.target.result);
        gAudioContext.decodeAudioData(event.target.result, this.bufferDecoded, this.bufferError);
      }.bind(this);

      reader.readAsArrayBuffer(blob);
    }.bind(this));
  }

  componentWillUnmount() {
  }

  render() {
    return (LOADER)
  }
}

class Freesound extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      details: {},
      bufferSource: null
    };
    this.onBufferDecoded = this.onBufferDecoded.bind(this);
    this.startSound = this.startSound.bind(this);
    this.stopSound = this.stopSound.bind(this);
  }

  componentDidMount() {
    fetch('http://localhost:3001/apiv2/sounds/' + this.props.data.id + '?format=json')
    .then(result=>result.json())
    .then(data=>this.setState({details: data}))
  }

  componentWillUnmount() {
    console.log('sound unmount', this.props.data.id);
  }

  onBufferDecoded(buffer) {
    console.log('onBufferDecoded', buffer);
    this.setState({ buffer: buffer });
  }

  startSound() {
    let aBufferSource = gAudioContext.createBufferSource();
    aBufferSource.buffer = this.state.buffer;
    aBufferSource.connect(gAudioContext.destination);
    aBufferSource.start();
    this.setState({ bufferSource: aBufferSource });

    aBufferSource.addEventListener('ended', function(e) {
      console.log('buffer ended', e)
      this.setState({ bufferSource: null });
    }.bind(this));
  }

  stopSound() {
    this.state.bufferSource.stop();
  }

  render() {
    return (

        Sound "{this.props.data.name}" (#{this.props.data.id})
        {this.state.details.previews && (! this.state.buffer) && (<AudioBufferLoader onBufferDecoded={this.onBufferDecoded} data={this.state.details.previews} />)}

          <button data-freesound-id={this.props.data.id} disabled={! this.state.buffer} onClick={this.startSound}>start</button>
          <button data-freesound-id={this.props.data.id} disabled={! this.state.bufferSource} onClick={this.stopSound}>stop</button>

          {this.state.buffer && Math.round(this.state.buffer.duration)}s

        <button data-freesound-id={this.props.data.id} disabled={! this.state.buffer} onClick={this.props.handleRemove}>remove</button>

    )
  }
}

class FreesoundList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {listItems: []};
    this.handleRemove = this.handleRemove.bind(this);
  }

  componentDidMount() {
    fetch('http://localhost:3001/apiv2/search/text?format=json&query=' + this.props.term + '&filter=duration:[1 TO 90]')
    .then(result=>result.json())
    .then(data=>this.setState({listItems: data.results}))
  }

  componentWillUnmount() {
    console.log('freesound list unmount', this.props.term);
  }

  handleRemove(event) {
    let freesoundID = event.target.getAttribute('data-freesound-id');
    console.log('handleRemove', freesoundID);
    this.setState(prevState => ({
      listItems: prevState.listItems.filter(item => item.id != freesoundID)
    }));
  }

  render() {
    return (

{this.props.term}, {this.state.listItems.length} items <button data-freesound-search={this.props.term} onClick={this.props.onRemoveSearch}>remove</button>

          {this.state.listItems.map(item => <Freesound key={item.id} data={item} handleRemove={this.handleRemove} />)}

    )
  }
}

// ---------------------------------------------------------------------------

class App extends Component {
  render() {
    return (

        <FreesoundSearch />

    );
  }
}

export default App;
<|endoftext|>


<commit_before>
RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription;
RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.RTCIceCandidate;
URL = window.webkitURL || window.URL;

/**
 * Interface with WebRTC-streamer API
 * @constructor
 * @param {string} videoElement - id of the video element tag
 * @param {string} srvurl -  url of webrtc-streamer (default is current location)
*/
function WebRtcStreamer (videoElement, srvurl) {
	this.videoElement     = videoElement;
	this.srvurl           = srvurl || location.protocol+"//"+window.location.hostname+":"+window.location.port;
	this.pc               = null;

	this.pcOptions        = { 'optional': [{'DtlsSrtpKeyAgreement': true} ] };

	this.mediaConstraints = {}
	if (navigator.userAgent.indexOf("Firefox") > 0) {
		this.mediaConstraints = {'offerToReceiveVideo': true, 'offerToReceiveAudio': true  };
	}
	else {
		this.mediaConstraints = {'mandatory': {'OfferToReceiveVideo': true, 'OfferToReceiveAudio': true }}
	}

	// getIceServers
	var iceServers = sendSync('/getIceServers');
	this.pcConfig         = iceServers || {'iceServers': [] };
}

/**
 * Connect a WebRTC Stream to videoElement
 * @param {string} videourl - id of WebRTC video stream
 * @param {string} audiourl - id of WebRTC audio stream
 * @param {string} options -  options of WebRTC call
*/
WebRtcStreamer.prototype.connect = function(videourl, audiourl, options) {
	this.disconnect();

	try {
		this.pc = this.createPeerConnection();
		var peerid = Math.random();
		this.pc.peerid = peerid;

		var streamer = this;
		var callurl = this.srvurl + "/call?peerid="+ peerid+"&url="+encodeURIComponent(videourl);
		if (audiourl) {
			callurl += "&audiourl="+encodeURIComponent(audiourl);
		}
		if (options) {
			callurl += "&options="+encodeURIComponent(options);
		}

		// create Offer
		this.pc.createOffer(function(sessionDescription) {
			trace("Create offer:" + JSON.stringify(sessionDescription));

			streamer.pc.setLocalDescription(sessionDescription
				, function() { send(callurl, null, sessionDescription, streamer.onReceiveCall, null, streamer); }
				, function() {} );

		}, function(error) {
			alert("Create offer error:" + JSON.stringify(error));
		}, this.mediaConstraints);

	} catch (e) {
		this.disconnect();
		alert("connect error: " + e);
	}
}

/**
 * Disconnect a WebRTC Stream and clear videoElement source
*/
WebRtcStreamer.prototype.disconnect = function() {
	if (this.pc) {
		send(this.srvurl + "/hangup?peerid="+this.pc.peerid);
		try {
			this.pc.close();
		}
		catch (e) {
			trace ("Failure close peer connection:" + e);
		}
		this.pc = null;
	}
	var videoElement = document.getElementById(this.videoElement);
	if (videoElement) {
		videoElement.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
	}
}

/*
* create RTCPeerConnection
*/
WebRtcStreamer.prototype.createPeerConnection = function() {
	trace("createPeerConnection  config: " + JSON.stringify(this.pcConfig) + " option:"+  JSON.stringify(this.pcOptions));
	var pc = new RTCPeerConnection(this.pcConfig, this.pcOptions);
	var streamer = this;
	pc.onicecandidate = function(evt) { streamer.onIceCandidate.call(streamer, evt) };
	if (typeof pc.ontrack != "undefined") {
		pc.ontrack        = function(evt) { streamer.onTrack.call(streamer,evt) };
	}
	else {
		pc.onaddstream    = function(evt) { streamer.onTrack.call(streamer,evt) };
	}
	pc.onremovestream = function(evt) {  streamer.disconnect(); };
	trace("Created RTCPeerConnnection with config: " + JSON.stringify(this.pcConfig) + "option:"+  JSON.stringify(this.pcOptions) );
	return pc;
}

/*
* RTCPeerConnection IceCandidate callback
*/
WebRtcStreamer.prototype.onIceCandidate = function (event) {
	if (event.candidate) {
		send(this.srvurl + "/addIceCandidate?peerid="+this.pc.peerid, null, event.candidate);
	}
	else {
		trace("End of candidates.");
	}
}

/*
* RTCPeerConnection AddTrack callback
*/
WebRtcStreamer.prototype.onTrack = function(event) {
	trace("Remote track added:" +  JSON.stringify(event));
	if (event.streams) {
		stream = event.streams[0];
	}
	else {
		stream = event.stream;
	}
	var videoElement = document.getElementById(this.videoElement);
	videoElement.src = URL.createObjectURL(stream);
	videoElement.play();
}

/*
* AJAX /call callback
*/
WebRtcStreamer.prototype.onReceiveCall = function(dataJson) {
	var streamer = this;
	trace("offer: " + JSON.stringify(dataJson));
	var peerid = this.pc.peerid;
	this.pc.setRemoteDescription(new RTCSessionDescription(dataJson)
		, function()      { send  (streamer.srvurl + "/getIceCandidate?peerid="+peerid, null, null, streamer.onReceiveCandidate, null, streamer); }
		, function(error) { trace ("setRemoteDescription error:" + JSON.stringify(error)); });
}

/*
* AJAX /getIceCandidate callback
*/
WebRtcStreamer.prototype.onReceiveCandidate = function(dataJson) {
	trace("candidate: " + JSON.stringify(dataJson));
	if (dataJson) {
		for (var i=0; i<dataJson.length; i++) {
			var candidate = new RTCIceCandidate(dataJson[i]);

			trace("Adding ICE candidate :" + JSON.stringify(candidate) );
			this.pc.addIceCandidate(candidate
				, function()      { trace ("addIceCandidate OK"); }
				, function(error) { trace ("addIceCandidate error:" + JSON.stringify(error)); } );
		}
	}
}
<commit_msg>html: disable image when webrtc connection is broken<commit_after>
RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
URL = window.URL || window.webkitURL;

/**
 * Interface with WebRTC-streamer API
 * @constructor
 * @param {string} videoElement - id of the video element tag
 * @param {string} srvurl -  url of webrtc-streamer (default is current location)
*/
function WebRtcStreamer (videoElement, srvurl) {
	this.videoElement     = videoElement;
	this.srvurl           = srvurl || location.protocol+"//"+window.location.hostname+":"+window.location.port;
	this.pc               = null;

	this.pcOptions        = { 'optional': [{'DtlsSrtpKeyAgreement': true} ] };

	this.mediaConstraints = {}
	if (navigator.userAgent.indexOf("Firefox") > 0) {
		this.mediaConstraints = {'offerToReceiveVideo': true, 'offerToReceiveAudio': true  };
	}
	else {
		this.mediaConstraints = {'mandatory': {'OfferToReceiveVideo': true, 'OfferToReceiveAudio': true }}
	}

	// getIceServers
	var iceServers = sendSync('/getIceServers');
	this.pcConfig         = iceServers || {'iceServers': [] };
}

/**
 * Connect a WebRTC Stream to videoElement
 * @param {string} videourl - id of WebRTC video stream
 * @param {string} audiourl - id of WebRTC audio stream
 * @param {string} options -  options of WebRTC call
*/
WebRtcStreamer.prototype.connect = function(videourl, audiourl, options) {
	this.disconnect();

	try {
		this.pc = this.createPeerConnection();
		var peerid = Math.random();
		this.pc.peerid = peerid;

		var streamer = this;
		var callurl = this.srvurl + "/call?peerid="+ peerid+"&url="+encodeURIComponent(videourl);
		if (audiourl) {
			callurl += "&audiourl="+encodeURIComponent(audiourl);
		}
		if (options) {
			callurl += "&options="+encodeURIComponent(options);
		}

		// create Offer
		this.pc.createOffer(function(sessionDescription) {
			trace("Create offer:" + JSON.stringify(sessionDescription));

			streamer.pc.setLocalDescription(sessionDescription
				, function() { send(callurl, null, sessionDescription, streamer.onReceiveCall, null, streamer); }
				, function() {} );

		}, function(error) {
			alert("Create offer error:" + JSON.stringify(error));
		}, this.mediaConstraints);

	} catch (e) {
		this.disconnect();
		alert("connect error: " + e);
	}
}

/**
 * Disconnect a WebRTC Stream and clear videoElement source
*/
WebRtcStreamer.prototype.disconnect = function() {
	var videoElement = document.getElementById(this.videoElement);
	if (videoElement) {
		videoElement.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
	}
	if (this.pc) {
		send(this.srvurl + "/hangup?peerid="+this.pc.peerid);
		try {
			this.pc.close();
		}
		catch (e) {
			trace ("Failure close peer connection:" + e);
		}
		this.pc = null;
	}
}

/*
* create RTCPeerConnection
*/
WebRtcStreamer.prototype.createPeerConnection = function() {
	trace("createPeerConnection  config: " + JSON.stringify(this.pcConfig) + " option:"+  JSON.stringify(this.pcOptions));
	var pc = new RTCPeerConnection(this.pcConfig, this.pcOptions);
	var streamer = this;
	pc.onicecandidate = function(evt) { streamer.onIceCandidate.call(streamer, evt) };
	if (typeof pc.ontrack != "undefined") {
		pc.ontrack        = function(evt) { streamer.onTrack.call(streamer,evt) };
	}
	else {
		pc.onaddstream    = function(evt) { streamer.onTrack.call(streamer,evt) };
	}
	pc.oniceconnectionstatechange = function(evt) {
		trace("oniceconnectionstatechange  state: " + pc.iceConnectionState);
		if ( (pc.iceConnectionState == "diconnected") || (pc.iceConnectionState == "failed") || (pc.iceConnectionState == "closed") )  {
			var videoElement = document.getElementById(streamer.videoElement);
			if (videoElement) {
				videoElement.style.opacity = "0.5";
			}
		}
	}

	trace("Created RTCPeerConnnection with config: " + JSON.stringify(this.pcConfig) + "option:"+  JSON.stringify(this.pcOptions) );
	return pc;
}

/*
* RTCPeerConnection IceCandidate callback
*/
WebRtcStreamer.prototype.onIceCandidate = function (event) {
	if (event.candidate) {
		send(this.srvurl + "/addIceCandidate?peerid="+this.pc.peerid, null, event.candidate);
	}
	else {
		trace("End of candidates.");
	}
}

/*
* RTCPeerConnection AddTrack callback
*/
WebRtcStreamer.prototype.onTrack = function(event) {
	trace("Remote track added:" +  JSON.stringify(event));
	if (event.streams) {
		stream = event.streams[0];
	}
	else {
		stream = event.stream;
	}
	var videoElement = document.getElementById(this.videoElement);
	videoElement.src = URL.createObjectURL(stream);
	videoElement.play();
}

/*
* AJAX /call callback
*/
WebRtcStreamer.prototype.onReceiveCall = function(dataJson) {
	var streamer = this;
	trace("offer: " + JSON.stringify(dataJson));
	var peerid = this.pc.peerid;
	this.pc.setRemoteDescription(new RTCSessionDescription(dataJson)
		, function()      { send  (streamer.srvurl + "/getIceCandidate?peerid="+peerid, null, null, streamer.onReceiveCandidate, null, streamer); }
		, function(error) { trace ("setRemoteDescription error:" + JSON.stringify(error)); });
}

/*
* AJAX /getIceCandidate callback
*/
WebRtcStreamer.prototype.onReceiveCandidate = function(dataJson) {
	trace("candidate: " + JSON.stringify(dataJson));
	if (dataJson) {
		for (var i=0; i<dataJson.length; i++) {
			var candidate = new RTCIceCandidate(dataJson[i]);

			trace("Adding ICE candidate :" + JSON.stringify(candidate) );
			this.pc.addIceCandidate(candidate
				, function()      { trace ("addIceCandidate OK"); }
				, function(error) { trace ("addIceCandidate error:" + JSON.stringify(error)); } );
		}
	}
}
<|endoftext|>


<commit_before>// TODO: Options for font weight
// TODO: Option to auto scroll to top when new message arrives
// TODO: Private room
// TODO: Add user count

var placeholderText = "Say something...";

function submitMessage(message) {
  if (message.length != 0 && message.trim().toLowerCase() != placeholderText.toLowerCase()) {
    $.post('http://107.170.152.141:9080/pub?id=furtively', message.slice(0,-1), function(data) {
      // newMessageInput.html(placeholderHTML);
    });
  };
};

$(function(){
  $.timeago.settings.refreshMillis = 10000;

  function setUpPushStream() {
    var pushstream = new PushStream({
      host: '107.170.152.141',
      port: 9080,
      modes: 'eventsource'
    });

    pushstream.onmessage = messageReceived;
    pushstream.addChannel('furtively');
    pushstream.connect();
  };

  // function checkIfScrolledDown(){
  //   var messagesDiv = $('section#messages');
  //   if (messagesDiv.scrollTop() > 30) {
      // TODO: Turn on overflow: hidden in messages section
      // TODO: Display "scroll to Top"
  //   }
  // };

  function messageReceived(text, id, channel) {
    // checkIfScrolledDown();
    var firstMessageColor = $('span.message-text').first().css("background-color");
    var newMessageColor = "";
    if (firstMessageColor === undefined || firstMessageColor === "rgba(0, 0, 0, 0)") {
      newMessageColor = "#ccc";
    } else {
      newMessageColor = "rgba(0, 0, 0, 0)";
    };

    var timeReceived = new Date;
    var liveTime = '<time class="timeago" id="message-time-' +
          id + '" datetime="' + timeReceived.toISOString() + '"></time>';
    $('section#messages').prepend(
      '
' + text +
      '' + liveTime +'
');

    var newMessage = $('div#message-'+id);

    newMessage.hide();
    var messageCount = $('div.message').size();
    if (messageCount > 15) {
      $('div.message:last').remove();
    };
    $('span.message-text').first().css("background-color", newMessageColor);
    $('time#message-time-'+id).timeago();
    newMessage.fadeIn();
  };

  function setUpMedium() {
    return new Medium({
      element: document.getElementById('new-message'),
      debug: false,
      placeholder: placeholderText,
      autofocus: true,
      mode: 'inline',
      maxLength: 140
    });
  };

  function updateSubscriberCountText(subCount) {
    $('div#subscriber-count').text("People Connected: " + subCount);
  };

  function updateSubscriberCount() {
    setTimeout(function(){
      $.ajax({
        url: "http://107.170.152.141:9080/pub?id=furtively",
        success: function(stats){
          updateSubscriberCountText(stats.subscribers);
          updateSubscriberCount();
        },
        dataType: "json"
      });
    }, 5000);
  };

  setUpPushStream();
  setUpMedium();
  updateSubscriberCount();
});<commit_msg>Just in case, force focus on new message div<commit_after>// TODO: Options for font weight
// TODO: Option to auto scroll to top when new message arrives
// TODO: Private room
// TODO: Add user count

var placeholderText = "Say something...";

function submitMessage(message) {
  if (message.length != 0 && message.trim().toLowerCase() != placeholderText.toLowerCase()) {
    $.post('http://107.170.152.141:9080/pub?id=furtively', message.slice(0,-1), function(data) {
      // newMessageInput.html(placeholderHTML);
    });
  };
};

$(function(){
  $.timeago.settings.refreshMillis = 10000;

  function setUpPushStream() {
    var pushstream = new PushStream({
      host: '107.170.152.141',
      port: 9080,
      modes: 'eventsource'
    });

    pushstream.onmessage = messageReceived;
    pushstream.addChannel('furtively');
    pushstream.connect();
  };

  // function checkIfScrolledDown(){
  //   var messagesDiv = $('section#messages');
  //   if (messagesDiv.scrollTop() > 30) {
      // TODO: Turn on overflow: hidden in messages section
      // TODO: Display "scroll to Top"
  //   }
  // };

  function messageReceived(text, id, channel) {
    // checkIfScrolledDown();
    var firstMessageColor = $('span.message-text').first().css("background-color");
    var newMessageColor = "";
    if (firstMessageColor === undefined || firstMessageColor === "rgba(0, 0, 0, 0)") {
      newMessageColor = "#ccc";
    } else {
      newMessageColor = "rgba(0, 0, 0, 0)";
    };

    var timeReceived = new Date;
    var liveTime = '<time class="timeago" id="message-time-' +
          id + '" datetime="' + timeReceived.toISOString() + '"></time>';
    $('section#messages').prepend(
      '
' + text +
      '' + liveTime +'
');

    var newMessage = $('div#message-'+id);

    newMessage.hide();
    var messageCount = $('div.message').size();
    if (messageCount > 15) {
      $('div.message:last').remove();
    };
    $('span.message-text').first().css("background-color", newMessageColor);
    $('time#message-time-'+id).timeago();
    newMessage.fadeIn();
  };

  function setUpMedium() {
    return new Medium({
      element: document.getElementById('new-message'),
      debug: false,
      placeholder: placeholderText,
      autofocus: true,
      mode: 'inline',
      maxLength: 140
    });
  };

  function updateSubscriberCountText(subCount) {
    $('div#subscriber-count').text("People Connected: " + subCount);
  };

  function updateSubscriberCount() {
    setTimeout(function(){
      $.ajax({
        url: "http://107.170.152.141:9080/pub?id=furtively",
        success: function(stats){
          updateSubscriberCountText(stats.subscribers);
          updateSubscriberCount();
        },
        dataType: "json"
      });
    }, 5000);
  };

  setUpPushStream();
  setUpMedium();
  updateSubscriberCount();
  $('div#new-message').focus();
});<|endoftext|>


<commit_before>document.addEventListener("turbolinks:load", function() {
  //Only work at words form page
  if ($('#template-selector').length == 0) { return; }

  $('#template-selector').change(function(){
    var selected_text = $('option:selected', $(this)).text();
    if(selected_text.length == 0) { return; }

    if(simplemde.value().length > 0) {
      if(!window.confirm('Are you sure?')) {
        return false;
      }
    }

    $('#word_title').val(selected_text);
    simplemde.value($(this).val());
  });
});
<commit_msg>Add Template Variable<commit_after>document.addEventListener("turbolinks:load", function() {
  //Only work at words form page
  if ($('#template-selector').length == 0) { return; }

  $('#template-selector').change(function(){
    var selected_text = $('option:selected', $(this)).text();
    if(selected_text.length == 0) { return; }

    if(simplemde.value().length > 0) {
      if(!window.confirm('Are you sure?')) {
        return false;
      }
    }

    $('#word_title').val(replace_template_variables(selected_text));
    simplemde.value($(this).val());
  });

  function replace_template_variables(str){
    var date = new Date();
    var yyyy = date.getFullYear();
    var mm = ("0"+date.getMonth() + 1).slice(-2);
    var dd = ("0"+date.getDate()).slice(-2);
    str = str.replace(/\$\{Year\}/g, yyyy);
    str = str.replace(/\$\{month\}/g, mm);
    str = str.replace(/\$\{day\}/g, dd);
    return str;
  }
});

<|endoftext|>


<commit_before>'use strict';

import 'waypoints/lib/noframework.waypoints.js';

export default class Waypoint {

    constructor() {
        this.Waypoint = window.Waypoint;
    }

    set(arr) {
        var waypoints = [];

        for (let i = 0; i < arr.length; i++) {
            waypoints[i] = new window.Waypoint({
                element: arr[i].el,
                handler: arr[i].handler,
                offset: arr[i].offset
            });
        }

        this.waypoints = waypoints;
    }

    disable() {
        for (let waypoint of this.waypoints) {
            waypoint.disable();
        }
    }

    enable() {
        for (let waypoint of this.waypoints) {
            waypoint.enable();
        }
    }

}<commit_msg>Refactor waypoint module<commit_after>'use strict';

import 'waypoints/lib/noframework.waypoints.js';

export default class Waypoint {

    constructor() {
        this.Waypoint = window.Waypoint;
        this.waypoints = [];
    }

    set(waypoints) {
        waypoints.forEach((waypoint) => {
            this.add(
                this.getWaypointInstance(waypoint)
            );
        });
    }

    getWaypointInstance(waypoint) {
        return new this.Waypoint({
            element: waypoint.el,
            handler: waypoint.handler,
            offset: waypoint.offset
        });
    }

    add(waypoint) {
        this.waypoints.push(waypoint);
    }

    disable() {
        for (let waypoint of this.waypoints) {
            waypoint.disable();
        }
    }

    enable() {
        for (let waypoint of this.waypoints) {
            waypoint.enable();
        }
    }

}<|endoftext|>


<commit_before>import React from 'react';
import {List} from 'semantic-ui-react';
import {displayValue} from '../util/runhelpers';

class DataList extends React.Component {
  constructor(props) {
    super(props);
    if (props.noData) {
      this.noData = props.noData;
    } else {
      this.noData = this.noDataDefault;
    }
    if (props.prepData) {
      this.prepData = props.prepData;
    } else {
      this.prepData = this.prepDataDefault;
    }
    if (props.formatValue) {
      this.formatValue = props.formatValue;
    } else {
      this.formatValue = this.formatValueDefault;
    }

    if (props.data) {
      this.preppedData = this.prepData(props.data);
      this.flatData = this.flatten(this.preppedData);
    }
  }

  isDict(v) {
    return (
      typeof v === 'object' &&
      v !== null &&
      !(v instanceof Array) &&
      !(v instanceof Date)
    );
  }

  flattenInternal(prefix, data) {
    var prefixStr = prefix.join('.');
    var retData = {};
    var newPrefix;
    Object.keys(data).map(
      (key, i) =>
        this.isDict(data[key])
          ? ((newPrefix = prefix.concat([key])),
            (retData = Object.assign(
              retData,
              this.flattenInternal(newPrefix, data[key]),
            )))
          : (retData[prefix.concat([key]).join('.')] = data[key]),
    );

    return retData;
  }

  flatten(data) {
    var flatData = this.flattenInternal([], data);
    return flatData;
  }

  prepDataDefault(data) {
    return data;
  }

  configItem(key, value, i) {
    return (
      <List.Item key={'config ' + key + i}>
        <List.Content>
          <List.Header>{key}</List.Header>
          <List.Description>{'' + this.formatValue(value)}</List.Description>
        </List.Content>
      </List.Item>
    );
  }

  noDataDefault() {
    return
No data
;
  }

  formatValueDefault(value) {
    return displayValue(value);
  }

  render() {
    return (

        {this.flatData ? (
          <List divided size="small">
            {Object.keys(this.flatData)
              .sort()
              .map((key, i) => this.configItem(key, this.flatData[key], i))}
          </List>
        ) : (
          this.noData()
        )}

    );
  }
}

export default DataList;
<commit_msg>handle case where data is not null but length 0<commit_after>import React from 'react';
import {List} from 'semantic-ui-react';
import {displayValue} from '../util/runhelpers';

class DataList extends React.Component {
  constructor(props) {
    super(props);
    if (props.noData) {
      this.noData = props.noData;
    } else {
      this.noData = this.noDataDefault;
    }
    if (props.prepData) {
      this.prepData = props.prepData;
    } else {
      this.prepData = this.prepDataDefault;
    }
    if (props.formatValue) {
      this.formatValue = props.formatValue;
    } else {
      this.formatValue = this.formatValueDefault;
    }

    if (props.data && Object.keys(props.data).length > 0) {
      this.preppedData = this.prepData(props.data);
      this.flatData = this.flatten(this.preppedData);
    }
  }

  isDict(v) {
    return (
      typeof v === 'object' &&
      v !== null &&
      !(v instanceof Array) &&
      !(v instanceof Date)
    );
  }

  flattenInternal(prefix, data) {
    var prefixStr = prefix.join('.');
    var retData = {};
    var newPrefix;
    Object.keys(data).map(
      (key, i) =>
        this.isDict(data[key])
          ? ((newPrefix = prefix.concat([key])),
            (retData = Object.assign(
              retData,
              this.flattenInternal(newPrefix, data[key]),
            )))
          : (retData[prefix.concat([key]).join('.')] = data[key]),
    );

    return retData;
  }

  flatten(data) {
    var flatData = this.flattenInternal([], data);
    return flatData;
  }

  prepDataDefault(data) {
    return data;
  }

  configItem(key, value, i) {
    return (
      <List.Item key={'config ' + key + i}>
        <List.Content>
          <List.Header>{key}</List.Header>
          <List.Description>{'' + this.formatValue(value)}</List.Description>
        </List.Content>
      </List.Item>
    );
  }

  noDataDefault() {
    return
No data
;
  }

  formatValueDefault(value) {
    return displayValue(value);
  }

  render() {
    return (

        {this.flatData ? (
          <List divided size="small">
            {Object.keys(this.flatData)
              .sort()
              .map((key, i) => this.configItem(key, this.flatData[key], i))}
          </List>
        ) : (
          this.noData()
        )}

    );
  }
}

export default DataList;
<|endoftext|>


<commit_before>import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { get } from '@ember/object';
import Preload from 'ui/mixins/preload';
import { reject, all as PromiseAll } from 'rsvp';
import C from 'ui/utils/constants';

const VALID_ROUTES = ['authenticated.cluster.nodes', 'authenticated.cluster.storage.classes',
  'authenticated.cluster.storage.persistent-volumes', 'authenticated.cluster.notifier',
  'authenticated.cluster.alert', 'authenticated.cluster.logging', 'authenticated.cluster.monitoring',
  'authenticated.cluster.security.members.index', 'authenticated.cluster.projects',
  'authenticated.cluster.quotas', 'authenticated.cluster.istio'];

export default Route.extend(Preload, {
  scope:        service(),
  globalStore:  service(),
  clusterStore: service(),
  modalService: service('modal'),
  settings:     service(),

  model(params, transition) {
    return get(this, 'globalStore').find('cluster', params.cluster_id)
      .then((cluster) => {
        const hideLocalCluster = get(this.settings, 'shouldHideLocalCluster');

        if (hideLocalCluster && get(cluster, 'id') === 'local') {
          return this.replaceWith('authenticated');
        }

        return get(this, 'scope').startSwitchToCluster(cluster).then(() => {
          if ( get(cluster, 'isReady') ) {
            return this.loadSchemas('clusterStore').then(() => PromiseAll([
              this.preload('clusterScan', 'globalStore'),
              this.preload('namespace', 'clusterStore'),
              this.preload('storageClass', 'clusterStore'),
              this.preload('persistentVolume', 'clusterStore'),
            ]).then(() => cluster));
          } else {
            return cluster;
          }
        }).catch((err) => {
          // @TODO-2.0 right now the API can't return schemas for a not-active cluster
          if ( err.status === 404 ) {
            return cluster;
          } else {
            return reject(err);
          }
        });
      })
      .catch((err) => this.loadingError(err, transition));
  },

  afterModel(model) {
    return get(this, 'scope').finishSwitchToCluster(model);
  },

  redirect(router, transition) {
    let route = this.get(`session.${ C.SESSION.CLUSTER_ROUTE }`);

    if ( transition.targetName === 'authenticated.cluster.index' && VALID_ROUTES.includes(route) ) {
      this.replaceWith(route);
    }
  },
  actions: {
    becameReady() {
      get(this, 'clusterStore').reset();
      this.refresh();
    },

    importYaml() {
      this.modalService.toggleModal('modal-import', {
        escToClose:  true,
        clusterOnly: true,
      });
    },
  },

});
<commit_msg>Add etcdBackup to the list of preloads for RKE cluster<commit_after>import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { get } from '@ember/object';
import Preload from 'ui/mixins/preload';
import { reject, all as PromiseAll } from 'rsvp';
import C from 'ui/utils/constants';

const VALID_ROUTES = ['authenticated.cluster.nodes', 'authenticated.cluster.storage.classes',
  'authenticated.cluster.storage.persistent-volumes', 'authenticated.cluster.notifier',
  'authenticated.cluster.alert', 'authenticated.cluster.logging', 'authenticated.cluster.monitoring',
  'authenticated.cluster.security.members.index', 'authenticated.cluster.projects',
  'authenticated.cluster.quotas', 'authenticated.cluster.istio'];

export default Route.extend(Preload, {
  scope:        service(),
  globalStore:  service(),
  clusterStore: service(),
  modalService: service('modal'),
  settings:     service(),

  model(params, transition) {
    return get(this, 'globalStore').find('cluster', params.cluster_id)
      .then((cluster) => {
        const hideLocalCluster = get(this.settings, 'shouldHideLocalCluster');

        if (hideLocalCluster && get(cluster, 'id') === 'local') {
          return this.replaceWith('authenticated');
        }

        return get(this, 'scope').startSwitchToCluster(cluster).then(() => {
          if ( get(cluster, 'isReady') ) {
            const preloads = [
              this.preload('clusterScan', 'globalStore'),
              this.preload('namespace', 'clusterStore'),
              this.preload('persistentVolume', 'clusterStore'),
              this.preload('storageClass', 'clusterStore'),
            ];

            if (get(cluster, 'isRKE')) {
              preloads.push(this.preload('etcdBackup', 'globalStore'));
            }

            return this.loadSchemas('clusterStore').then(() => PromiseAll(preloads).then(() => cluster));
          } else {
            return cluster;
          }
        }).catch((err) => {
          // @TODO-2.0 right now the API can't return schemas for a not-active cluster
          if ( err.status === 404 ) {
            return cluster;
          } else {
            return reject(err);
          }
        });
      })
      .catch((err) => this.loadingError(err, transition));
  },

  afterModel(model) {
    return get(this, 'scope').finishSwitchToCluster(model);
  },

  redirect(router, transition) {
    let route = this.get(`session.${ C.SESSION.CLUSTER_ROUTE }`);

    if ( transition.targetName === 'authenticated.cluster.index' && VALID_ROUTES.includes(route) ) {
      this.replaceWith(route);
    }
  },
  actions: {
    becameReady() {
      get(this, 'clusterStore').reset();
      this.refresh();
    },

    importYaml() {
      this.modalService.toggleModal('modal-import', {
        escToClose:  true,
        clusterOnly: true,
      });
    },
  },

});
<|endoftext|>