<commit_before>$(function() {
  var CATALOG_URL = "https://data.ioos.us/dataset?q=";

  headerScroll = function () {
    var scroll = $(this).scrollTop();
      if (scroll > 50) {
        $('header > div > a').css('visibility', 'hidden');
        $('header').css('position', 'fixed');
        if ($('body').width() > 768) {
            $('header').css('top', '-50px');
            $('#grid-container, #main-container.comt-projects').css('margin-top', '200px');
        }
      } else {
        $('header > div > a').css('visibility', 'visible');
          $('header').css({'position':'static','top':'0'});
          if ($('body').width() > 768)
      $('#grid-container, #main-container.comt-projects').css('margin-top', '25px');
      }
  };
  $(document).scroll(headerScroll);
  window.onresize = function (e) {
    if ($('body').width() > 768 && $('header').css('top') === undefined) {
      headerScroll();
    }
  };
  $('[data-toggle="tooltip"]').tooltip();
  $('.collapse')
    .collapse('hide')
    .on('show.bs.collapse', function() {
      $('#search-data-button').addClass('open');
      setTimeout(function() {
        $('#search-input-collapse input').focus();
      }, 50);
    })
  .on('hide.bs.collapse', function() {
    $('#search-data-button').removeClass('open');
    setTimeout(function() {
      $('#search-data-button').blur();
    }, 50);
  });
  function searchCatalog () {
    var query = $('input').val().trim();
    if (query !== "") {
      window.open(CATALOG_URL + query, "_blank");
    }
  }
  $('#search-input-collapse input').on('keyup', function(e) {
    if (e.which === 13) {
      searchCatalog();
    }
  });
  $('#search-input-collapse button').on('click', searchCatalog);
  $('#feedback').on('click',function(e){
    e.preventDefault();
    e.stopPropagation();
    $('#feedbackForm textarea, #feedbackForm input:not([type="submit"])').val('');
    $('#feedbackForm').modal({show:true});
  });
  $('#feedbackForm [id]').on('focus', function(e) {
    if ($(e.currentTarget).parent().hasClass('has-error'))
      $(e.currentTarget).parent().removeClass('has-error').find('span').css('visibility', 'hidden');
  });
  $('#feedbackForm').on('shown.bs.modal', function() {
    $('#feedbackForm textarea').focus();
  });

  $('#feedbackForm input[type="submit"]').on('click', function (e) {
    var valid = true;
    $('#feedbackForm [id]').each(function (i, v) {
      if ($(v).val() === '') {
        $(v).parent().addClass('has-error').find('span').css('visibility', 'visible');
        valid = false;
      }
      else
        if ($(v).attr('id') === 'feedback-email' && !validateEmail($(v).val())) {
          $(v).parent().addClass('has-error').find('span').css('visibility', 'visible');
          valid = false;
        }
    });
    if (valid)
      $.ajax({
        url   : 'https://feedback.ioos.us/?SiteID=1&Name=' + $('#feedback-name').val() + '&Email=' + $('#feedback-email').val() + '&Comment=' + $('#feedback-comment').val(),
        type  : 'POST',
        success: function (data) {
          alert('Feedback Submitted');
          $('#feedbackForm').modal('hide');
        },
        error: function (error) {
          alert('An Error has Occurred');
        }
      });

    $(e.currentTarget).blur();
  });
  function validateEmail(email) {
    var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(email);
  }
  headerScroll();
});
<commit_msg>feedback button oceancsmap url<commit_after>$(function() {
  var CATALOG_URL = "https://data.ioos.us/dataset?q=";

  headerScroll = function () {
    var scroll = $(this).scrollTop();
      if (scroll > 50) {
        $('header > div > a').css('visibility', 'hidden');
        $('header').css('position', 'fixed');
        if ($('body').width() > 768) {
            $('header').css('top', '-50px');
            $('#grid-container, #main-container.comt-projects').css('margin-top', '200px');
        }
      } else {
        $('header > div > a').css('visibility', 'visible');
          $('header').css({'position':'static','top':'0'});
          if ($('body').width() > 768)
      $('#grid-container, #main-container.comt-projects').css('margin-top', '25px');
      }
  };
  $(document).scroll(headerScroll);
  window.onresize = function (e) {
    if ($('body').width() > 768 && $('header').css('top') === undefined) {
      headerScroll();
    }
  };
  $('[data-toggle="tooltip"]').tooltip();
  $('.collapse')
    .collapse('hide')
    .on('show.bs.collapse', function() {
      $('#search-data-button').addClass('open');
      setTimeout(function() {
        $('#search-input-collapse input').focus();
      }, 50);
    })
  .on('hide.bs.collapse', function() {
    $('#search-data-button').removeClass('open');
    setTimeout(function() {
      $('#search-data-button').blur();
    }, 50);
  });
  function searchCatalog () {
    var query = $('input').val().trim();
    if (query !== "") {
      window.open(CATALOG_URL + query, "_blank");
    }
  }
  $('#search-input-collapse input').on('keyup', function(e) {
    if (e.which === 13) {
      searchCatalog();
    }
  });
  $('#search-input-collapse button').on('click', searchCatalog);
  $('#feedback').on('click',function(e){
    e.preventDefault();
    e.stopPropagation();
    $('#feedbackForm textarea, #feedbackForm input:not([type="submit"])').val('');
    $('#feedbackForm').modal({show:true});
  });
  $('#feedbackForm [id]').on('focus', function(e) {
    if ($(e.currentTarget).parent().hasClass('has-error'))
      $(e.currentTarget).parent().removeClass('has-error').find('span').css('visibility', 'hidden');
  });
  $('#feedbackForm').on('shown.bs.modal', function() {
    $('#feedbackForm textarea').focus();
  });

  $('#feedbackForm input[type="submit"]').on('click', function (e) {
    var valid = true;
    $('#feedbackForm [id]').each(function (i, v) {
      if ($(v).val() === '') {
        $(v).parent().addClass('has-error').find('span').css('visibility', 'visible');
        valid = false;
      }
      else
        if ($(v).attr('id') === 'feedback-email' && !validateEmail($(v).val())) {
          $(v).parent().addClass('has-error').find('span').css('visibility', 'visible');
          valid = false;
        }
    });
    if (valid)
      $.ajax({
        url   : 'https://oceansMap2.asascience.com/?SiteID=1&Name=' + $('#feedback-name').val() + '&Email=' + $('#feedback-email').val() + '&Comment=' + $('#feedback-comment').val(),
        type  : 'POST',
        success: function (data) {
          alert('Feedback Submitted');
          $('#feedbackForm').modal('hide');
        },
        error: function (error) {
          alert('An Error has Occurred');
        }
      });

    $(e.currentTarget).blur();
  });
  function validateEmail(email) {
    var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(email);
  }
  headerScroll();
});
<|endoftext|>


<commit_before>var settings = new Settings();

var debug = false;
var isStatsOn = false;

try {
	if(!window.Websocket) {
		alert('This is an experiment that has only been tested in the latest versions of browsers that support WebSockets may not work in other browsers');
	}
} catch(err) {
	alert('This is an experiment that has only been tested in the latest versions of browsers that support WebSockets may not work in other browsers');
}

var runLoop = function() {
	app.update();
	app.draw();
}

var app = new App(settings, document.getElementById('canvas'));

window.addEventListener('resize', app.resize, false);

document.addEventListener('mousemove', app.mousemove, false);
document.addEventListener('mousedown', app.mousedown, false);
document.addEventListener('mouseup', app.mouseup, false);

document.addEventListener('touchstart',   app.touchstart, false);
document.addEventListener('touchend',     app.touchend, false);
document.addEventListener('touchcancel',  app.touchend, false);
document.addEventListener('touchmove',    app.touchmove, false);

setInterval(runLoop,30);

var addStats = function() {
	// Draw fps
	var stats = new Stats();
	document.getElementById('fps').appendChild(stats.domElement);

	setInterval(function () {
	    stats.update();
	}, 1000/60);

	// Array Remove - By John Resig (MIT Licensed)
	Array.remove = function(array, from, to) {
	  var rest = array.slice((to || from) + 1 || array.length);
	  array.length = from < 0 ? array.length + from : from;
	  return array.push.apply(array, rest);
	};
}

document.addEventListener('keydown',function(e) {
	if(e.which == 27 && !isStatsOn) {
		addStats();
		isStatsOn = true;
	}
})

if(debug) {
	addStats();
	isStatsOn = true;
}

$(function() {
	$('a[rel=external]').click(function(e) {
		e.preventDefault();
		window.open($(this).attr('href'));
	});
});

document.body.onselectstart = function() { return false; }
<commit_msg>Revert "Replace user agent sniffing with feature detection."<commit_after>var settings = new Settings();

var debug = false;
var isStatsOn = false;

try {
	var ua = navigator.userAgent.toLowerCase();
	if(ua.search('chrome') < 0 && ua.search('safari') < 0) {
		alert('This is an experiment that has only been tested in Chrome, Safari and Firefox 4Beta, and may not work in other browsers');
	}
} catch(err) {
	alert('This is an experiment that has only been tested in Chrome, Safari and Firefox 4Beta, and may not work in other browsers');
}

var runLoop = function() {
	app.update();
	app.draw();
}

var app = new App(settings, document.getElementById('canvas'));

window.addEventListener('resize', app.resize, false);

document.addEventListener('mousemove', app.mousemove, false);
document.addEventListener('mousedown', app.mousedown, false);
document.addEventListener('mouseup', app.mouseup, false);

document.addEventListener('touchstart',   app.touchstart, false);
document.addEventListener('touchend',     app.touchend, false);
document.addEventListener('touchcancel',  app.touchend, false);
document.addEventListener('touchmove',    app.touchmove, false);

setInterval(runLoop,30);

var addStats = function() {
	// Draw fps
	var stats = new Stats();
	document.getElementById('fps').appendChild(stats.domElement);

	setInterval(function () {
	    stats.update();
	}, 1000/60);

	// Array Remove - By John Resig (MIT Licensed)
	Array.remove = function(array, from, to) {
	  var rest = array.slice((to || from) + 1 || array.length);
	  array.length = from < 0 ? array.length + from : from;
	  return array.push.apply(array, rest);
	};
}

document.addEventListener('keydown',function(e) {
	if(e.which == 27 && !isStatsOn) {
		addStats();
		isStatsOn = true;
	}
})

if(debug) {
	addStats();
	isStatsOn = true;
}

$(function() {
	$('a[rel=external]').click(function(e) {
		e.preventDefault();
		window.open($(this).attr('href'));
	});
});

document.body.onselectstart = function() { return false; }<|endoftext|>


<commit_before>require.config({

    shim : {
        'jQuery' : {
            exports : '$'
        },

        'jQueryCsv' : {
            exports : '$.csv',
            deps : [ 'jQuery' ]
        },

        'BootstrapGrowl' : {
            deps : [ 'jQuery' ]
        },

        'Underscore' : {
            exports : '_',
            init : function() {
                this._.templateSettings = {
                    evaluate : /\{\{(.+?)\}\}/gim,
                    interpolate : /\{\{=(.+?)\}\}/gim,
                    escape : /\{\{-(.+?)\}\}/gim
                };
                return _;
            }
        },

        'Backbone' : {
            deps : [ 'Underscore', 'jQuery' ],
            exports : 'Backbone'
        },

        'BackbonePaginator' : {
            deps : [ 'Backbone' ],
            exports : 'Backbone.Paginator'
        },

        'ApplicationRouter' : {
            deps : [ 'jQuery', 'Underscore', 'Backbone' ]
        },

        'diff_match_patch' : {
            exports : 'diff_match_patch'
        },

        'yaml' : {
            exports : 'YAML'
        },

        // TODO: which export
        'BootstrapModal' : {
            exports : 'BootstrapModal',
            deps : [ 'jQuery' ]
        },
        'BootstrapDropdown' : {
            exports : 'BootstrapDropdown',
            deps : [ 'jQuery' ]
        },
        'BootstrapTooltip' : {
            exports : 'BootstrapTooltip',
            deps : [ 'jQuery' ]
        },
        'BootstrapPopover' : {
            exports : 'BootstrapPopover',
            deps : [ 'jQuery' ,'BootstrapTooltip']
        },
        'Typeahead' : {
            exports : 'Typeahead',
            deps : [ 'jQuery' ],
            init : function() {
                jQuery.fn.twitterTypeahead = jQuery.fn.typeahead;
                return jQuery.fn.twitterTypeahead;
            }
        },

        // Fake dependency to force Handsontable to be loaded after Typeahead
        'Handsontable' : {
            exports : 'Handsontable',
            deps : [ 'jQuery', 'Typeahead' ]
        },
        'CodeMirror' : {
            exports : 'CodeMirror'
        },
        'CodeMirrorYaml' : {
            deps : [ 'CodeMirror' ]
        },
        'Leaflet' : {
            exports : 'L'
        },
        'Xeditable' : {
            deps : [ 'jQuery', 'BootstrapPopover' ],
            init : function() {
                jQuery.fn.editable.defaults.mode = 'inline';
                return jQuery.fn.editable;
            }
        }
    },

    paths : {
        jQuery : './../components/jquery/jquery',
        jQueryCsv : './../components/jquery-csv/src/jquery.csv',
        jQueryToast : './../components/jquery-toast/jquery.toast',
        Underscore : './../components/underscore/underscore',
        Backbone : './../components/backbone/backbone',
        BackbonePaginator : './../components/backbone.paginator/lib/backbone.paginator',
        i18nprecompile : './../components/require-handlebars-plugin/hbs/i18nprecompile',
        json2 : './../components/require-handlebars-plugin/hbs/json2',
        text : './../components/requirejs-text/text',
        diff_match_patch : './../components/diff-match-patch/diff_match_patch',
        yaml : './../components/yamljs/bin/yaml',
        Typeahead : './../components/typeahead.js/dist/typeahead',
        BootstrapModal : './../components/bootstrap/js/modal',
        BootstrapDropdown : './../components/bootstrap/js/dropdown',
        BootstrapTooltip : './../components/bootstrap/js/tooltip',
        BootstrapPopover : './../components/bootstrap/js/popover',
        BootstrapGrowl : './../components/bootstrap-growl/jquery.bootstrap-growl',
        moment : './../components/momentjs/min/moment+langs',
        UmxAppTemplates : './theme-techonmap/umx.app.templates',
        CodeMirror : './../components/codemirror/lib/codemirror',
        CodeMirrorYaml : './../components/codemirror/mode/yaml/yaml',
        Handsontable : './../components/handsontable/dist/jquery.handsontable.full',
        Leaflet : './../components/leaflet-dist/leaflet',
        Xeditable : './../components/x-editable/dist/bootstrap3-editable/js/bootstrap-editable'
    }
});

require([ 'core/router', 'core/client', 'Backbone', 'Underscore' ],

function(Router, client, Backbone, _) {
    var app = {
        root : '/'
    };

    window.Router = new Router();
    client.setup(window, app);
    Backbone.pubSub = _.extend({}, Backbone.Events);
    Backbone.history.start({
        pushState : true
    });
});<commit_msg>momentjs file path was updated<commit_after>require.config({

    shim : {
        'jQuery' : {
            exports : '$'
        },

        'jQueryCsv' : {
            exports : '$.csv',
            deps : [ 'jQuery' ]
        },

        'BootstrapGrowl' : {
            deps : [ 'jQuery' ]
        },

        'Underscore' : {
            exports : '_',
            init : function() {
                this._.templateSettings = {
                    evaluate : /\{\{(.+?)\}\}/gim,
                    interpolate : /\{\{=(.+?)\}\}/gim,
                    escape : /\{\{-(.+?)\}\}/gim
                };
                return _;
            }
        },

        'Backbone' : {
            deps : [ 'Underscore', 'jQuery' ],
            exports : 'Backbone'
        },

        'BackbonePaginator' : {
            deps : [ 'Backbone' ],
            exports : 'Backbone.Paginator'
        },

        'ApplicationRouter' : {
            deps : [ 'jQuery', 'Underscore', 'Backbone' ]
        },

        'diff_match_patch' : {
            exports : 'diff_match_patch'
        },

        'yaml' : {
            exports : 'YAML'
        },

        // TODO: which export
        'BootstrapModal' : {
            exports : 'BootstrapModal',
            deps : [ 'jQuery' ]
        },
        'BootstrapDropdown' : {
            exports : 'BootstrapDropdown',
            deps : [ 'jQuery' ]
        },
        'BootstrapTooltip' : {
            exports : 'BootstrapTooltip',
            deps : [ 'jQuery' ]
        },
        'BootstrapPopover' : {
            exports : 'BootstrapPopover',
            deps : [ 'jQuery' ,'BootstrapTooltip']
        },
        'Typeahead' : {
            exports : 'Typeahead',
            deps : [ 'jQuery' ],
            init : function() {
                jQuery.fn.twitterTypeahead = jQuery.fn.typeahead;
                return jQuery.fn.twitterTypeahead;
            }
        },

        // Fake dependency to force Handsontable to be loaded after Typeahead
        'Handsontable' : {
            exports : 'Handsontable',
            deps : [ 'jQuery', 'Typeahead' ]
        },
        'CodeMirror' : {
            exports : 'CodeMirror'
        },
        'CodeMirrorYaml' : {
            deps : [ 'CodeMirror' ]
        },
        'Leaflet' : {
            exports : 'L'
        },
        'Xeditable' : {
            deps : [ 'jQuery', 'BootstrapPopover' ],
            init : function() {
                jQuery.fn.editable.defaults.mode = 'inline';
                return jQuery.fn.editable;
            }
        }
    },

    paths : {
        jQuery : './../components/jquery/jquery',
        jQueryCsv : './../components/jquery-csv/src/jquery.csv',
        jQueryToast : './../components/jquery-toast/jquery.toast',
        Underscore : './../components/underscore/underscore',
        Backbone : './../components/backbone/backbone',
        BackbonePaginator : './../components/backbone.paginator/lib/backbone.paginator',
        i18nprecompile : './../components/require-handlebars-plugin/hbs/i18nprecompile',
        json2 : './../components/require-handlebars-plugin/hbs/json2',
        text : './../components/requirejs-text/text',
        diff_match_patch : './../components/diff-match-patch/diff_match_patch',
        yaml : './../components/yamljs/bin/yaml',
        Typeahead : './../components/typeahead.js/dist/typeahead',
        BootstrapModal : './../components/bootstrap/js/modal',
        BootstrapDropdown : './../components/bootstrap/js/dropdown',
        BootstrapTooltip : './../components/bootstrap/js/tooltip',
        BootstrapPopover : './../components/bootstrap/js/popover',
        BootstrapGrowl : './../components/bootstrap-growl/jquery.bootstrap-growl',
        moment : './../components/momentjs/min/moment-with-langs',
        UmxAppTemplates : './theme-techonmap/umx.app.templates',
        CodeMirror : './../components/codemirror/lib/codemirror',
        CodeMirrorYaml : './../components/codemirror/mode/yaml/yaml',
        Handsontable : './../components/handsontable/dist/jquery.handsontable.full',
        Leaflet : './../components/leaflet-dist/leaflet',
        Xeditable : './../components/x-editable/dist/bootstrap3-editable/js/bootstrap-editable'
    }
});

require([ 'core/router', 'core/client', 'Backbone', 'Underscore' ],

function(Router, client, Backbone, _) {
    var app = {
        root : '/'
    };

    window.Router = new Router();
    client.setup(window, app);
    Backbone.pubSub = _.extend({}, Backbone.Events);
    Backbone.history.start({
        pushState : true
    });
});
<|endoftext|>


<commit_before>$.extend(true, window, {
  aposPages: {
    groups: {},
    // Return a page type object if one was configured for the given type name.
    // JavaScript doesn't iterate over object properties in a defined order,
    // so we maintain the list of types as a flat array. This convenience method
    // prevents this from being inconvenient and allows us to choose to do more
    // optimization later.

    getType: function(name) {
      return _.find(apos.data.aposPages.types, function(item) {
        return item.name === name;
      });
    },

    addType: function(type) {
      apos.data.aposPages.types.push(type);
    },

    // Replace a type with a new type object. Typically this is done to replace
    // a type object we got from the server (with just a name and a label) with a
    // type object that includes a settings property with functions for a page
    // settings dialog and so on.
    //
    // We replace rather than extending so that closures with references
    // to "self" still do the right thing.

    replaceType: function(name, object) {
      var newTypes = [];
      for (var i in apos.data.aposPages.types) {
        var type = apos.data.aposPages.types[i];
        if (type.name === name) {
          object.name = type.name;
          object.label = type.label;
          newTypes.push(object);
        } else {
          newTypes.push(type);
        }
      }
      apos.data.aposPages.types = newTypes;
    },

    // Right now this is called even for noneditors, but we don't put the
    // menu dropdown markup in the admin bar for them. TODO: we need to better
    // separate globally useful stuff like apos.data.aposPages.types from
    // clearly editor-specific stuff like editing page settings
    enableUI: function(options) {

      if (!options) {
        options = {};
      }
      if (!options.root) {
        options.root = apos.data.aposPages.root;
      }

      // Allow / or /pages/ to be specified, just quietly fix it
      options.root = options.root.replace(/\/$/, '');

      // Available in other scopes
      aposPages.options = options;

      // Shared state closure for the page settings dialogs (new and edit)
      (function() {
        var oldTypeName;
        var typeData = {};
        // Active dialog
        var $el;

        $('body').on('click', '.apos-new-page', function() {
          var parent = $(this).data('slug');
          $el = apos.modalFromTemplate('.apos-new-page-settings', {
            init: function(callback) {
              populateType();
              refreshType();
              return callback(null);
            },
            save: save
          });
          function save(callback) {
            return addOrEdit('new', { parent: parent }, callback);
          }
          return false;
        });

        $('body').on('click', '.apos-edit-page', function() {
          var slug = $(this).data('slug');
          $el = apos.modalFromTemplate('.apos-edit-page-settings', {
            save: save,
            init: function(callback) {
              populateType();
              // TODO: refactor this frequently used dance of boolean values
              // into editor.js or content.js
              var published = apos.data.aposPages.page.published;
              if (published === undefined) {
                published = 1;
              } else {
                // Simple POST friendly boolean values
                published = published ? '1' : '0';
              }
              $el.find('[name=published]').val(published);
              $el.find('[name=type]').val(apos.data.aposPages.page.type);
              $el.find('[name=title]').val(apos.data.aposPages.page.title);
              $el.find('[name=slug]').val(slug);
              refreshType();

              // Watch the title for changes, update the slug - but only if
              // the slug was in sync with the title to start with

              var $slug = $el.find('[name=slug]');
              var $title = $el.find('[name=title]');

              apos.suggestSlugOnTitleEdits($slug, $title);

              return callback(null);
            }
          });
          function save(callback) {
            return addOrEdit('edit', { slug: slug }, callback);
          }
          return false;
        });

        function populateType() {
          var $type = $el.find('[name=type]');
          $type.html('');
          _.each(apos.data.aposPages.menu || apos.data.aposPages.types, function(type) {
            var $option = $('<option></option>');
            $option.text(type.label);
            $option.attr('value', type.name);
            $type.append($option);
          });
          // Some types have custom settings of their own. When appropriate
          // instantiate the additional template and make it part of the form.
          // If the type has a selector for a settings template (settings.sel)
          // then it must also have an unserialize function to populate that
          // template's form fields from an object and a serialize function
          // to return an object based on the form fields. You can set up
          // client side javascript to assist with the use of the fields in
          // your unserialize function.

          $el.on('change', '[name=type]', function() {
            refreshType();
          });
        }

        function refreshType() {
          var typeName = $el.find('[name=type]').val();
          if (oldTypeName) {
            var oldType = aposPages.getType(oldTypeName);
            if (oldType.settings) {
              typeData[oldTypeName] = oldType.settings.serialize($el, $el.find('[data-type-details]'));
            }
            $el.find('[data-type-details]').html('');
          }
          oldTypeName = typeName;
          var type = aposPages.getType(typeName);

          if (type.settings) {
            var $typeEl = apos.fromTemplate('.apos-page-settings-' + type._css);
            $el.find('[data-type-details]').html($typeEl);
            var typeDefaults = typeData[typeName];
            if (!typeDefaults) {
              if (apos.data.aposPages.page.type === type.name) {
                typeDefaults = apos.data.aposPages.page.typeSettings;
              }
            }
            if (!typeDefaults) {
              typeDefaults = {};
            }
            type.settings.unserialize(typeDefaults, $el, $el.find('[data-type-details]'));
          }
        }

        function addOrEdit(action, options, callback) {
          var typeName = $el.find('[name=type]').val();
          var type = aposPages.getType(typeName);
          var data = {
            title: $el.find('[name=title]').val(),
            slug: $el.find('[name=slug]').val(),
            type: $el.find('[name=type]').val(),
            published: $el.find('[name=published]').val()
          };
          _.extend(data, { parent: options.parent, originalSlug: options.slug });
          if (type.settings && type.settings.serialize) {
            data.typeSettings = type.settings.serialize($el, $el.find('[data-type-details]'));
          }
          $.ajax(
            {
              url: '/apos-pages/' + action,
              data: data,
              type: 'POST',
              dataType: 'json',
              success: function(data) {
                window.location.href = aposPages.options.root + data.slug;
              },
              error: function() {
                alert('Server error');
                callback('Server error');
              }
            }
          );
          return false;
        }
      })();

      $('body').on('click', '[data-reorganize-page]', function() {
        var $tree;
        var $el = apos.modalFromTemplate('.apos-reorganize-page', {
          init: function(callback) {
            $tree = $el.find('[data-tree]');
            $tree.tree({
              data: [],
              autoOpen: 3,
              dragAndDrop: true,
              onCanMoveTo: function(moved_node, target_node, position) {
                // Cannot create peers of root
                if ((target_node.slug === '/') && (position !== 'inside')) {
                  return false;
                }
                return true;
              }
            });
            $tree.on('tree.move', function(e) {
              e.preventDefault();
              var data = {
                  moved: e.move_info.moved_node.slug,
                  target: e.move_info.target_node.slug,
                  position: e.move_info.position
              };
              apos.log('ajax call beginning');
              $.ajax({
                url: '/apos-pages/move-jqtree',
                data: data,
                type: 'POST',
                dataType: 'json',
                success: function() {
                  apos.log('success called');
                  e.move_info.do_move();
                },
                error: function() {
                  apos.log('error called');
                  // This didn't work, probably because something
                  // else has changed in the page tree. Refreshing
                  // is an appropriate response
                  apos.afterYield(function() { reload(null); });
                }
              });
            });
            reload(callback);
          },
          // After a reorg the page URL may have changed, be prepared to
          // navigate there or to the home page or just refresh to reflect
          // possible new tabs
          afterHide: function(callback) {
            var page = apos.data.aposPages.page;
            var _id = page._id;
            $.get('/apos-pages/info', { _id: _id }, function(data) {
              var newPathname = (apos.data.aposPages.root + data.slug).replace(/^\/\//, '/');
              if (window.location.pathname === newPathname) {
                apos.change('tree');
                return callback();
              } else {
                // Navigates away, so don't call the callback
                window.location.pathname = newPathname;
              }
            }).error(function() {
              // If the page no longer exists, navigate away to home page
              window.location.pathname = apos.data.aposPages.root;
            });
          }
        });
        function reload(callback) {
          $.getJSON('/apos-pages/get-jqtree', function(data) {
            $tree.tree('loadData', data);
            if (callback) {
              return callback();
            }
          }).error(function() {
            alert('The server did not respond or you do not have the appropriate privileges.');
            $el.trigger('aposModalHide');
          });
        }
      });

      $('body').on('click', '.apos-delete-page', function() {
        var slug = $(this).data('slug');
        $.ajax(
          {
            url: '/apos-pages/delete',
            data: {
              slug: slug
            },
            type: 'POST',
            dataType: 'json',
            success: function(data) {
              if(data.status === 'ok') {
                alert('Moved to the trash. Select "Reorganize" from the "Page" menu to drag it back out.');
                window.location.href = aposPages.options.root + data.parent;
              } else {
                alert(data.status);
              }
            },
            error: function() {
              alert('Server error');
            }
          }
        );
        return false;
      });

      $('body').on('click', '[data-versions-page]', function() {
        var pageId = apos.data.aposPages.page._id;
        aposPages.browseVersions(pageId);
      });

      // This is a hack to figure out what *isn't* a page based on
      // the available checkboxes
      var typeNames = [];
      $('.apos-search-filter').each(function() {
        typeNames.push($(this).attr('name'));
      });

      // Show and hide search results by type
      $('body').on('click', '.apos-search-filter', function() {
        var checked = $(this).prop('checked');
        var name = $(this).attr('name');
        var names = [];
        $('.apos-result').each(function() {
          var $result = $(this);
          if (name === 'page') {
            if (!_.contains(typeNames, $result.data('type'))) {
              if (checked) {
                $result.show();
              } else {
                $result.hide();
              }
            }
          } else {
            if ($result.data('type') === name) {
              if (checked) {
                $result.show();
              } else {
                $result.hide();
              }
            }
          }
        });
      });
    },

    // This method can also be invoked by snippets and anything else that
    // is represented by a page.
    browseVersions: function(pageId) {
      var $el = apos.modalFromTemplate('.apos-versions-page', {
        init: function(callback) {
          $versions = $el.find('[data-versions]');

          $versions.on('click', '[data-version-id]', function() {
            var id = $(this).data('versionId');
            $.post('/apos-pages/revert',
              { page_id: pageId, version_id: id },
              function(data) {
                alert('Switched versions.');
                $el.trigger('aposModalHide');
                apos.change('revert');
              }
            ).error(function() {
              alert('Server error or version no longer available.');
            });
          });

          // Load the available versions
          $template = $versions.find('[data-version].apos-template');
          $template.detach();
          // Easier to render as a nice server side template
          $.get('/apos-pages/versions', {
            _id: pageId
          }, function(data) {
            $versions.html(data);
          });
          return callback();
        }
      });
    }
  }
});

<commit_msg>A page type that has custom page settings must now have an _typeCss property. _css is unsuitable because page types may share an instance type<commit_after>$.extend(true, window, {
  aposPages: {
    groups: {},
    // Return a page type object if one was configured for the given type name.
    // JavaScript doesn't iterate over object properties in a defined order,
    // so we maintain the list of types as a flat array. This convenience method
    // prevents this from being inconvenient and allows us to choose to do more
    // optimization later.

    getType: function(name) {
      return _.find(apos.data.aposPages.types, function(item) {
        return item.name === name;
      });
    },

    addType: function(type) {
      apos.data.aposPages.types.push(type);
    },

    // Replace a type with a new type object. Typically this is done to replace
    // a type object we got from the server (with just a name and a label) with a
    // type object that includes a settings property with functions for a page
    // settings dialog and so on.
    //
    // We replace rather than extending so that closures with references
    // to "self" still do the right thing.

    replaceType: function(name, object) {
      var newTypes = [];
      for (var i in apos.data.aposPages.types) {
        var type = apos.data.aposPages.types[i];
        if (type.name === name) {
          object.name = type.name;
          object.label = type.label;
          newTypes.push(object);
        } else {
          newTypes.push(type);
        }
      }
      apos.data.aposPages.types = newTypes;
    },

    // Right now this is called even for noneditors, but we don't put the
    // menu dropdown markup in the admin bar for them. TODO: we need to better
    // separate globally useful stuff like apos.data.aposPages.types from
    // clearly editor-specific stuff like editing page settings
    enableUI: function(options) {

      if (!options) {
        options = {};
      }
      if (!options.root) {
        options.root = apos.data.aposPages.root;
      }

      // Allow / or /pages/ to be specified, just quietly fix it
      options.root = options.root.replace(/\/$/, '');

      // Available in other scopes
      aposPages.options = options;

      // Shared state closure for the page settings dialogs (new and edit)
      (function() {
        var oldTypeName;
        var typeData = {};
        // Active dialog
        var $el;

        $('body').on('click', '.apos-new-page', function() {
          var parent = $(this).data('slug');
          $el = apos.modalFromTemplate('.apos-new-page-settings', {
            init: function(callback) {
              populateType();
              refreshType();
              return callback(null);
            },
            save: save
          });
          function save(callback) {
            return addOrEdit('new', { parent: parent }, callback);
          }
          return false;
        });

        $('body').on('click', '.apos-edit-page', function() {
          var slug = $(this).data('slug');
          $el = apos.modalFromTemplate('.apos-edit-page-settings', {
            save: save,
            init: function(callback) {
              populateType();
              // TODO: refactor this frequently used dance of boolean values
              // into editor.js or content.js
              var published = apos.data.aposPages.page.published;
              if (published === undefined) {
                published = 1;
              } else {
                // Simple POST friendly boolean values
                published = published ? '1' : '0';
              }
              $el.find('[name=published]').val(published);
              $el.find('[name=type]').val(apos.data.aposPages.page.type);
              $el.find('[name=title]').val(apos.data.aposPages.page.title);
              $el.find('[name=slug]').val(slug);
              refreshType();

              // Watch the title for changes, update the slug - but only if
              // the slug was in sync with the title to start with

              var $slug = $el.find('[name=slug]');
              var $title = $el.find('[name=title]');

              apos.suggestSlugOnTitleEdits($slug, $title);

              return callback(null);
            }
          });
          function save(callback) {
            return addOrEdit('edit', { slug: slug }, callback);
          }
          return false;
        });

        function populateType() {
          var $type = $el.find('[name=type]');
          $type.html('');
          _.each(apos.data.aposPages.menu || apos.data.aposPages.types, function(type) {
            var $option = $('<option></option>');
            $option.text(type.label);
            $option.attr('value', type.name);
            $type.append($option);
          });
          // Some types have custom settings of their own. When appropriate
          // instantiate the additional template and make it part of the form.
          // If the type has a selector for a settings template (settings.sel)
          // then it must also have an unserialize function to populate that
          // template's form fields from an object and a serialize function
          // to return an object based on the form fields. You can set up
          // client side javascript to assist with the use of the fields in
          // your unserialize function.

          $el.on('change', '[name=type]', function() {
            refreshType();
          });
        }

        function refreshType() {
          var typeName = $el.find('[name=type]').val();
          if (oldTypeName) {
            var oldType = aposPages.getType(oldTypeName);
            if (oldType.settings) {
              typeData[oldTypeName] = oldType.settings.serialize($el, $el.find('[data-type-details]'));
            }
            $el.find('[data-type-details]').html('');
          }
          oldTypeName = typeName;
          var type = aposPages.getType(typeName);

          if (type.settings) {
            var $typeEl = apos.fromTemplate('.apos-page-settings-' + type._typeCss);
            $el.find('[data-type-details]').html($typeEl);
            var typeDefaults = typeData[typeName];
            if (!typeDefaults) {
              if (apos.data.aposPages.page.type === type.name) {
                typeDefaults = apos.data.aposPages.page.typeSettings;
              }
            }
            if (!typeDefaults) {
              typeDefaults = {};
            }
            type.settings.unserialize(typeDefaults, $el, $el.find('[data-type-details]'));
          }
        }

        function addOrEdit(action, options, callback) {
          var typeName = $el.find('[name=type]').val();
          var type = aposPages.getType(typeName);
          var data = {
            title: $el.find('[name=title]').val(),
            slug: $el.find('[name=slug]').val(),
            type: $el.find('[name=type]').val(),
            published: $el.find('[name=published]').val()
          };
          _.extend(data, { parent: options.parent, originalSlug: options.slug });
          if (type.settings && type.settings.serialize) {
            data.typeSettings = type.settings.serialize($el, $el.find('[data-type-details]'));
          }
          $.ajax(
            {
              url: '/apos-pages/' + action,
              data: data,
              type: 'POST',
              dataType: 'json',
              success: function(data) {
                window.location.href = aposPages.options.root + data.slug;
              },
              error: function() {
                alert('Server error');
                callback('Server error');
              }
            }
          );
          return false;
        }
      })();

      $('body').on('click', '[data-reorganize-page]', function() {
        var $tree;
        var $el = apos.modalFromTemplate('.apos-reorganize-page', {
          init: function(callback) {
            $tree = $el.find('[data-tree]');
            $tree.tree({
              data: [],
              autoOpen: 3,
              dragAndDrop: true,
              onCanMoveTo: function(moved_node, target_node, position) {
                // Cannot create peers of root
                if ((target_node.slug === '/') && (position !== 'inside')) {
                  return false;
                }
                return true;
              }
            });
            $tree.on('tree.move', function(e) {
              e.preventDefault();
              var data = {
                  moved: e.move_info.moved_node.slug,
                  target: e.move_info.target_node.slug,
                  position: e.move_info.position
              };
              apos.log('ajax call beginning');
              $.ajax({
                url: '/apos-pages/move-jqtree',
                data: data,
                type: 'POST',
                dataType: 'json',
                success: function() {
                  apos.log('success called');
                  e.move_info.do_move();
                },
                error: function() {
                  apos.log('error called');
                  // This didn't work, probably because something
                  // else has changed in the page tree. Refreshing
                  // is an appropriate response
                  apos.afterYield(function() { reload(null); });
                }
              });
            });
            reload(callback);
          },
          // After a reorg the page URL may have changed, be prepared to
          // navigate there or to the home page or just refresh to reflect
          // possible new tabs
          afterHide: function(callback) {
            var page = apos.data.aposPages.page;
            var _id = page._id;
            $.get('/apos-pages/info', { _id: _id }, function(data) {
              var newPathname = (apos.data.aposPages.root + data.slug).replace(/^\/\//, '/');
              if (window.location.pathname === newPathname) {
                apos.change('tree');
                return callback();
              } else {
                // Navigates away, so don't call the callback
                window.location.pathname = newPathname;
              }
            }).error(function() {
              // If the page no longer exists, navigate away to home page
              window.location.pathname = apos.data.aposPages.root;
            });
          }
        });
        function reload(callback) {
          $.getJSON('/apos-pages/get-jqtree', function(data) {
            $tree.tree('loadData', data);
            if (callback) {
              return callback();
            }
          }).error(function() {
            alert('The server did not respond or you do not have the appropriate privileges.');
            $el.trigger('aposModalHide');
          });
        }
      });

      $('body').on('click', '.apos-delete-page', function() {
        var slug = $(this).data('slug');
        $.ajax(
          {
            url: '/apos-pages/delete',
            data: {
              slug: slug
            },
            type: 'POST',
            dataType: 'json',
            success: function(data) {
              if(data.status === 'ok') {
                alert('Moved to the trash. Select "Reorganize" from the "Page" menu to drag it back out.');
                window.location.href = aposPages.options.root + data.parent;
              } else {
                alert(data.status);
              }
            },
            error: function() {
              alert('Server error');
            }
          }
        );
        return false;
      });

      $('body').on('click', '[data-versions-page]', function() {
        var pageId = apos.data.aposPages.page._id;
        aposPages.browseVersions(pageId);
      });

      // This is a hack to figure out what *isn't* a page based on
      // the available checkboxes
      var typeNames = [];
      $('.apos-search-filter').each(function() {
        typeNames.push($(this).attr('name'));
      });

      // Show and hide search results by type
      $('body').on('click', '.apos-search-filter', function() {
        var checked = $(this).prop('checked');
        var name = $(this).attr('name');
        var names = [];
        $('.apos-result').each(function() {
          var $result = $(this);
          if (name === 'page') {
            if (!_.contains(typeNames, $result.data('type'))) {
              if (checked) {
                $result.show();
              } else {
                $result.hide();
              }
            }
          } else {
            if ($result.data('type') === name) {
              if (checked) {
                $result.show();
              } else {
                $result.hide();
              }
            }
          }
        });
      });
    },

    // This method can also be invoked by snippets and anything else that
    // is represented by a page.
    browseVersions: function(pageId) {
      var $el = apos.modalFromTemplate('.apos-versions-page', {
        init: function(callback) {
          $versions = $el.find('[data-versions]');

          $versions.on('click', '[data-version-id]', function() {
            var id = $(this).data('versionId');
            $.post('/apos-pages/revert',
              { page_id: pageId, version_id: id },
              function(data) {
                alert('Switched versions.');
                $el.trigger('aposModalHide');
                apos.change('revert');
              }
            ).error(function() {
              alert('Server error or version no longer available.');
            });
          });

          // Load the available versions
          $template = $versions.find('[data-version].apos-template');
          $template.detach();
          // Easier to render as a nice server side template
          $.get('/apos-pages/versions', {
            _id: pageId
          }, function(data) {
            $versions.html(data);
          });
          return callback();
        }
      });
    }
  }
});

<|endoftext|>


<commit_before>import audioPlayer from './audioPlayer.js';
import searchResultComponent from './components/searchResultList.js';
import showComponent from './components/show.js';
import rssFetcher from './rssFetcher.js';
import registerServiceWorker from './registerServiceWorker.js';
import * as userData from './userData.js';
import * as modal from './components/modal.js';

// https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/
const SEARCH_BASE = '//itunes.apple.com/search?media=podcast&entity=podcast&limit=25&term=';

window.addEventListener('load', function() {
  let timeout;
  document.querySelector('input').addEventListener('keyup', event => {
    if(event.target instanceof HTMLInputElement) {
      const query = event.target.value;
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        doSearch(query);
      }, 250);
    }
  });
  renderUserShows();
});

function doSearch(query){
  const escapedQuery = encodeURIComponent(query);
  fetch(`${SEARCH_BASE}${escapedQuery}`)
    .then((res) => res.json())
    .then((json) => renderSearchResults(json));
}

/**
 * Render search result items
 * @param {SearchResults} json
 */
function renderSearchResults(json){
  const {results} = json;
  userData.setSearchResults(results);
  const resultsEl = document.querySelector('.search-results');
  resultsEl.innerHTML = searchResultComponent(results);
}

function renderUserShows(){
  const shows = userData.getShows();
  console.info('rendering user shows', shows.length);
  const resultsEl = document.querySelector('.search-results');
  resultsEl.innerHTML = searchResultComponent(shows);
}

// handle search result click
document.addEventListener('click', (event) => {
  if(!(event.target instanceof HTMLElement)) {
    return;
  }
  if(event.target.matches('.podcast a')) {
    event.preventDefault();
    const feedUrl = event.target.href;
    const show = userData.getSearchResult(feedUrl);
    saveShow(show);
  }
  if(event.target.matches('.play[data-url]')) {
    [].slice.call(document.querySelectorAll('.playing')).forEach((i) => i.classList.remove('playing'));

    if(audioPlayer.play(event.target.getAttribute('data-url'))) {
      event.target.closest('.show-item').classList.add('playing');
    }
  }
});

/**
 * @param {Podcast} show
 */
function saveShow(show) {
  modal.displayMessage(`Saving ${show.collectionName}`);
  rssFetcher(show.feedUrl)
    .then((feedData) => {
      show.items = feedData.item;
      userData.saveShow(show);
      modal.hideMessage();
    });
}

registerServiceWorker();
<commit_msg>render user shows at startup and podcast feed on click<commit_after>import audioPlayer from './audioPlayer.js';
import searchResultComponent from './components/searchResultList.js';
import userShowsComponent from './components/userShowList.js';
import showComponent from './components/show.js';
import rssFetcher from './rssFetcher.js';
import registerServiceWorker from './registerServiceWorker.js';
import * as userData from './userData.js';
import * as modal from './components/modal.js';

// https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/
const SEARCH_BASE = '//itunes.apple.com/search?media=podcast&entity=podcast&limit=25&term=';

window.addEventListener('load', function() {
  let timeout;
  document.querySelector('input').addEventListener('keyup', event => {
    if(event.target instanceof HTMLInputElement) {
      const query = event.target.value;
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        doSearch(query);
      }, 250);
    }
  });
  renderUserShows();
});

function doSearch(query){
  const escapedQuery = encodeURIComponent(query);
  fetch(`${SEARCH_BASE}${escapedQuery}`)
    .then((res) => res.json())
    .then((json) => renderSearchResults(json));
}

/**
 * Render search result items
 * @param {SearchResults} json
 */
function renderSearchResults(json){
  const {results} = json;
  userData.setSearchResults(results);
  const resultsEl = document.querySelector('.search-results');
  resultsEl.innerHTML = searchResultComponent(results);
}

function renderUserShows(){
  const shows = userData.getShows();
  console.info('rendering user shows', shows.length);
  const resultsEl = document.querySelector('.search-results');
  resultsEl.innerHTML = userShowsComponent(shows);
}

// handle search result click
document.addEventListener('click', (event) => {
  if(!(event.target instanceof HTMLElement)) {
    return;
  }
  if(event.target.matches('.search-result .podcast a')) {
    event.preventDefault();
    const feedUrl = event.target.href;
    const show = userData.getSearchResult(feedUrl);
    saveShow(show);
  }
  if(event.target.matches('.user-show .podcast a')) {
    event.preventDefault();
    const feedUrl = event.target.href;
    /** @type {Podcast} */
    const show = userData.getShow(feedUrl);
    if(show) {
      show.items = userData.getShowFeed(show.collectionId);
      document.querySelector('.show-list').innerHTML = showComponent(show);
    }
  }
  if(event.target.matches('.play[data-url]')) {
    [].slice.call(document.querySelectorAll('.playing')).forEach((i) => i.classList.remove('playing'));

    if(audioPlayer.play(event.target.getAttribute('data-url'))) {
      event.target.closest('.show-item').classList.add('playing');
    }
  }
});

/**
 * @param {Podcast} show
 */
function saveShow(show) {
  modal.displayMessage(`Saving ${show.collectionName}`);
  rssFetcher(show.feedUrl)
    .then((feedData) => {
      userData.saveShow(show, feedData.item);
      modal.hideMessage();
    });
}

registerServiceWorker();
<|endoftext|>


<commit_before>var loc = window.location;
var sockets;
if (loc.protocol === 'https:') {
  sockets = 'wss';
} else {
  sockets = 'ws';
}

// staged back-off strategy for websockets
var retryTimes = [ 50, 250, 1000, 2500, 5000, 10000, 30000, 60000, 120000, 300000, 600000, 86400000 ]; //in ms
var retryIndex = 0;

// stub for a proper class
var maki = {
    config: null
  , templates: {}
  , routes: []
  , socket: null // only one connected socket at a time (for now)
  , sockets: {
      subscriptions: [],
      subscribe: function( channel ) {
        if (!channel) { var channel = window.location.pathname; }
        if (!maki.socket) { maki.sockets.connect(); }
        if (maki.sockets.subscriptions.indexOf( channel ) >= 0) { return; }

        maki.sockets.subscriptions.push( channel );
        var message = new jsonRPC('subscribe', { channel: channel });
        return maki.socket.send( message.toJSON() );
      },
      unsubscribe: function( channel ) {
        if (!channel) { var channel = window.location.pathname; }
        if (!maki.socket) { maki.sockets.connect(); }
        if (maki.sockets.subscriptions.indexOf( channel ) == -1) { return; }

        var i = maki.sockets.subscriptions.indexOf( channel );
        maki.sockets.subscriptions.splice( i , 1 );

        var message = new jsonRPC('unsubscribe', { channel: channel });
        return maki.socket.send( message.toJSON() );
      },
      publish: function( channel , message ) {
        if (!maki.socket) { maki.sockets.connect(); }

      },
      disconnect: function() {
        if (maki.socket) {
          // unbind onclose, onmessage
          maki.socket.onclose = null;
          maki.socket.onmessage = null;

          // must happen after onclose event is unbound
          maki.socket.close();
          maki.socket = null;
        }
      },
      connect: function() {
        maki.sockets.disconnect();

        var path = sockets + '://' + window.location.host + window.location.pathname;
        maki.socket = new WebSocket( path );
        maki.socket.onclose = function onClose() {
          console.log('lost connection, reconnect... ');
          // TODO: randomize reconnection timeout buffer
          if (retryIndex < retryTimes.length) {
            console.log('retrying in ' + retryTimes[ retryIndex ] + 'ms');
            setTimeout( maki.sockets.connect , retryTimes[ retryIndex++ ] );
          } else {
            console.log('failed for the last time.  not attempting again.');
          }
        };
        maki.socket.onmessage = function onMessage(msg) {
          try {
            var data = JSON.parse( msg.data );
          } catch (e) {
            var data = {};
          }
          // experimental JSON-RPC implementation
          if (data.jsonrpc === '2.0') {
            switch (data.method) {
              case 'ping':
                console.log('was ping, playing pong');
                maki.socket.send(JSON.stringify({
                  'jsonrpc': '2.0',
                  'result': 'pong',
                  'id': data.id
                }));
              break;
              case 'patch':
                // TODO: update in-memory data (two-way binding);
                console.log( data );
              break;
              default:
                console.log('unhandled jsonrpc method ' , data.method);
              break;
            }
          } else {

          }
        };
        maki.socket.onopen = function onOpen() {
          retryIndex = 0;
          // this is redundant, as the connection will already be subscribed
          // however, we need to modify internal stores, so call it anyways
          maki.sockets.subscribe( window.location.pathname );
        };
      }
    }
};

function DataBinder( objectID ) {
  var pubSub = $({});

  var attribute = 'bind-' + objectID;
  var message = objectID + ':change';

  $(document).on('change', '[data-' + attribute + ']', function(e) {
    var $input = $(this);
    pubSub.trigger( message , [ $input.data( attribute ) ] , $input.val() );
  });

  pubSub.on( message , function(e, property, value) {
    $('[data-' + attribute + '=' + property + ']').each(function() {
      var $bound = $(this);
      if ( $bound.is('input, textarea, select') ) {
        $bound.val( value );
      } else {
        $bound.html( value );
      }
    });
  });
}

$(window).on('ready', function() {

  // update classes for links
  // note: only anchor tags are updated here.
  // don't create links that aren't anchor tags.  that's what they're for.
  $('a:not([href="'+window.location.pathname+'"])').removeClass('active');
  $('a[href="'+window.location.pathname+'"]').addClass('active');

  // Semantic UI crap
  $('.dropdown').dropdown();
  $('.ui.rating').rating();
  $('.tooltipped').popup();
  $('.message .close').on('click', function() {
    $(this).closest('.message').fadeOut();
  });
  $('.tabular.menu .item').tab();

  $.ajax({
    type: 'OPTIONS',
    url: '/'
  }).done(function(data) {
    if (!data || !data.resources) return console.log('failed to acquire resource list; disabling fancy stuff.');
    if (!data.config) return console.log('failed to acquire server config; disabling fancy stuff.');

    maki.config = data.config;

    // bind things
    $.fn.api.settings.api.search = '/search?query={value}';
    $('.search input')
      .api({
        action: 'search',
        stateContext: '.ui.input'
      });

    // server is online!
    maki.$viewport = $('[data-for=viewport]');
    maki.sockets.connect();

    // exit instead of binding client view handler
    if (!maki.config.views || !maki.config.views.client || maki.config.views.client !== true) return console.log('client view rendering disabled.');

    maki.resources = data.resources;
    maki.resources.forEach(function(resource) {
      // only support routing for lists and singles
      [ 'query', 'get' ].forEach(function(m) {
        var path = resource.paths[ m ];
        if (path) maki.templates[ path ] = resource.templates[ m ];
      });
    });

    // bind pushState stuff
    $( document ).on('click', 'a', function(e) {
      e.preventDefault();
      var $a = $(this);
      var href = $a.attr('href');

      var template;
      Object.keys(maki.templates).forEach(function(route) {
        // HACK: don't bother matching routes of almost-zero length
        // TODO: fix this
        if (href.length <= 1) return template = 'index';

        var string = route;
        var regex = new RegExp( eval( string ) );
        // TODO: do not match '/' –- see bugfix at top of this loop
        if (regex.test( href )) template = maki.templates[ route ];
      });

      // TODO: use local factory / caching mechanism
      $.ajax({
          url: href
        , async: false
      }).always(function( results ) {
        if (!results) template = '500';

        maki.sockets.unsubscribe( window.location.pathname );
        maki.sockets.subscribe( href );

        var obj = {};
        obj[ template ] = results;

        maki.$viewport.html( Templates[ template ]( obj ) );

        $('a.active').removeClass('active');
        $a.addClass('active');

        history.pushState({}, '', href );
      });

      return false;
    });

    $('.message .close').on('click', function() {
      $(this).closest('.message').fadeOut();
    });

  });
});
<commit_msg>Add controls for lookup fields.<commit_after>var loc = window.location;
var sockets;
if (loc.protocol === 'https:') {
  sockets = 'wss';
} else {
  sockets = 'ws';
}

// staged back-off strategy for websockets
var retryTimes = [ 50, 250, 1000, 2500, 5000, 10000, 30000, 60000, 120000, 300000, 600000, 86400000 ]; //in ms
var retryIndex = 0;

// stub for a proper class
var maki = {
    config: null
  , templates: {}
  , routes: []
  , socket: null // only one connected socket at a time (for now)
  , sockets: {
      subscriptions: [],
      subscribe: function( channel ) {
        if (!channel) { var channel = window.location.pathname; }
        if (!maki.socket) { maki.sockets.connect(); }
        if (maki.sockets.subscriptions.indexOf( channel ) >= 0) { return; }

        maki.sockets.subscriptions.push( channel );
        var message = new jsonRPC('subscribe', { channel: channel });
        return maki.socket.send( message.toJSON() );
      },
      unsubscribe: function( channel ) {
        if (!channel) { var channel = window.location.pathname; }
        if (!maki.socket) { maki.sockets.connect(); }
        if (maki.sockets.subscriptions.indexOf( channel ) == -1) { return; }

        var i = maki.sockets.subscriptions.indexOf( channel );
        maki.sockets.subscriptions.splice( i , 1 );

        var message = new jsonRPC('unsubscribe', { channel: channel });
        return maki.socket.send( message.toJSON() );
      },
      publish: function( channel , message ) {
        if (!maki.socket) { maki.sockets.connect(); }

      },
      disconnect: function() {
        if (maki.socket) {
          // unbind onclose, onmessage
          maki.socket.onclose = null;
          maki.socket.onmessage = null;

          // must happen after onclose event is unbound
          maki.socket.close();
          maki.socket = null;
        }
      },
      connect: function() {
        maki.sockets.disconnect();

        var path = sockets + '://' + window.location.host + window.location.pathname;
        maki.socket = new WebSocket( path );
        maki.socket.onclose = function onClose() {
          console.log('lost connection, reconnect... ');
          // TODO: randomize reconnection timeout buffer
          if (retryIndex < retryTimes.length) {
            console.log('retrying in ' + retryTimes[ retryIndex ] + 'ms');
            setTimeout( maki.sockets.connect , retryTimes[ retryIndex++ ] );
          } else {
            console.log('failed for the last time.  not attempting again.');
          }
        };
        maki.socket.onmessage = function onMessage(msg) {
          try {
            var data = JSON.parse( msg.data );
          } catch (e) {
            var data = {};
          }
          // experimental JSON-RPC implementation
          if (data.jsonrpc === '2.0') {
            switch (data.method) {
              case 'ping':
                console.log('was ping, playing pong');
                maki.socket.send(JSON.stringify({
                  'jsonrpc': '2.0',
                  'result': 'pong',
                  'id': data.id
                }));
              break;
              case 'patch':
                // TODO: update in-memory data (two-way binding);
                console.log( data );
              break;
              default:
                console.log('unhandled jsonrpc method ' , data.method);
              break;
            }
          } else {

          }
        };
        maki.socket.onopen = function onOpen() {
          retryIndex = 0;
          // this is redundant, as the connection will already be subscribed
          // however, we need to modify internal stores, so call it anyways
          maki.sockets.subscribe( window.location.pathname );
        };
      }
    }
};

function DataBinder( objectID ) {
  var pubSub = $({});

  var attribute = 'bind-' + objectID;
  var message = objectID + ':change';

  $(document).on('change', '[data-' + attribute + ']', function(e) {
    var $input = $(this);
    pubSub.trigger( message , [ $input.data( attribute ) ] , $input.val() );
  });

  pubSub.on( message , function(e, property, value) {
    $('[data-' + attribute + '=' + property + ']').each(function() {
      var $bound = $(this);
      if ( $bound.is('input, textarea, select') ) {
        $bound.val( value );
      } else {
        $bound.html( value );
      }
    });
  });
}

$(window).on('ready', function() {

  // update classes for links
  // note: only anchor tags are updated here.
  // don't create links that aren't anchor tags.  that's what they're for.
  $('a:not([href="'+window.location.pathname+'"])').removeClass('active');
  $('a[href="'+window.location.pathname+'"]').addClass('active');

  // Semantic UI crap
  $('.dropdown').dropdown();
  $('.ui.rating').rating();
  $('.tooltipped').popup();
  $('.message .close').on('click', function() {
    $(this).closest('.message').fadeOut();
  });
  $('.tabular.menu .item').tab();
  $('.ui.search')
    .search({
      apiSettings: {
        url: '/search?q={query}'
      },
      type: 'category',
      onSelect: function(result, response) {
        var self = this;
        console.log('controller:', self);
        console.log('selected', result);
        $(self).children('input').val( result.id );

      }
    })
  ;

  $.ajax({
    type: 'OPTIONS',
    url: '/'
  }).done(function(data) {
    if (!data || !data.resources) return console.log('failed to acquire resource list; disabling fancy stuff.');
    if (!data.config) return console.log('failed to acquire server config; disabling fancy stuff.');

    maki.config = data.config;

    // server is online!
    maki.$viewport = $('[data-for=viewport]');
    maki.sockets.connect();

    // exit instead of binding client view handler
    if (!maki.config.views || !maki.config.views.client || maki.config.views.client !== true) return console.log('client view rendering disabled.');

    maki.resources = data.resources;
    maki.resources.forEach(function(resource) {
      // only support routing for lists and singles
      [ 'query', 'get' ].forEach(function(m) {
        var path = resource.paths[ m ];
        if (path) maki.templates[ path ] = resource.templates[ m ];
      });
    });

    // bind pushState stuff
    $( document ).on('click', 'a', function(e) {
      e.preventDefault();
      var $a = $(this);
      var href = $a.attr('href');

      var template;
      Object.keys(maki.templates).forEach(function(route) {
        // HACK: don't bother matching routes of almost-zero length
        // TODO: fix this
        if (href.length <= 1) return template = 'index';

        var string = route;
        var regex = new RegExp( eval( string ) );
        // TODO: do not match '/' –- see bugfix at top of this loop
        if (regex.test( href )) template = maki.templates[ route ];
      });

      // TODO: use local factory / caching mechanism
      $.ajax({
          url: href
        , async: false
      }).always(function( results ) {
        if (!results) template = '500';

        maki.sockets.unsubscribe( window.location.pathname );
        maki.sockets.subscribe( href );

        var obj = {};
        obj[ template ] = results;

        maki.$viewport.html( Templates[ template ]( obj ) );

        $('a.active').removeClass('active');
        $a.addClass('active');

        history.pushState({}, '', href );
      });

      return false;
    });

    $('.message .close').on('click', function() {
      $(this).closest('.message').fadeOut();
    });

  });
});
<|endoftext|>


<commit_before>(function($, window, navigator, Recorder) {

  /* Variables */

  var $title = $('#title'),
      $lights = $('#lights'),
      $blueLights = $('#blue-lights'),
      $letsRock = $('#lets-rock'),
      $curtains = $('.curtains'),
      $dimmed = $('.dimmed'),
      $shadow = $('#shadow'),
      $diary = $('#diary'),
      $tapeRecorder = $('#tape-recorder'),
      $bPlay = $('#play'),
      $bStop = $('#stop'),
      $bRecord = $('#record'),
      $bEject = $('#eject'),
      $tapeCircles = $('.tape-circle'),
      $redLight = $('.red-light'),
      $info = $('#info'),
      $infoIcon = $('#info-icon'),
      readyToStartRecording = false;

  /* Variables for the audio recording process */

  var recorder,
      audio = document.createElement('audio'),
      a = $('a')[0];

  // Set up getUserMedia, AudioContext and URL
  navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
  window.URL = window.URL || window.webkitURL;

  // A nice easter egg...
  console.log('One day my log will have something to say about this');

  /* Animation and transition events*/

  // Show title when the lights animation has ended
  $lights.on('animationend webkitAnimationEnd', showTitle);

  // Show Let's Rock div when the title transition has ended
  $title.on('transitionend webkitTransitionEnd', dimRoom);

  $dimmed.on('transitionend  webkitTransitionEnd', function() {
    if (readyToStartRecording) {
      showTapeRecorder();
    }
    else {
      showLetsRock();
    }
  });

  // Hide lights div when the Let's Rock transition has ended
  $letsRock.on('transitionend webkitTransitionEnd', handleLetsRockTransitions);

  // Remove flashing lights when the flashing and flipping animations end
  $letsRock.on('animationend webkitAnimationEnd', prepareForRecording);

  // Hide diary message if the div is clicked
  $diary.on('transitionend webkitTransitionEnd', function() {
    if ($diary.css('opacity') === '0') {
      $diary.hide();
    }
  });

  $tapeRecorder.on('transitionend webkitTransitionEnd', function() {
    if ($tapeRecorder.css('opacity') === '0') {
      $tapeRecorder.hide();
    }
    else {
      listenTapeRecorderEvents(true);
    }
  });

  /* Click events */

  // Flip text when "Let's rock" is clicked
  $letsRock.on('click', startRecordingAnimation);

  // Hide diary when is clicked
  $diary.on('click', function() {
    hideDiaryMessage();
  });

  $infoIcon.on('click', (function() {
    var margin = $info.css('margin-bottom'),
        arrowOpacity = $infoIcon.css('opacity');

    return function() {
      if ($info.css('margin-bottom') !== '0px') {
        $infoIcon.css('opacity', '1');
        $info.css('margin-bottom', '0');
      }
      else {
        $infoIcon.css('opacity', arrowOpacity);
        $info.css('margin-bottom', margin);
      }
    };

  }()));

  function dimRoom() {
    dimCurtains();
    showShadow();
  }

  function dimCurtains() {
    // Show destroys the opacity transition except in IE
    $dimmed.show();

    // Hack for Firefox and Webkit browsers
    setTimeout(function() {
      $dimmed.css('opacity', '0.5');
    }, 50);
  }

  function showShadow() {
    $shadow.addClass('movement');
    $shadow.show();
  }

  function illuminateRoom() {
    illuminateCurtains();
    hideShadow();
  }

  function illuminateCurtains() {
    // Hide destroys the opacity transition except in IE
    $dimmed.hide();
    $dimmed.css('opacity', '0.0');
  }

  function hideShadow() {
    $shadow.removeClass('movement');
    $shadow.hide();
  }

  function showTitle() {
    $title.css('opacity', '1.0');
  }

  function showLetsRock() {
    $letsRock.css('opacity', '1.0');
  }

  function handleLetsRockTransitions(e) {
    var property = e.originalEvent.propertyName;

    if (property === 'opacity') {
      keepLightsOn();
    }
  }

  function keepLightsOn() {
    $lights.removeClass('turn-lights-on');
    $lights.hide();
  }

  function startRecordingAnimation() {
    illuminateRoom();
    setFlashingLights();
    flipText();
    readyToStartRecording = true;
  }

  function setFlashingLights() {
    $lights.show();
    $blueLights.show();
    $lights.addClass('flashing-lights');
    $blueLights.addClass('flashing-blue-lights');
  }

  function removeFlashingLights() {
    $lights.hide();
    $blueLights.hide();
    $lights.removeClass('flashing-lights');
    $blueLights.removeClass('flashing-blue-lights');
  }

  function flipText() {
    $title.addClass('flipping-text');
    $letsRock.addClass('flipping-text');
  }

  function restoreTextOrientation() {
    $title.removeClass('flipping-text');
    $letsRock.removeClass('flipping-text');
  }

  function showDiaryMessage() {
    $diary.show();

    // Hack for Firefox and Webkit browsers
    setTimeout(function() {
      $diary.css('opacity', '1.0');
    }, 50);
  }

  function hideDiaryMessage() {
    $diary.css('opacity', '0.0');
  }

  function showTapeRecorder() {
    $tapeRecorder.css('display', 'inline-block');

    // Hack for Firefox and Webkit browsers
    setTimeout(function() {
      $tapeRecorder.css('opacity', '1.0');
    }, 50);
  }

  function hideTapeRecorder() {
    unpressButtons();
    listenTapeRecorderEvents(false);
    $tapeRecorder.css('opacity', '0.0');
  }

  function startTapeAnimation() {
    if (!($tapeCircles.hasClass('spin'))) {
      $tapeCircles.addClass('spin');
    }
  }

  function stopTapeAnimation() {
    $tapeCircles.removeClass('spin');
  }

  function turnOnRecordingLight() {
    $redLight.css('background', '#f00');
  }

  function turnOffRecordingLight() {
    $redLight.css('background', '#000');
  }

  function isRecordingAudio() {
    return (recorder && recorder.isRecording());
  }

  function unpressButtons() {
    if ($bRecord.hasClass('pressed')) {
      $bRecord.removeClass('pressed');
    }

    if ($bPlay.hasClass('pressed')) {
      $bPlay.removeClass('pressed');
    }
  }

  function toggleButtonsPressed(buttons) {
    for (var i = 0; i < buttons.length; i++) {
      if (buttons[i].hasClass('pressed')) {
        buttons[i].removeClass('pressed');
      }
      else {
        buttons[i].addClass('pressed');
      }
    }
  }

  /* Audio recording process */

  // Audio player events
  $(audio).on('paused ended', function() {
    toggleButtonsPressed([$bPlay]);
    stopTapeAnimation();
  });

  $(audio).on('play', function() {
    toggleButtonsPressed([$bPlay]);
    startTapeAnimation();
  });

  // Buttons of tape recorder
  function listenTapeRecorderEvents(enabled) {
    // Stop listening click events
    if (!enabled) {
      $bRecord.off('click');
      $bStop.off('click');
      $bPlay.off('click');
      return;
    }

    // Start listening click events

    // The recording button of the tape recorder is clicked
    $bRecord.on('click', function() {
      if (!isRecordingAudio() && !isPlayingAudio()) {
        askForRecordingPermission();
      }

    });

    // Stop audio if playing or stop recording
    $bStop.on('click', function() {
      if (isRecordingAudio() || isPlayingAudio()) {
        if (isRecordingAudio()) {
          stopRecording();
          turnOffRecordingLight();
        }
        else {
          stopAudio();
        }
        stopTapeAnimation();
        toggleButtonsPressed([$bRecord, $bPlay]);
      }
    });

    $bPlay.on('click', function() {
        if (!isRecordingAudio() && !isPlayingAudio()) {
          playAudio();
        }

    });
  }

  function prepareForRecording() {
    removeFlashingLights();
    dimRoom();

    $letsRock.off('click');
    $letsRock.on('click', function() {
      restoreTextOrientation();
      hideTapeRecorder();
      readyToStartRecording = false;

      // Hide the diary message if it is visible
      if ($diary.css('opacity') === '1') {
        hideDiaryMessage();
      }

      // Check if audio is being recorded or played when "Let's rock" is clicked
      if (isPlayingAudio() || isRecordingAudio()) {
        if (isPlayingAudio()) {
          stopAudio();
        }

        if (isRecordingAudio()) {
          stopRecording();
          turnOffRecordingLight();
        }
        stopTapeAnimation();
      }

      $letsRock.off('click');
      $letsRock.on('click', startRecordingAnimation);
    });

    //showTapeRecorder();
  }

  function askForRecordingPermission() {
    if (!recorder) {
      if (navigator.getUserMedia) {
        navigator.getUserMedia({audio:true},
          function(stream) {
            initializeRecorder(stream);
            if (recorder) {
              startRecordingProcess();
            }
          },
          function(error) {
            console.log('Error: ' + error);
          });
      }
      else {
        showDiaryMessage();
      }
    }
    else {
      startRecordingProcess();
    }
  }

  // Start tape animation and start recording
  function startRecordingProcess() {
    turnOnRecordingLight();
    startTapeAnimation();
    toggleButtonsPressed([$bRecord, $bPlay]);
    startRecording();
  }

  // Initialize Recorder object for the first time
  function initializeRecorder(stream) {
    var audioContext = new AudioContext();
    var input = audioContext.createMediaStreamSource(stream);

    // Hack for Firefox - Prevent the action of garbage collector
    if (navigator.userAgent.toLowerCase().indexOf('firefox') !== -1) {
      window.audioInputHackForFirefox = input;
    }

    recorder = new Recorder(input, {workerPath:'js/recorderWorker.js'});
  }

  function startRecording() {
    if (!recorder) {
      return;
    }

    recorder.record();
  }

  function stopRecording() {
    if (!recorder) {
      return;
    }

    recorder.stop();
    createWAV();
    recorder.clear();
  }

  function playAudio() {
    if (audio.src !== '') {
      audio.play();
    }
  }

  function stopAudio() {
    audio.pause();
    audio.currentTime = 0;
  }

  function isPlayingAudio() {
    return !audio.paused;
  }

  // Create WAV file, load it in the audio player
  // and set up a download link
  function createWAV() {
    if (!recorder) {
      return;
    }

    recorder.exportWAV(function(blob) {
      var url = URL.createObjectURL(blob);
      audio.src = url;
      a.href = url;
      a.download = 'message.wav';
    });
  }

}(jQuery, window, navigator, Recorder));
<commit_msg>Close info message when window or text is clicked<commit_after>(function($, window, navigator, Recorder) {

  /* Variables */

  var $window = $(window),
      $title = $('#title'),
      $lights = $('#lights'),
      $blueLights = $('#blue-lights'),
      $letsRock = $('#lets-rock'),
      $curtains = $('.curtains'),
      $dimmed = $('.dimmed'),
      $shadow = $('#shadow'),
      $diary = $('#diary'),
      $tapeRecorder = $('#tape-recorder'),
      $bPlay = $('#play'),
      $bStop = $('#stop'),
      $bRecord = $('#record'),
      $bEject = $('#eject'),
      $tapeCircles = $('.tape-circle'),
      $redLight = $('.red-light'),
      $info = $('#info'),
      $infoIcon = $('#info-icon'),
      $infoText = $('#info-text'),
      $github = $('.github'),
      readyToStartRecording = false;

  /* Variables for the audio recording process */

  var recorder,
      audio = document.createElement('audio'),
      a = $('a')[0];

  // Set up getUserMedia, AudioContext and URL
  navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
  window.URL = window.URL || window.webkitURL;

  // A nice easter egg...
  console.log('One day my log will have something to say about this');

  /* Animation and transition events*/

  // Show title when the lights animation has ended
  $lights.on('animationend webkitAnimationEnd', showTitle);

  // Show Let's Rock div when the title transition has ended
  $title.on('transitionend webkitTransitionEnd', dimRoom);

  $dimmed.on('transitionend  webkitTransitionEnd', function() {
    if (readyToStartRecording) {
      showTapeRecorder();
    }
    else {
      showLetsRock();
    }
  });

  // Hide lights div when the Let's Rock transition has ended
  $letsRock.on('transitionend webkitTransitionEnd', handleLetsRockTransitions);

  // Remove flashing lights when the flashing and flipping animations end
  $letsRock.on('animationend webkitAnimationEnd', prepareForRecording);

  // Hide diary message if the div is clicked
  $diary.on('transitionend webkitTransitionEnd', function() {
    if ($diary.css('opacity') === '0') {
      $diary.hide();
    }
  });

  $tapeRecorder.on('transitionend webkitTransitionEnd', function() {
    if ($tapeRecorder.css('opacity') === '0') {
      $tapeRecorder.hide();
    }
    else {
      listenTapeRecorderEvents(true);
    }
  });

  /* Click events */

  // Flip text when "Let's rock" is clicked
  $letsRock.on('click', startRecordingAnimation);

  // Hide diary when is clicked
  $diary.on('click', function() {
    hideDiaryMessage();
  });

  // Toggle the info message visibility
  var toggleInfoMessage = (function() {
    var margin = $info.css('margin-bottom'),
    arrowOpacity = $infoIcon.css('opacity');

    return function() {
      if ($info.css('margin-bottom') === margin) {
        $infoIcon.css('opacity', '1');
        $info.css('margin-bottom', '0');
      }
      else {
        $infoIcon.css('opacity', arrowOpacity);
        $info.css('margin-bottom', margin);
      }
    };
  }());

  $infoIcon.on('click', toggleInfoMessage);
  $window.on('click', toggleInfoMessage);
  $github.on('click', function(e) {
    e.stopPropagation();
  });

  function dimRoom() {
    dimCurtains();
    showShadow();
  }

  function dimCurtains() {
    // Show destroys the opacity transition except in IE
    $dimmed.show();

    // Hack for Firefox and Webkit browsers
    setTimeout(function() {
      $dimmed.css('opacity', '0.5');
    }, 50);
  }

  function showShadow() {
    $shadow.addClass('movement');
    $shadow.show();
  }

  function illuminateRoom() {
    illuminateCurtains();
    hideShadow();
  }

  function illuminateCurtains() {
    // Hide destroys the opacity transition except in IE
    $dimmed.hide();
    $dimmed.css('opacity', '0.0');
  }

  function hideShadow() {
    $shadow.removeClass('movement');
    $shadow.hide();
  }

  function showTitle() {
    $title.css('opacity', '1.0');
  }

  function showLetsRock() {
    $letsRock.css('opacity', '1.0');
  }

  function handleLetsRockTransitions(e) {
    var property = e.originalEvent.propertyName;

    if (property === 'opacity') {
      keepLightsOn();
    }
  }

  function keepLightsOn() {
    $lights.removeClass('turn-lights-on');
    $lights.hide();
  }

  function startRecordingAnimation() {
    illuminateRoom();
    setFlashingLights();
    flipText();
    readyToStartRecording = true;
  }

  function setFlashingLights() {
    $lights.show();
    $blueLights.show();
    $lights.addClass('flashing-lights');
    $blueLights.addClass('flashing-blue-lights');
  }

  function removeFlashingLights() {
    $lights.hide();
    $blueLights.hide();
    $lights.removeClass('flashing-lights');
    $blueLights.removeClass('flashing-blue-lights');
  }

  function flipText() {
    $title.addClass('flipping-text');
    $letsRock.addClass('flipping-text');
  }

  function restoreTextOrientation() {
    $title.removeClass('flipping-text');
    $letsRock.removeClass('flipping-text');
  }

  function showDiaryMessage() {
    $diary.show();

    // Hack for Firefox and Webkit browsers
    setTimeout(function() {
      $diary.css('opacity', '1.0');
    }, 50);
  }

  function hideDiaryMessage() {
    $diary.css('opacity', '0.0');
  }

  function showTapeRecorder() {
    $tapeRecorder.css('display', 'inline-block');

    // Hack for Firefox and Webkit browsers
    setTimeout(function() {
      $tapeRecorder.css('opacity', '1.0');
    }, 50);
  }

  function hideTapeRecorder() {
    unpressButtons();
    listenTapeRecorderEvents(false);
    $tapeRecorder.css('opacity', '0.0');
  }

  function startTapeAnimation() {
    if (!($tapeCircles.hasClass('spin'))) {
      $tapeCircles.addClass('spin');
    }
  }

  function stopTapeAnimation() {
    $tapeCircles.removeClass('spin');
  }

  function turnOnRecordingLight() {
    $redLight.css('background', '#f00');
  }

  function turnOffRecordingLight() {
    $redLight.css('background', '#000');
  }

  function isRecordingAudio() {
    return (recorder && recorder.isRecording());
  }

  function unpressButtons() {
    if ($bRecord.hasClass('pressed')) {
      $bRecord.removeClass('pressed');
    }

    if ($bPlay.hasClass('pressed')) {
      $bPlay.removeClass('pressed');
    }
  }

  function toggleButtonsPressed(buttons) {
    for (var i = 0; i < buttons.length; i++) {
      if (buttons[i].hasClass('pressed')) {
        buttons[i].removeClass('pressed');
      }
      else {
        buttons[i].addClass('pressed');
      }
    }
  }

  /* Audio recording process */

  // Audio player events
  $(audio).on('paused ended', function() {
    toggleButtonsPressed([$bPlay]);
    stopTapeAnimation();
  });

  $(audio).on('play', function() {
    toggleButtonsPressed([$bPlay]);
    startTapeAnimation();
  });

  // Buttons of tape recorder
  function listenTapeRecorderEvents(enabled) {
    // Stop listening click events
    if (!enabled) {
      $bRecord.off('click');
      $bStop.off('click');
      $bPlay.off('click');
      return;
    }

    // Start listening click events

    // The recording button of the tape recorder is clicked
    $bRecord.on('click', function() {
      if (!isRecordingAudio() && !isPlayingAudio()) {
        askForRecordingPermission();
      }

    });

    // Stop audio if playing or stop recording
    $bStop.on('click', function() {
      if (isRecordingAudio() || isPlayingAudio()) {
        if (isRecordingAudio()) {
          stopRecording();
          turnOffRecordingLight();
        }
        else {
          stopAudio();
        }
        stopTapeAnimation();
        toggleButtonsPressed([$bRecord, $bPlay]);
      }
    });

    $bPlay.on('click', function() {
        if (!isRecordingAudio() && !isPlayingAudio()) {
          playAudio();
        }

    });
  }

  function prepareForRecording() {
    removeFlashingLights();
    dimRoom();

    $letsRock.off('click');
    $letsRock.on('click', function() {
      restoreTextOrientation();
      hideTapeRecorder();
      readyToStartRecording = false;

      // Hide the diary message if it is visible
      if ($diary.css('opacity') === '1') {
        hideDiaryMessage();
      }

      // Check if audio is being recorded or played when "Let's rock" is clicked
      if (isPlayingAudio() || isRecordingAudio()) {
        if (isPlayingAudio()) {
          stopAudio();
        }

        if (isRecordingAudio()) {
          stopRecording();
          turnOffRecordingLight();
        }
        stopTapeAnimation();
      }

      $letsRock.off('click');
      $letsRock.on('click', startRecordingAnimation);
    });

    //showTapeRecorder();
  }

  function askForRecordingPermission() {
    if (!recorder) {
      if (navigator.getUserMedia) {
        navigator.getUserMedia({audio:true},
          function(stream) {
            initializeRecorder(stream);
            if (recorder) {
              startRecordingProcess();
            }
          },
          function(error) {
            console.log('Error: ' + error);
          });
      }
      else {
        showDiaryMessage();
      }
    }
    else {
      startRecordingProcess();
    }
  }

  // Start tape animation and start recording
  function startRecordingProcess() {
    turnOnRecordingLight();
    startTapeAnimation();
    toggleButtonsPressed([$bRecord, $bPlay]);
    startRecording();
  }

  // Initialize Recorder object for the first time
  function initializeRecorder(stream) {
    var audioContext = new AudioContext();
    var input = audioContext.createMediaStreamSource(stream);

    // Hack for Firefox - Prevent the action of garbage collector
    if (navigator.userAgent.toLowerCase().indexOf('firefox') !== -1) {
      window.audioInputHackForFirefox = input;
    }

    recorder = new Recorder(input, {workerPath:'js/recorderWorker.js'});
  }

  function startRecording() {
    if (!recorder) {
      return;
    }

    recorder.record();
  }

  function stopRecording() {
    if (!recorder) {
      return;
    }

    recorder.stop();
    createWAV();
    recorder.clear();
  }

  function playAudio() {
    if (audio.src !== '') {
      audio.play();
    }
  }

  function stopAudio() {
    audio.pause();
    audio.currentTime = 0;
  }

  function isPlayingAudio() {
    return !audio.paused;
  }

  // Create WAV file, load it in the audio player
  // and set up a download link
  function createWAV() {
    if (!recorder) {
      return;
    }

    recorder.exportWAV(function(blob) {
      var url = URL.createObjectURL(blob);
      audio.src = url;
      a.href = url;
      a.download = 'message.wav';
    });
  }

}(jQuery, window, navigator, Recorder));
<|endoftext|>


<commit_before>var circle, path, text, rect;

var	width = 900,
    height = 550,
	r = 12;

var x = d3.scale.linear()
    .domain([0, width])
    .range([0, width]);

var y = d3.scale.linear()
    .domain([0, height])
    .range([height, 0]);

var force = d3.layout.force()
	    .charge(-400)
	    .linkDistance(40)
	    .size([width, height]);

var zoom = d3.behavior.zoom().x(y).y(y).scaleExtent([.5, 5]).on("zoom", redraw)

var svg = d3.select("#tpt").append("svg:svg")
      	.attr("width", width)
      	.attr("height", height)
	  	.attr("viewBox", "0 0 " + width + " " + height)
      	.attr("preserveAspectRatio", "XminYmin meet")
		.attr("pointer-events", "all")
		.call(zoom);

var vis = svg.append("g");

// Use elliptical arc path segments to doubly-encode directionality.
force.on('tick', function() {
  path.attr("d", linkArc);
  circle.attr("transform", transform);
  text.attr("transform", transform);
});

function linkArc(d) {
  var x = (d.target.x),
      y = (d.target.y),
      dx = x - d.source.x,
      dy = y - d.source.y,
      dr = Math.sqrt(dx * dx + dy * dy);
  return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + x + "," + y;
}

function transform(d) {
  return "translate(" + d.x + "," + d.y + ")";
}

function redraw() {
   tx = d3.event.translate[0],
   ty = d3.event.translate[1];
   tx = Math.min(tx, 1.1*d3.event.scale*width);
   tx = Math.max(tx, -1.1*d3.event.scale*width);
   ty = Math.min(ty, 1.1*d3.event.scale*width);
   ty = Math.max(ty, -1.1*d3.event.scale*width);
	vis.attr("transform",
	 "translate(" + [tx, ty] + ")"
	 + " scale(" + d3.event.scale + ")");
}

function createGraph(data){

		var maxflux = 0,
			maxpagerank = 0;
		if (data.directed) {
			vis.append("g:defs").selectAll("marker")
			    .data(["suit"])
			  .enter().append("marker")
			    .attr("id", function(d) { return d; })
			    .attr("viewBox", "0 -5 10 10")
			    .attr("refX", 15)
			    .attr("refY", -1.5)
			    .attr("markerWidth", 6)
			    .attr("markerHeight", 6)
			    .attr("orient", "auto")
			  .append("path")
			    .attr("d", "M0,-5L10,0L0,5");
		};

		data.links.forEach(function(l) {
		  l.source = data.nodes[l.source] || (data.nodes[l.source] = {name: l.source});
		  l.target = data.nodes[l.target] || (data.nodes[l.target] = {name: l.target});
		  if (l.weight > maxflux) {maxflux = l.weight;};
		});

		data.nodes.forEach(function(n) {
			if (n.pagerank != null) {
				if (n.pagerank > maxpagerank) {maxpagerank = n.pagerank;};
			};
		});

		path = vis.append("g:g").selectAll("path")
		    .data(data.links)
		  .enter().append("path")
		    .attr("class", "link")
			.attr("marker-end", function(d) { return "url(#" + "suit" + ")"; })
			.attr("stroke-width", function(d) { return 3*Math.exp(d.weight/maxflux - 1) + "px"; });

		circle = vis.append("g:g").selectAll("circle")
		    .data(data.nodes)
		  .enter().append("circle")
		    .attr("r", function(d) {if (d.pagerank != null) { return 1.7*r*d.pagerank/maxpagerank} return r})
			.attr("class", function(d) { if (d.type != null) {return "circle " + d.type; } return "circle none"})
		    .call(force.drag);

		text = vis.append("g:g").selectAll("text")
		    .data(data.nodes)
		  .enter().append("text")
		    .attr("x", 0)
		    .attr("y", 4)
			.attr("class","id")
		    .text(function(d) { return d.id; });

	    force
	        .nodes( data.nodes )
	        .links( data.links )
	        .start();

		vis.style("opacity", 1e-6)
		   .transition()
		   .duration(1000)
		   .style("opacity", 1);

}

function updateGraph(data){
	$("circle").parent().remove();
	$("path").parent().remove();
	$("text").parent().remove();
	createGraph(data);

}<commit_msg>add minimum node size<commit_after>var circle, path, text, rect;

var	width = 900,
    height = 550,
	r = 12;

var x = d3.scale.linear()
    .domain([0, width])
    .range([0, width]);

var y = d3.scale.linear()
    .domain([0, height])
    .range([height, 0]);

var force = d3.layout.force()
	    .charge(-400)
	    .linkDistance(40)
	    .size([width, height]);

var zoom = d3.behavior.zoom().x(y).y(y).scaleExtent([.5, 5]).on("zoom", redraw)

var svg = d3.select("#tpt").append("svg:svg")
      	.attr("width", width)
      	.attr("height", height)
	  	.attr("viewBox", "0 0 " + width + " " + height)
      	.attr("preserveAspectRatio", "XminYmin meet")
		.attr("pointer-events", "all")
		.call(zoom);

var vis = svg.append("g");

// Use elliptical arc path segments to doubly-encode directionality.
force.on('tick', function() {
  path.attr("d", linkArc);
  circle.attr("transform", transform);
  text.attr("transform", transform);
});

function linkArc(d) {
  var x = (d.target.x),
      y = (d.target.y),
      dx = x - d.source.x,
      dy = y - d.source.y,
      dr = Math.sqrt(dx * dx + dy * dy);
  return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + x + "," + y;
}

function transform(d) {
  return "translate(" + d.x + "," + d.y + ")";
}

function redraw() {
   tx = d3.event.translate[0],
   ty = d3.event.translate[1];
   tx = Math.min(tx, 1.1*d3.event.scale*width);
   tx = Math.max(tx, -1.1*d3.event.scale*width);
   ty = Math.min(ty, 1.1*d3.event.scale*width);
   ty = Math.max(ty, -1.1*d3.event.scale*width);
	vis.attr("transform",
	 "translate(" + [tx, ty] + ")"
	 + " scale(" + d3.event.scale + ")");
}

function createGraph(data){

		var maxflux = 0,
			maxpagerank = 0;
		if (data.directed) {
			vis.append("g:defs").selectAll("marker")
			    .data(["suit"])
			  .enter().append("marker")
			    .attr("id", function(d) { return d; })
			    .attr("viewBox", "0 -5 10 10")
			    .attr("refX", 15)
			    .attr("refY", -1.5)
			    .attr("markerWidth", 6)
			    .attr("markerHeight", 6)
			    .attr("orient", "auto")
			  .append("path")
			    .attr("d", "M0,-5L10,0L0,5");
		};

		data.links.forEach(function(l) {
		  l.source = data.nodes[l.source] || (data.nodes[l.source] = {name: l.source});
		  l.target = data.nodes[l.target] || (data.nodes[l.target] = {name: l.target});
		  if (l.weight > maxflux) {maxflux = l.weight;};
		});

		data.nodes.forEach(function(n) {
			if (n.pagerank != null) {
				if (n.pagerank > maxpagerank) {maxpagerank = n.pagerank;};
			};
		});

		path = vis.append("g:g").selectAll("path")
		    .data(data.links)
		  .enter().append("path")
		    .attr("class", "link")
			.attr("marker-end", function(d) { return "url(#" + "suit" + ")"; })
			.attr("stroke-width", function(d) { return 3*Math.exp(d.weight/maxflux - 1) + "px"; });

		circle = vis.append("g:g").selectAll("circle")
		    .data(data.nodes)
		  .enter().append("circle")
		    .attr("r", function(d) {if (d.pagerank != null) { return Math.max(1.7*r*d.pagerank/maxpagerank,4)} return r})
			.attr("class", function(d) { if (d.type != null) {return "circle " + d.type; } return "circle none"})
		    .call(force.drag);

		text = vis.append("g:g").selectAll("text")
		    .data(data.nodes)
		  .enter().append("text")
		    .attr("x", 0)
		    .attr("y", 4)
			.attr("class","id")
		    .text(function(d) { return d.id; });

	    force
	        .nodes( data.nodes )
	        .links( data.links )
	        .start();

		vis.style("opacity", 1e-6)
		   .transition()
		   .duration(1000)
		   .style("opacity", 1);

}

function updateGraph(data){
	$("circle").parent().remove();
	$("path").parent().remove();
	$("text").parent().remove();
	createGraph(data);

}<|endoftext|>