<commit_before>(function() {

	'use strict';

	angular.module('digitalfondue.dftabmenu', []).directive('dfTabMenu', ['$window','$timeout', function($window, $timeout) {
		return {
			restrict : 'E',
			compile: function($element, $attrs) {
				var doc = $window.document;
				var root = $element[0];
				$element.append('

');
				var list = $element.find('ol');

				//move elements to the main menu
				var elements = angular.element(root.querySelectorAll('li[menu-item]'));
				for(var e = 0;e < elements.length; e++) {
					list.append(elements[e]);
				};

				$element.find('ol').append('
' +
						'' +
						$attrs.moreMenuTemplate +
						'

');

				//clone elements into the more menu
				var moreList = angular.element(root.querySelector('ul.df-tab-menu-dropdown'));
				var moreElements = root.querySelectorAll('ol > li[menu-item]');
				for(var e = 0;e < elements.length; e++) {
					moreList.append(angular.element(moreElements[e]).clone());
				};

				return function ($scope, $element, $attrs) {
					var root = $element[0];
					var wdw = angular.element($window);
					var currentDisplayItems = null;
					var dropdownOpen = false;

					//var elementsSize = [];
					var getElementsSize = function() {
						var elements = root.querySelectorAll('ol > li[menu-item]');
						angular.element(elements).removeClass('ng-hide');
						var elementsSize = [];
						for(var e = 0;e < elements.length; e++) {
							var size = elements[e].offsetWidth;
							if(size > 0) {
								elementsSize[e] = elements[e].offsetWidth;
							}
						};
						return elementsSize;
					}

					// handle directive (such as ng-translate) that may change the size of the elements
					var unregister = $scope.$watch(function() {
						var element = root.querySelector('ol[main-menu].df-tab-menu > li[menu-item].df-tab-menu-active');
						return element != null ? element.scrollWidth : 0;
					}, function(w, oldW) {
						if(w != null && w > 0) {
							//getElementsSize();
							buildMenu();
						}
					}, true);

					var getMoreElementSize = function() {
						angular.element(root.querySelector('ol > li[more-menu-item]')).removeClass('ng-hide');
						return root.querySelector('ol[main-menu].df-tab-menu > li[more-menu-item]').offsetWidth;
					}

					var getVisibleItems = function(_maxWidth, _activeItemIndex) {
						var visibleItems = [];
						var elementsSize = getElementsSize();
						//40px: scrollbar tolerance. Not proud of this, but it works...
						var sum = elementsSize[_activeItemIndex] + getMoreElementSize() + 40;
						visibleItems.push(_activeItemIndex);
						var items = root.querySelectorAll('ol > li[menu-item]');
						for(var i = 0; i < items.length; i++) {
							if(i != _activeItemIndex) {
								sum += elementsSize[i];
								if(sum > _maxWidth) {
									return visibleItems;
								} else {
									visibleItems.push(i);
								}
							}
						}
						return visibleItems;
					};

					var getActiveItemIndex = function() {
						var items = root.querySelectorAll('ol > li[menu-item]');
						for(var i = 0; i < items.length; i++) {
							if(angular.element(items[i]).hasClass('df-tab-menu-active')) {
								return i;
							}
						}
					}

					var buildMenu = function() {
						//
						//
						var maxWidth = root.querySelector('ol').offsetWidth;
						var activeItemIndex = getActiveItemIndex();
						var visibleItems = getVisibleItems(maxWidth, activeItemIndex);

						if(visibleItems.length < root.querySelectorAll('ol > li[menu-item]').length) {
							currentDisplayItems = visibleItems.length;
							angular.element(root.querySelector('ol > li[more-menu-item]')).removeClass('ng-hide');

							var elements = root.querySelectorAll('ol > li[menu-item]');

							var moreElements = root.querySelectorAll('ul[more-menu] > li[menu-item]');
							for(var i = 0; i < elements.length; i++) {
								if(visibleItems.indexOf(i) != -1) {
									angular.element(elements[i]).removeClass('ng-hide');
									angular.element(moreElements[i]).addClass('ng-hide');
								} else {
									angular.element(elements[i]).addClass('ng-hide');
									angular.element(moreElements[i]).removeClass('ng-hide');
								}
							}
						} else {
							currentDisplayItems = null;
							angular.element(root.querySelector('ol > li[more-menu-item]')).addClass('ng-hide');
							angular.element(root.querySelectorAll('ol > li[menu-item]')).removeClass('ng-hide');

							dropdownOpen = false;
							drawDropDown();
							//FIXME (?)
							/*$scope.$evalAsync(function() {
								$scope.dropdownOpen = false;
							});*/
						}
					};

					var drawDropDown = function() {
						if(dropdownOpen) {
							angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).addClass('df-tab-menu-dropdown-open');
							angular.element(doc).bind('click', closeDropdown);
						} else {
							angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).removeClass('df-tab-menu-dropdown-open');
							angular.element(doc).unbind('click', closeDropdown);
						}
					};

					//dropdown controls
					//$scope.dropdownOpen = false;

					var closeDropdown = function() {
						//$scope.$apply(function() {
						dropdownOpen = false;
						toggleDropdown();
						//});
					};

					/*$scope.$watch('dropdownOpen', function(d) {
						if(d) {
							angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).addClass('df-tab-menu-dropdown-open');
							angular.element(doc).bind('click', closeDropdown);
						} else {
							angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).removeClass('df-tab-menu-dropdown-open');
							angular.element(doc).unbind('click', closeDropdown);
						}
					});*/

					var toggleDropdown = function(e) {
						e.stopPropagation();
						dropdownOpen = !dropdownOpen;
						drawDropDown();
						/*$scope.$apply(function() {
							$scope.dropdownOpen = !$scope.dropdownOpen;
						});*/
					};

					angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).bind('click', toggleDropdown);

					$attrs.$observe('menuControl', function(c) {
						if(root.querySelector('ol > li[menu-item=\"' + c + '\"]') == null) {
							throw "Invalid state: " + c + ", please verify the menu-item(s) configuration";
						}
						//set active state
						angular.element(root.querySelector('ol > li.df-tab-menu-active')).removeClass('df-tab-menu-active');
						angular.element(root.querySelector('ol > li[menu-item=\"' + c + '\"]')).addClass('df-tab-menu-active');
						//$scope.dropdownOpen = false;
						// force redrawing
						currentDisplayItems = null;
						buildMenu();
		            });

					wdw.bind('resize', buildMenu);

					$scope.$on('$destroy', function() {
						wdw.unbind('resize', buildMenu);
						angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).unbind('click', toggleDropdown);
						angular.element(doc).unbind('click', closeDropdown);
					});

					var buildMenuTimeout;
					$scope.$watch(function() {
						$timeout.cancel(buildMenuTimeout);
						buildMenuTimeout = $timeout(buildMenu, 25, false);
					});
				};
		     }
		}

	}]);
})();<commit_msg>support dynamic menu: fix issues with toggle menu...<commit_after>(function() {

	'use strict';

	angular.module('digitalfondue.dftabmenu', []).directive('dfTabMenu', ['$window','$timeout', function($window, $timeout) {
		return {
			restrict : 'E',
			compile: function($element, $attrs) {
				var doc = $window.document;
				var root = $element[0];
				$element.append('

');
				var list = $element.find('ol');

				//move elements to the main menu
				var elements = angular.element(root.querySelectorAll('li[menu-item]'));
				for(var e = 0;e < elements.length; e++) {
					list.append(elements[e]);
				};

				$element.find('ol').append('
' +
						'' +
						$attrs.moreMenuTemplate +
						'

');

				//clone elements into the more menu
				var moreList = angular.element(root.querySelector('ul.df-tab-menu-dropdown'));
				var moreElements = root.querySelectorAll('ol > li[menu-item]');
				for(var e = 0;e < elements.length; e++) {
					moreList.append(angular.element(moreElements[e]).clone());
				};

				return function ($scope, $element, $attrs) {
					var root = $element[0];
					var wdw = angular.element($window);
					var currentDisplayItems = null;
					var dropdownOpen = false;

					//var elementsSize = [];
					var getElementsSize = function() {
						var elements = root.querySelectorAll('ol > li[menu-item]');
						angular.element(elements).removeClass('ng-hide');
						var elementsSize = [];
						for(var e = 0;e < elements.length; e++) {
							var size = elements[e].offsetWidth;
							if(size > 0) {
								elementsSize[e] = elements[e].offsetWidth;
							}
						};
						return elementsSize;
					}

					// handle directive (such as ng-translate) that may change the size of the elements
					var unregister = $scope.$watch(function() {
						var element = root.querySelector('ol[main-menu].df-tab-menu > li[menu-item].df-tab-menu-active');
						return element != null ? element.scrollWidth : 0;
					}, function(w, oldW) {
						if(w != null && w > 0) {
							//getElementsSize();
							buildMenu();
						}
					}, true);

					var getMoreElementSize = function() {
						angular.element(root.querySelector('ol > li[more-menu-item]')).removeClass('ng-hide');
						return root.querySelector('ol[main-menu].df-tab-menu > li[more-menu-item]').offsetWidth;
					}

					var getVisibleItems = function(_maxWidth, _activeItemIndex) {
						var visibleItems = [];
						var elementsSize = getElementsSize();
						//40px: scrollbar tolerance. Not proud of this, but it works...
						var sum = elementsSize[_activeItemIndex] + getMoreElementSize() + 40;
						visibleItems.push(_activeItemIndex);
						var items = root.querySelectorAll('ol > li[menu-item]');
						for(var i = 0; i < items.length; i++) {
							if(i != _activeItemIndex) {
								sum += elementsSize[i];
								if(sum > _maxWidth) {
									return visibleItems;
								} else {
									visibleItems.push(i);
								}
							}
						}
						return visibleItems;
					};

					var getActiveItemIndex = function() {
						var items = root.querySelectorAll('ol > li[menu-item]');
						for(var i = 0; i < items.length; i++) {
							if(angular.element(items[i]).hasClass('df-tab-menu-active')) {
								return i;
							}
						}
					}

					var buildMenu = function() {
						//
						//
						var maxWidth = root.querySelector('ol').offsetWidth;
						var activeItemIndex = getActiveItemIndex();
						var visibleItems = getVisibleItems(maxWidth, activeItemIndex);

						if(visibleItems.length < root.querySelectorAll('ol > li[menu-item]').length) {
							currentDisplayItems = visibleItems.length;
							angular.element(root.querySelector('ol > li[more-menu-item]')).removeClass('ng-hide');

							var elements = root.querySelectorAll('ol > li[menu-item]');

							var moreElements = root.querySelectorAll('ul[more-menu] > li[menu-item]');
							for(var i = 0; i < elements.length; i++) {
								if(visibleItems.indexOf(i) != -1) {
									angular.element(elements[i]).removeClass('ng-hide');
									angular.element(moreElements[i]).addClass('ng-hide');
								} else {
									angular.element(elements[i]).addClass('ng-hide');
									angular.element(moreElements[i]).removeClass('ng-hide');
								}
							}
						} else {
							currentDisplayItems = null;
							angular.element(root.querySelector('ol > li[more-menu-item]')).addClass('ng-hide');
							angular.element(root.querySelectorAll('ol > li[menu-item]')).removeClass('ng-hide');

							dropdownOpen = false;
							drawDropDown();
							//FIXME (?)
							/*$scope.$evalAsync(function() {
								$scope.dropdownOpen = false;
							});*/
						}
					};

					var closeDropdown = function(e) {
						//$scope.$apply(function() {
						dropdownOpen = false;
						drawDropDown(e);
						//});
					};

					var drawDropDown = function() {
						if(dropdownOpen) {
							angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).addClass('df-tab-menu-dropdown-open');
							angular.element(doc).bind('click', closeDropdown);
						} else {
							angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).removeClass('df-tab-menu-dropdown-open');
							angular.element(doc).unbind('click', closeDropdown);
						}
					};

					//dropdown controls
					//$scope.dropdownOpen = false;

					/*$scope.$watch('dropdownOpen', function(d) {
						if(d) {
							angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).addClass('df-tab-menu-dropdown-open');
							angular.element(doc).bind('click', closeDropdown);
						} else {
							angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).removeClass('df-tab-menu-dropdown-open');
							angular.element(doc).unbind('click', closeDropdown);
						}
					});*/

					var toggleDropdown = function(e) {
						if(e) {e.stopPropagation()};
						dropdownOpen = !dropdownOpen;
						drawDropDown();
						/*$scope.$apply(function() {
							$scope.dropdownOpen = !$scope.dropdownOpen;
						});*/
					};

					angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).bind('click', toggleDropdown);

					$attrs.$observe('menuControl', function(c) {
						if(root.querySelector('ol > li[menu-item=\"' + c + '\"]') == null) {
							throw "Invalid state: " + c + ", please verify the menu-item(s) configuration";
						}
						//set active state
						angular.element(root.querySelector('ol > li.df-tab-menu-active')).removeClass('df-tab-menu-active');
						angular.element(root.querySelector('ol > li[menu-item=\"' + c + '\"]')).addClass('df-tab-menu-active');
						//$scope.dropdownOpen = false;
						// force redrawing
						currentDisplayItems = null;
						buildMenu();
		            });

					wdw.bind('resize', buildMenu);

					$scope.$on('$destroy', function() {
						wdw.unbind('resize', buildMenu);
						angular.element(root.querySelector('.df-tab-menu a[dropdown-toggle]')).unbind('click', toggleDropdown);
						angular.element(doc).unbind('click', closeDropdown);
					});

					var buildMenuTimeout;
					$scope.$watch(function() {
						$timeout.cancel(buildMenuTimeout);
						buildMenuTimeout = $timeout(buildMenu, 25, false);
					});
				};
		     }
		}

	}]);
})();<|endoftext|>


<commit_before>$(function() {
$("#search_field").tokenInput("/searches/suggest_gems.json", {
	onAdd: function (item) {
	$('input[id$=search_field]').val(item.name);
    },
    crossDomain: false,
    tokenLimit: 1,
    tokenValue: name
	});
$('input[id$=token-input-search_field]').keyup(function() {
	var copyVal = $(this).val();
	$('input[id$=search_field]').val(copyVal);
	});
});
<commit_msg>add hack to preserve search text if search box loses focus<commit_after>$(function() {
  var search_text = null;

  $("#search_field").tokenInput("/searches/suggest_gems.json", {
  	onAdd: function (item) {
      $('input[id$=search_field]').val(item.name);
    },
      crossDomain: false,
      tokenLimit: 1,
      tokenValue: name
  });

  $('input[id$=token-input-search_field]').keyup(function() {
    search_text = $(this).val();
    $('input[id$=search_field]').val(search_text);
  });

  // XXX hack to preserve search text search box
  // looses focus
  $("#token-input-search_field").blur(function(event){
    $(event.target).val(search_text);
  });

});
<|endoftext|>


<commit_before>// gently stolen from: https://github.com/arthurtsang/api-designer/commit/2ad4f3e253b17fa95ea3ea012419d933b098d914
// just linted and indented
angular.module('ramlEditorApp')
.factory('RamlServeFS', function($http, $q, config) {
  var service = {};

  function errorFunction(data, status, headers, config) {
    alert(status + ': ' + data);
  }

  service.directory = function(path) {
    var deferred = $q.defer();

    if (path === '/') {
      deferred.resolve({
        path: '/',
        name: '/',
        type: 'folder',
        children: [
          {
            path: '/api.raml',
            name: 'api.raml',
            type: 'file'
          },
          {
            path: '/test.raml',
            name: 'test.raml',
            type: 'file'
          }
        ]
      });
    }
    if (path === '/api.raml') {
      deferred.resolve({
        path: '/api.raml',
        name: 'api.raml',
        type: 'file'
      });
    }
    // $http({
    //     method: 'GET',
    //     data: '',
    //     url: 'files/' + ((path === '/') ? '' : path),
    //     withCredentials: false
    //   }).success(function(data) {
    //     deferred.resolve({
    //       path: path,
    //       meta: {},
    //       children: data
    //     });
    //   })
    //   .error(errorFunction);
    return deferred.promise;
  };

  service.load = function(path, name) {
    var deferred = $q.defer();
    if (path.endsWith('.meta')) {
      deferred.resolve('{ "created": "' + Date.now() + '" }');
    }
    if (path.endsWith('.raml')) {
      deferred.resolve('#%RAML 0.8\ntitle: tutored private api\nversion: 0.1.0\nbaseUri: http://localhost:8080/api\nmediaType: application/json\nschemas:\n  - ParseId: |\n      {\n        "type": "string",\n        "minLength": 10,\n        "maxLength": 10\n      }\n  - Lesson: |\n      {\n        "definitions": {\n          "parseId": {\n            "type": "string",\n            "minLength": 10,\n            "maxLength": 10\n          }\n        },\n        "type": "object",\n        "properties": {\n          "tutor": { "$ref": "#/definitions/parseId" },\n          "student": { "$ref": "#/definitions/parseId" }\n        }\n      }\nprotocols: [ HTTP ]\n/lesson:\n  post:\n    description: "creates a new lesson: a student books a lesson with a tutor"\n    body:\n      application/json:\n        schema: Lesson\n        example: |\n          {\n            "tutor": "asd0masd01",\n            "student": "asd0masd02"\n          }\n    responses:\n      200:\n        description: everything went well!\n        body:\n          application/json:\n            example: |\n              { "message": "everything done" }\n');
    }

    // $http({
    //     method: 'GET',
    //     data: '',
    //     url: 'files/' + path,
    //     withCredentials: false
    //   }).success(function(data) {
    //     deferred.resolve(data[0].content);
    //   })
    //   .error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  service.remove = function(path, name) {
    var deferred = $q.defer();
    // $http({
    //   method: 'DELETE',
    //   data: '',
    //   url: 'files/' + path,
    //   withCredentials: false
    // }).success(function(data) {
    deferred.resolve();
    // }).error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  service.rename = function(source, destination) {
    var deferred = $q.defer();
    // $http({
    //   method: 'PUT',
    //   data: {
    //     action: 'rename',
    //     newName: destination
    //   },
    //   url: 'files/' + source,
    //   withCredentials: false
    // }).success(function(data) {
    deferred.resolve();
    // }).error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  service.createFolder = function(path) {
    var deferred = $q.defer();
    // $http({
    //   method: 'POST',
    //   data: {
    //     type: 'folder'
    //   },
    //   url: 'files/' + path,
    //   withCredentials: false
    // }).success(function(data) {
    deferred.resolve();
    // }).error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  service.save = function(path, contents) {
    var deferred = $q.defer();
    // $http({
    //   method: 'POST',
    //   data: {
    //     type: 'file',
    //     content: contents
    //   },
    //   url: 'files/' + path,
    //   withCredentials: false
    // }).success(function(data) {
    deferred.resolve();
    // }).error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  return service;
})
.run(function (RamlServeFS, config) {
  // Set RamlServeFS as the filesystem to use
  config.set('fsFactory', 'RamlServeFS');
});
<commit_msg>persistence module rework, mostly path issues<commit_after>// gently stolen from: https://github.com/arthurtsang/api-designer/commit/2ad4f3e253b17fa95ea3ea012419d933b098d914
// just linted and indented
angular.module('ramlEditorApp')
.factory('APIStore', function($http, $q, config) {
  var service = {};

  function errorFunction(data, status, headers, config) {
    alert(status + ': ' + data);
  }

  service.directory = function(path) {
    var deferred = $q.defer();
    $http({
      method: 'GET',
      url: 'files/' + ((path === '/') ? '' : path),
      withCredentials: false
    }).success(function (data) {
      deferred.resolve(data);
    })
    .error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  service.load = function(path, name) {
    var deferred = $q.defer();
    $http({
        method: 'GET',
        url: 'files' + path,
        withCredentials: false
      }).success(function(data) {
        deferred.resolve(data);
      })
      .error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  service.remove = function(path, name) {
    var deferred = $q.defer();
    // $http({
    //   method: 'DELETE',
    //   data: '',
    //   url: 'files/' + path,
    //   withCredentials: false
    // }).success(function(data) {
    deferred.resolve();
    // }).error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  service.rename = function(source, destination) {
    var deferred = $q.defer();
    // $http({
    //   method: 'PUT',
    //   data: {
    //     action: 'rename',
    //     newName: destination
    //   },
    //   url: 'files/' + source,
    //   withCredentials: false
    // }).success(function(data) {
    deferred.resolve();
    // }).error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  service.createFolder = function(path) {
    var deferred = $q.defer();
    // $http({
    //   method: 'POST',
    //   data: {
    //     type: 'folder'
    //   },
    //   url: 'files/' + path,
    //   withCredentials: false
    // }).success(function(data) {
    deferred.resolve();
    // }).error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  service.save = function(path, contents) {
    var deferred = $q.defer();
    // $http({
    //   method: 'POST',
    //   data: {
    //     type: 'file',
    //     content: contents
    //   },
    //   url: 'files/' + path,
    //   withCredentials: false
    // }).success(function(data) {
    deferred.resolve();
    // }).error(deferred.reject.bind(deferred));
    return deferred.promise;
  };

  return service;
})
.run(function (APIStore, config) {
  // Set APIStore as the filesystem to use
  config.set('fsFactory', 'APIStore');
});
<|endoftext|>


<commit_before>var Project = Backbone.Model.extend({
    SRS_DEFAULT: '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs',
    initialize: function() {
        // Set default values.
        if (!this.get('srs')) {
            this.set({'srs': this.SRS_DEFAULT});
        }
        if (!this.get('Stylesheet')) {
            this.set({'Stylesheet': new StylesheetList([])});
            this.get('Stylesheet').bind('all', function() { self.save(); });
        }
        if (!this.get('Layer')) {
            this.set({'Layer': new LayerList([])});
            this.get('Layer').bind('all', function() { self.save(); });
        }
    },
    parse: function(response) {
        var self = this;
        // Instantiate StylesheetList and LayerList collections from JSON lists
        // of plain JSON objects.
        response.Stylesheet = new StylesheetList(response.Stylesheet ? response.Stylesheet : []);
        response.Stylesheet.bind('all', function() { self.save(); });
        response.Layer = new LayerList(response.Layer ? response.Layer : []);
        response.Layer.bind('all', function() { self.save(); });
        return response;
    },
    // Override url() method for convenience so we don't always need a
    // collection reference around for CRUD operations on a single model.
    url : function() {
        return '/api/project/' + this.id;
    },
    validate: function(attributes) {
        if (/^[a-z0-9\-_]+$/i.test(this.id) === false) {
            return 'Name must contain only letters, numbers, dashes, and underscores.';
        }
    }
});

var ProjectList = Backbone.Collection.extend({
    model: Project,
    url: '/api/project'
});

var ProjectListView = Backbone.View.extend({
    id: 'ProjectListView',
    tagName: 'div',
    className: 'column',
    initialize: function() {
        _.bindAll(this, 'render');
        this.collection.bind('all', this.render);
        this.collection.fetch();
    },
    render: function() {
        var self = this;
        $(this.el).html(ich.ProjectListView());
        this.collection.each(function(project) {
            var projectRow = new ProjectRowView({
                model: project,
                collection: this.collection
            });
            $('ul', self.el).append(projectRow.el);
        });
        return this;
    },
    events: {
        'click input.submit': 'add'
    },
    add: function() {
        // @TODO: this code is considerably more complicated than it should be
        // because this.collection.create() does not appear to stop itself from
        // saving a project even if its validation fails.
        // See: https://github.com/documentcloud/backbone/issues#issue/66
        window.app.loading();
        var projectId = $('input.text', this.el).val();
        var project = new Project({id: projectId});
        var self = this;
        project.save(project, {
            success: function() {
                self.collection.add(project);
                window.app.done();
            },
            error: function(project, error) {
                window.app.done();
                window.app.message('Error', error);
            }
        });
        return false;
    }
});

var ProjectRowView = Backbone.View.extend({
    tagName: 'li',
    className: 'clearfix',
    initialize: function () {
        _.bindAll(this, 'render');
        this.render();
    },
    render: function () {
        $(this.el).html(ich.ProjectRowView(this.model));
        return this;
    },
    events: {
        'click .file-delete': 'delete'
    },
    delete: function() {
        window.app.loading();
        if (confirm('Are you sure you want to delete this project?')) {
            this.model.destroy({
                success: function() {
                    window.app.done();
                },
                error: function() {
                    window.app.done();
                    window.app.message('Error', 'The project could not be deleted.');
                }
            });
        }
        else {
            window.app.done();
        }
        return false;
    }
});

var ProjectView = Backbone.View.extend({
    events: {
        'click div#header a.save': 'saveProject',
        'click div#header a.info': 'projectInfo',
        'click div#header a.minimal': 'minimal',
        'click div#header a.home': 'home'
    },
    initialize: function () {
        _.bindAll(this, 'render');
        this.model.fetch({
            success: this.render,
            error: this.render
        });
    },
    render: function() {
        $(this.el).html(ich.ProjectView(this.model));

        var layers = new LayerListView({collection: this.model.get('Layer')});
        var stylesheets = new StylesheetListView({collection: this.model.get('Stylesheet')});
        var map = new MapView({model: this.model});

        $('#sidebar', this.el).append(layers.el);
        $('#sidebar', this.el).append(map.el);
        $('#main', this.el).append(stylesheets.el);

        window.app.el.html(this.el);
        return this;
    },
    saveProject: function() {
        alert('@TODO save');
        return false;
    },
    projectInfo: function() {
        alert('@TODO projectInfo');
        return false;
    },
    home: function() {
        return (!$('div#header a.save', this.el).is('.changed') || confirm('You have unsaved changes. Are you sure you want to close this project?'));
    },
    minimal: function() {
        $('a.minimal', this.el).toggleClass('active');
        if ($('a.minimal', this.el).is('.active')) {
            $('#main').hide();
            $('a.home').hide();
            $('a.save').hide();
            $('#sidebar').css('width', '100%');
            // @TODO.
            // TileMill.project_watcher = setInterval(TileMill.project.watch, 1000);
        }
        else {
            $('#main').show();
            $('a.home').show();
            $('a.save').show();
            $('#sidebar').css('width', '30%');
            // @TODO
            // window.clearInterval(TileMill.project_watcher);
        }
        return false;
    }
});

/**
 * Router controller: Project page.
TileMill.bind('project', function() {
  var id = $.bbq.getState('id');
  TileMill.backend.get('project/' + id + '/' + id + '.mml', function(mml) {
    // Bail if MML was not valid.
    if (typeof mml != 'string') {
      TileMill.errorPage(mml.data);
      return false;
    }

    // Store current project data.
    TileMill.data.mml = mml;
    TileMill.data.id = id;
    TileMill.data.type = 'project';
    TileMill.data.filename = 'project/' + id + '/' + id + '.mml';

    // Set the unique query string.
    TileMill.data.uniq = (new Date().getTime());

    // Parse MML.
    var parsed = TileMill.mml.parseMML(mml);

    TileMill.show(ich.project({id: id}));

    var layers = TileMill.mml.init();
    var inspector = TileMill.inspector.init();
    var stylesheets = TileMill.stylesheet.init();
    var map = TileMill.map.init();
    var color = TileMill.colors.init();

    $('#sidebar').append(layers);
    $('#sidebar').append(inspector);
    $('#sidebar').append(map).addClass('with-map');
    $('#main').append(stylesheets);
    $('#main').append(color);

    // Init elements which require DOM presence.
    TileMill.colors.initFarb(color);
    // TODO: actually configure map, don't pretend to
    TileMill.map.initOL(map, TileMill.backend.servers(TileMill.mml.url()), {
        navigation: 1,
        fullscreen: 1,
        zoom: 1,
        panzoombar: 0
    }, parsed.metadata.mapCenter);

    $(document).bind('keypress', function(event) {
      if (event.charCode == 19) {
        TileMill.project.save();
        return false;
      }
    });

    // Hide inspector to start.
    inspector.hide();
    $('a.inspector-close').click(function() {
      $('#layers').show();
      $('#inspector').hide();
      return false;
    });

    setInterval(TileMill.project.status, 10000);
  });
});

// TileMill.project = {};

/**
 * Mark this project as changed (and needing to be saved).
TileMill.project.changed = function() {
  $('div#header a.save').addClass('changed');
};

TileMill.project.checkStale = function(data) {
  $('#tabs a.tab').each(function() {
    if (($.url.setUrl($(this).data('tilemill').src)
            .param('filename') == data.filename) &&
      ($(this).data('tilemill').mtime != data.mtime)) {
      TileMill.inspector.load();
      TileMill.data.uniq = (new Date().getTime());
      TileMill.map.reload(
          $('#map-preview'),
          TileMill.backend.servers(TileMill.mml.url()));
      $('div#header a.save').removeClass('changed');
      $(this).data('tilemill').mtime = data.mtime;
    }
  });
};

TileMill.project.status = function() {
  TileMill.backend.status(function(status) {
    if (status) {
        $('.status').removeClass('offline');
    } else {
        $('.status').text('tile server offline');
        $('.status').addClass('offline');
    }
  });
};

TileMill.project.watch = function() {
  $('#tabs a.tab').each(function() {
    TileMill.stylesheet.setCode($('#tabs a.active'), true);
    TileMill.stylesheet.mtime(
      $.url.setUrl($(this).data('tilemill').src).param('filename'),
      TileMill.project.checkStale);
  });
};

/**
 * Save a project from its current DOM state.
TileMill.project.save = function() {
  // Refresh storage of active stylesheet before saving.
  TileMill.stylesheet.setCode($('#tabs a.active'), true);

  var id = TileMill.data.id,
      queue = new TileMill.queue();
  queue.add(function(id, next) {
    var mml = {
      metadata: {
        mapCenter: TileMill.map.getCenter($('#map-preview'))
      },
      stylesheets: [],
      layers: []
    };

    $('#tabs a.tab').each(function() {
      mml.stylesheets.push(
          TileMill.backend.url($.url.setUrl($(this).data('tilemill').src)
              .param('filename')) + '&c=' + TileMill.data.uniq);
      TileMill.stylesheet.save(
          $.url.setUrl($(this).data('tilemill').src).param('filename'),
          $('input', this).val());
    });
    $('#layers ul.sidebar-content li').reverse().each(function() {
      var layer = $(this).data('tilemill');
      if (layer) {
        layer.status = !!$(this).find('input[type=checkbox]').is(':checked');
        mml.layers.push(layer);
      }
    });
    mml = TileMill.mml.generate(mml);
    TileMill.backend.post('project/' + id + '/' + id + '.mml', mml, next);
  }, [id]);
  queue.add(function() {
    TileMill.inspector.load();
    TileMill.data.uniq = (new Date().getTime());
    TileMill.map.reload(
        $('#map-preview'),
        TileMill.backend.servers(TileMill.mml.url()));
    $('div#header a.save').removeClass('changed');
  });
  queue.execute();
};

/**
 * Add a new project.
TileMill.project.add = function(name) {
  $('body').append(ich.loading({}));

  var queue = new TileMill.queue();
  queue.add(function(name, next) {
    var self = this;
    TileMill.backend.list('project', function(projects) {
      if ($.inArray(name, projects) !== -1) {
        $('body div.loading').remove();
        TileMill.message('Error', 'A project with the name <em>' +
            name + '</em> already exists. Please choose another name.',
            'error');
        self.reset();
      }
      else {
        next();
      }
    });
  }, [name]);
  queue.add(function(name, next) {
    TileMill.backend.post('project/' + name + '/' + name + '.mss',
      TileMill.mss.generate({
        'Map': {
          'map-bgcolor': '#fff'
        },
        '#world': {
          'polygon-fill': '#eee',
          'line-color': '#ccc',
          'line-width': '0.5'
        }
      }),
      next);
  }, [name]);
  queue.add(function(name, next) {
    var mss = 'project/' + name + '/' + name + '.mss';
    var mml = 'project/' + name + '/' + name + '.mml';
    var data = TileMill.mml.generate({
      stylesheets: [TileMill.backend.url(mss)],
      layers: [{
        id: 'world',
        srs: '&srs900913;',
        // TODO: make configurable
        file: 'http://tilemill-data.s3.amazonaws.com/world_borders_merc.zip',
        type: 'shape'
      }]
    });
    TileMill.backend.post(mml, data, next);
  }, [name]);
  queue.add(function(name) {
    $.bbq.pushState({ 'action': 'project', 'id': name });
  }, [name]);
  queue.execute();
};
*/
<commit_msg>Fix for last commit.<commit_after>var Project = Backbone.Model.extend({
    SRS_DEFAULT: '+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs',
    initialize: function() {
        var self = this;
        // Set default values.
        if (!this.get('srs')) {
            this.set({'srs': this.SRS_DEFAULT});
        }
        if (!this.get('Stylesheet')) {
            this.set({'Stylesheet': new StylesheetList([])});
            this.get('Stylesheet').bind('all', function() { self.save(); });
        }
        if (!this.get('Layer')) {
            this.set({'Layer': new LayerList([])});
            this.get('Layer').bind('all', function() { self.save(); });
        }
    },
    parse: function(response) {
        var self = this;
        // Instantiate StylesheetList and LayerList collections from JSON lists
        // of plain JSON objects.
        response.Stylesheet = new StylesheetList(response.Stylesheet ? response.Stylesheet : []);
        response.Stylesheet.bind('all', function() { self.save(); });
        response.Layer = new LayerList(response.Layer ? response.Layer : []);
        response.Layer.bind('all', function() { self.save(); });
        return response;
    },
    // Override url() method for convenience so we don't always need a
    // collection reference around for CRUD operations on a single model.
    url : function() {
        return '/api/project/' + this.id;
    },
    validate: function(attributes) {
        if (/^[a-z0-9\-_]+$/i.test(this.id) === false) {
            return 'Name must contain only letters, numbers, dashes, and underscores.';
        }
    }
});

var ProjectList = Backbone.Collection.extend({
    model: Project,
    url: '/api/project'
});

var ProjectListView = Backbone.View.extend({
    id: 'ProjectListView',
    tagName: 'div',
    className: 'column',
    initialize: function() {
        _.bindAll(this, 'render');
        this.collection.bind('all', this.render);
        this.collection.fetch();
    },
    render: function() {
        var self = this;
        $(this.el).html(ich.ProjectListView());
        this.collection.each(function(project) {
            var projectRow = new ProjectRowView({
                model: project,
                collection: this.collection
            });
            $('ul', self.el).append(projectRow.el);
        });
        return this;
    },
    events: {
        'click input.submit': 'add'
    },
    add: function() {
        // @TODO: this code is considerably more complicated than it should be
        // because this.collection.create() does not appear to stop itself from
        // saving a project even if its validation fails.
        // See: https://github.com/documentcloud/backbone/issues#issue/66
        window.app.loading();
        var projectId = $('input.text', this.el).val();
        var project = new Project({id: projectId});
        var self = this;
        project.save(project, {
            success: function() {
                self.collection.add(project);
                window.app.done();
            },
            error: function(project, error) {
                window.app.done();
                window.app.message('Error', error);
            }
        });
        return false;
    }
});

var ProjectRowView = Backbone.View.extend({
    tagName: 'li',
    className: 'clearfix',
    initialize: function () {
        _.bindAll(this, 'render');
        this.render();
    },
    render: function () {
        $(this.el).html(ich.ProjectRowView(this.model));
        return this;
    },
    events: {
        'click .file-delete': 'delete'
    },
    delete: function() {
        window.app.loading();
        if (confirm('Are you sure you want to delete this project?')) {
            this.model.destroy({
                success: function() {
                    window.app.done();
                },
                error: function() {
                    window.app.done();
                    window.app.message('Error', 'The project could not be deleted.');
                }
            });
        }
        else {
            window.app.done();
        }
        return false;
    }
});

var ProjectView = Backbone.View.extend({
    events: {
        'click div#header a.save': 'saveProject',
        'click div#header a.info': 'projectInfo',
        'click div#header a.minimal': 'minimal',
        'click div#header a.home': 'home'
    },
    initialize: function () {
        _.bindAll(this, 'render');
        this.model.fetch({
            success: this.render,
            error: this.render
        });
    },
    render: function() {
        $(this.el).html(ich.ProjectView(this.model));

        var layers = new LayerListView({collection: this.model.get('Layer')});
        var stylesheets = new StylesheetListView({collection: this.model.get('Stylesheet')});
        var map = new MapView({model: this.model});

        $('#sidebar', this.el).append(layers.el);
        $('#sidebar', this.el).append(map.el);
        $('#main', this.el).append(stylesheets.el);

        window.app.el.html(this.el);
        return this;
    },
    saveProject: function() {
        alert('@TODO save');
        return false;
    },
    projectInfo: function() {
        alert('@TODO projectInfo');
        return false;
    },
    home: function() {
        return (!$('div#header a.save', this.el).is('.changed') || confirm('You have unsaved changes. Are you sure you want to close this project?'));
    },
    minimal: function() {
        $('a.minimal', this.el).toggleClass('active');
        if ($('a.minimal', this.el).is('.active')) {
            $('#main').hide();
            $('a.home').hide();
            $('a.save').hide();
            $('#sidebar').css('width', '100%');
            // @TODO.
            // TileMill.project_watcher = setInterval(TileMill.project.watch, 1000);
        }
        else {
            $('#main').show();
            $('a.home').show();
            $('a.save').show();
            $('#sidebar').css('width', '30%');
            // @TODO
            // window.clearInterval(TileMill.project_watcher);
        }
        return false;
    }
});

/**
 * Router controller: Project page.
TileMill.bind('project', function() {
  var id = $.bbq.getState('id');
  TileMill.backend.get('project/' + id + '/' + id + '.mml', function(mml) {
    // Bail if MML was not valid.
    if (typeof mml != 'string') {
      TileMill.errorPage(mml.data);
      return false;
    }

    // Store current project data.
    TileMill.data.mml = mml;
    TileMill.data.id = id;
    TileMill.data.type = 'project';
    TileMill.data.filename = 'project/' + id + '/' + id + '.mml';

    // Set the unique query string.
    TileMill.data.uniq = (new Date().getTime());

    // Parse MML.
    var parsed = TileMill.mml.parseMML(mml);

    TileMill.show(ich.project({id: id}));

    var layers = TileMill.mml.init();
    var inspector = TileMill.inspector.init();
    var stylesheets = TileMill.stylesheet.init();
    var map = TileMill.map.init();
    var color = TileMill.colors.init();

    $('#sidebar').append(layers);
    $('#sidebar').append(inspector);
    $('#sidebar').append(map).addClass('with-map');
    $('#main').append(stylesheets);
    $('#main').append(color);

    // Init elements which require DOM presence.
    TileMill.colors.initFarb(color);
    // TODO: actually configure map, don't pretend to
    TileMill.map.initOL(map, TileMill.backend.servers(TileMill.mml.url()), {
        navigation: 1,
        fullscreen: 1,
        zoom: 1,
        panzoombar: 0
    }, parsed.metadata.mapCenter);

    $(document).bind('keypress', function(event) {
      if (event.charCode == 19) {
        TileMill.project.save();
        return false;
      }
    });

    // Hide inspector to start.
    inspector.hide();
    $('a.inspector-close').click(function() {
      $('#layers').show();
      $('#inspector').hide();
      return false;
    });

    setInterval(TileMill.project.status, 10000);
  });
});

// TileMill.project = {};

/**
 * Mark this project as changed (and needing to be saved).
TileMill.project.changed = function() {
  $('div#header a.save').addClass('changed');
};

TileMill.project.checkStale = function(data) {
  $('#tabs a.tab').each(function() {
    if (($.url.setUrl($(this).data('tilemill').src)
            .param('filename') == data.filename) &&
      ($(this).data('tilemill').mtime != data.mtime)) {
      TileMill.inspector.load();
      TileMill.data.uniq = (new Date().getTime());
      TileMill.map.reload(
          $('#map-preview'),
          TileMill.backend.servers(TileMill.mml.url()));
      $('div#header a.save').removeClass('changed');
      $(this).data('tilemill').mtime = data.mtime;
    }
  });
};

TileMill.project.status = function() {
  TileMill.backend.status(function(status) {
    if (status) {
        $('.status').removeClass('offline');
    } else {
        $('.status').text('tile server offline');
        $('.status').addClass('offline');
    }
  });
};

TileMill.project.watch = function() {
  $('#tabs a.tab').each(function() {
    TileMill.stylesheet.setCode($('#tabs a.active'), true);
    TileMill.stylesheet.mtime(
      $.url.setUrl($(this).data('tilemill').src).param('filename'),
      TileMill.project.checkStale);
  });
};

/**
 * Save a project from its current DOM state.
TileMill.project.save = function() {
  // Refresh storage of active stylesheet before saving.
  TileMill.stylesheet.setCode($('#tabs a.active'), true);

  var id = TileMill.data.id,
      queue = new TileMill.queue();
  queue.add(function(id, next) {
    var mml = {
      metadata: {
        mapCenter: TileMill.map.getCenter($('#map-preview'))
      },
      stylesheets: [],
      layers: []
    };

    $('#tabs a.tab').each(function() {
      mml.stylesheets.push(
          TileMill.backend.url($.url.setUrl($(this).data('tilemill').src)
              .param('filename')) + '&c=' + TileMill.data.uniq);
      TileMill.stylesheet.save(
          $.url.setUrl($(this).data('tilemill').src).param('filename'),
          $('input', this).val());
    });
    $('#layers ul.sidebar-content li').reverse().each(function() {
      var layer = $(this).data('tilemill');
      if (layer) {
        layer.status = !!$(this).find('input[type=checkbox]').is(':checked');
        mml.layers.push(layer);
      }
    });
    mml = TileMill.mml.generate(mml);
    TileMill.backend.post('project/' + id + '/' + id + '.mml', mml, next);
  }, [id]);
  queue.add(function() {
    TileMill.inspector.load();
    TileMill.data.uniq = (new Date().getTime());
    TileMill.map.reload(
        $('#map-preview'),
        TileMill.backend.servers(TileMill.mml.url()));
    $('div#header a.save').removeClass('changed');
  });
  queue.execute();
};

/**
 * Add a new project.
TileMill.project.add = function(name) {
  $('body').append(ich.loading({}));

  var queue = new TileMill.queue();
  queue.add(function(name, next) {
    var self = this;
    TileMill.backend.list('project', function(projects) {
      if ($.inArray(name, projects) !== -1) {
        $('body div.loading').remove();
        TileMill.message('Error', 'A project with the name <em>' +
            name + '</em> already exists. Please choose another name.',
            'error');
        self.reset();
      }
      else {
        next();
      }
    });
  }, [name]);
  queue.add(function(name, next) {
    TileMill.backend.post('project/' + name + '/' + name + '.mss',
      TileMill.mss.generate({
        'Map': {
          'map-bgcolor': '#fff'
        },
        '#world': {
          'polygon-fill': '#eee',
          'line-color': '#ccc',
          'line-width': '0.5'
        }
      }),
      next);
  }, [name]);
  queue.add(function(name, next) {
    var mss = 'project/' + name + '/' + name + '.mss';
    var mml = 'project/' + name + '/' + name + '.mml';
    var data = TileMill.mml.generate({
      stylesheets: [TileMill.backend.url(mss)],
      layers: [{
        id: 'world',
        srs: '&srs900913;',
        // TODO: make configurable
        file: 'http://tilemill-data.s3.amazonaws.com/world_borders_merc.zip',
        type: 'shape'
      }]
    });
    TileMill.backend.post(mml, data, next);
  }, [name]);
  queue.add(function(name) {
    $.bbq.pushState({ 'action': 'project', 'id': name });
  }, [name]);
  queue.execute();
};
*/
<|endoftext|>


<commit_before>import React from 'react';
import { compose, style } from 'glamor';
import { ipcRenderer } from 'electron';
import {
  IPC_WINDOW_RESIZE,
  IPC_WINDOW_COLLAPSE,
  IPC_WINDOW_EXPAND,
  IPC_LOAD_THEME,
  IPC_QUERY_COMMAND,
} from '../../../../ipc';
import QueryFieldContainer from '../../containers/query-field-container';
import ResultListContainer from '../../containers/result-list-container';

const outerBase = style({
  backgroundColor: '#f2f2f2',
  boxSizing: 'border-box',
  width: '100%',
  paddingLeft: 3,
  paddingRight: 3,
});

const base = style({
  backgroundColor: '#f2f2f2',
  color: '#333',
  borderRadius: 3,
  borderWidth: 0,
  boxSizing: 'border-box',
  fontFamily: 'Lucida Grande, Arial, sans-serif',
  fontWeight: 'lighter',
  width: '100%',
  overflow: 'hidden',
});

const getOuterStyles = theme =>
  theme && theme.window
    ? compose(outerBase, {
        backgroundColor: window.backgroundColor,
      })
    : outerBase;

const getInnerStyles = theme =>
  theme && theme.window ? compose(base, theme.window) : base;

export default class App extends React.PureComponent {
  static displayName = 'App';
  state = {
    query: '',
    results: [],
    theme: {},
    keys: [],
    details: '',
    selectedIndex: 0,
  };

  componentDidMount() {
    ipcRenderer.on(IPC_LOAD_THEME, (evt, nextTheme) => {
      this.setTheme(nextTheme);
    });
  }

  componentDidUpdate() {
    if (
      this.state.theme &&
      this.state.theme.window &&
      this.state.theme.window.width
    ) {
      ipcRenderer.send(IPC_WINDOW_RESIZE, {
        width:
          this.state.theme.window.width > 650
            ? 650
            : this.state.theme.window.width,
      });
    }
  }

  setTheme = theme => {
    this.setState({ theme });
  };

  setDetails = details => {
    this.setState({ details });
  };

  resetDetails = () => {
    this.setDetails('');
  };

  updateResults = results => {
    this.setState({ results });
  };

  resetResults = () => {
    this.setState({ results: [] });
  };

  updateQuery = nextQuery => {
    this.setState({ query: nextQuery });
    this.resetSelectedIndex();
    this.resetDetails();
    ipcRenderer.send(IPC_QUERY_COMMAND, { q: nextQuery });
    ipcRenderer.send(IPC_WINDOW_EXPAND);
  };

  resetQuery = () => {
    this.setState({ results: [] });
    ipcRenderer.send(IPC_WINDOW_COLLAPSE);
  };

  setActiveKey = key => {
    this.setState(prevState => ({
      keys: [...prevState.keys, key],
    }));
  };

  clearActiveKey = key => {
    this.setState(prevState => ({
      keys: prevState.keys.filter(keyToOmit => keyToOmit === key),
    }));
  };

  resetKeys = () => {
    this.setState({ keys: [] });
  };

  setSelectedIndex = selectedIndex => {
    this.setState({ selectedIndex });
  };

  resetSelectedIndex = () => {
    this.setState({ selectedIndex: 0 });
  };

  render() {
    const outerStyles = getOuterStyles(this.state.theme);
    const innerStyles = getInnerStyles(this.state.theme);
    return (
      <div {...outerStyles}>
        <div {...innerStyles}>
          <QueryFieldContainer
            q={this.state.query}
            theme={this.state.theme}
            onChange={this.updateQuery}
            onReset={this.resetQuery}
          />
          <ResultListContainer
            details={this.state.details}
            keys={this.state.keys}
            onClearActiveKey={this.clearActiveKey}
            onLoadDetails={this.setDetails}
            onResetKeys={this.resetKeys}
            onResetResults={this.resetResults}
            onSelectItem={this.setSelectedIndex}
            onSetActiveKey={this.setActiveKey}
            onUpdateResults={this.updateResults}
            results={this.state.results}
            selectedIndex={this.state.selectedIndex}
            theme={this.state.theme}
          />
        </div>
      </div>
    );
  }
}
<commit_msg>fix(components/app): use `theme`<commit_after>import React from 'react';
import { compose, style } from 'glamor';
import { ipcRenderer } from 'electron';
import {
  IPC_WINDOW_RESIZE,
  IPC_WINDOW_COLLAPSE,
  IPC_WINDOW_EXPAND,
  IPC_LOAD_THEME,
  IPC_QUERY_COMMAND,
} from '../../../../ipc';
import QueryFieldContainer from '../../containers/query-field-container';
import ResultListContainer from '../../containers/result-list-container';

const outerBase = style({
  backgroundColor: '#f2f2f2',
  boxSizing: 'border-box',
  width: '100%',
  paddingLeft: 3,
  paddingRight: 3,
});

const base = style({
  backgroundColor: '#f2f2f2',
  color: '#333',
  borderRadius: 3,
  borderWidth: 0,
  boxSizing: 'border-box',
  fontFamily: 'Lucida Grande, Arial, sans-serif',
  fontWeight: 'lighter',
  width: '100%',
  overflow: 'hidden',
});

const getOuterStyles = theme =>
  theme && theme.window
    ? compose(outerBase, {
        backgroundColor: theme.window.backgroundColor,
      })
    : outerBase;

const getInnerStyles = theme =>
  theme && theme.window ? compose(base, theme.window) : base;

export default class App extends React.PureComponent {
  static displayName = 'App';
  state = {
    query: '',
    results: [],
    theme: {},
    keys: [],
    details: '',
    selectedIndex: 0,
  };

  componentDidMount() {
    ipcRenderer.on(IPC_LOAD_THEME, (evt, nextTheme) => {
      this.setTheme(nextTheme);
    });
  }

  componentDidUpdate() {
    if (
      this.state.theme &&
      this.state.theme.window &&
      this.state.theme.window.width
    ) {
      ipcRenderer.send(IPC_WINDOW_RESIZE, {
        width:
          this.state.theme.window.width > 650
            ? 650
            : this.state.theme.window.width,
      });
    }
  }

  setTheme = theme => {
    this.setState({ theme });
  };

  setDetails = details => {
    this.setState({ details });
  };

  resetDetails = () => {
    this.setDetails('');
  };

  updateResults = results => {
    this.setState({ results });
  };

  resetResults = () => {
    this.setState({ results: [] });
  };

  updateQuery = nextQuery => {
    this.setState({ query: nextQuery });
    this.resetSelectedIndex();
    this.resetDetails();
    ipcRenderer.send(IPC_QUERY_COMMAND, { q: nextQuery });
    ipcRenderer.send(IPC_WINDOW_EXPAND);
  };

  resetQuery = () => {
    this.setState({ results: [] });
    ipcRenderer.send(IPC_WINDOW_COLLAPSE);
  };

  setActiveKey = key => {
    this.setState(prevState => ({
      keys: [...prevState.keys, key],
    }));
  };

  clearActiveKey = key => {
    this.setState(prevState => ({
      keys: prevState.keys.filter(keyToOmit => keyToOmit === key),
    }));
  };

  resetKeys = () => {
    this.setState({ keys: [] });
  };

  setSelectedIndex = selectedIndex => {
    this.setState({ selectedIndex });
  };

  resetSelectedIndex = () => {
    this.setState({ selectedIndex: 0 });
  };

  render() {
    const outerStyles = getOuterStyles(this.state.theme);
    const innerStyles = getInnerStyles(this.state.theme);
    return (
      <div {...outerStyles}>
        <div {...innerStyles}>
          <QueryFieldContainer
            q={this.state.query}
            theme={this.state.theme}
            onChange={this.updateQuery}
            onReset={this.resetQuery}
          />
          <ResultListContainer
            details={this.state.details}
            keys={this.state.keys}
            onClearActiveKey={this.clearActiveKey}
            onLoadDetails={this.setDetails}
            onResetKeys={this.resetKeys}
            onResetResults={this.resetResults}
            onSelectItem={this.setSelectedIndex}
            onSetActiveKey={this.setActiveKey}
            onUpdateResults={this.updateResults}
            results={this.state.results}
            selectedIndex={this.state.selectedIndex}
            theme={this.state.theme}
          />
        </div>
      </div>
    );
  }
}
<|endoftext|>


<commit_before>// Copyright 2014-2018, University of Colorado Boulder

/*
 * IMPORTANT: This file was auto-generated by "grunt generate-config". Please do not modify this directly. Instead
 * please modify energy-forms-and-changes/package.json to control dependencies.
 *
 * RequireJS configuration file for the energy-forms-and-changes sim.
 * Paths are relative to the location of this file.
 */

require.config( {

  deps: [ 'energy-forms-and-changes-main' ],

  paths: {

    // Third-party libs
    text: '../../sherpa/lib/text-2.0.12',

    // PhET plugins
    audio: '../../chipper/js/requirejs-plugins/audio',
    image: '../../chipper/js/requirejs-plugins/image',
    mipmap: '../../chipper/js/requirejs-plugins/mipmap',
    string: '../../chipper/js/requirejs-plugins/string',
    ifphetio: '../../chipper/js/requirejs-plugins/ifphetio',

    // PhET libs, uppercase names to identify them in require.js imports.
    // IMPORTANT: DO NOT modify. This file is auto-generated. See documentation at the top.
    AXON: '../../axon/js',
    BRAND: '../../brand/' + phet.chipper.brand + '/js',
    DOT: '../../dot/js',
    ENERGY_FORMS_AND_CHANGES: '.',
    JOIST: '../../joist/js',
    KITE: '../../kite/js',
    PHETCOMMON: '../../phetcommon/js',
    PHET_CORE: '../../phet-core/js',
    PHET_IO: '../../phet-io/js',
    REPOSITORY: '..',
    SCENERY: '../../scenery/js',
    SCENERY_PHET: '../../scenery-phet/js',
    SUN: '../../sun/js',
    TAMBO: '../../tambo/js',
    TANDEM: '../../tandem/js',
    TWIXT: '../../twixt/js'
  },

  // optional cache buster to make browser refresh load all included scripts, can be disabled with ?cacheBuster=false
  urlArgs: phet.chipper.getCacheBusterArgs()
} );
<commit_msg>renamed "audio" plugin to "sound"<commit_after>// Copyright 2014-2018, University of Colorado Boulder

/*
 * IMPORTANT: This file was auto-generated by "grunt generate-config". Please do not modify this directly. Instead
 * please modify energy-forms-and-changes/package.json to control dependencies.
 *
 * RequireJS configuration file for the energy-forms-and-changes sim.
 * Paths are relative to the location of this file.
 */

require.config( {

  deps: [ 'energy-forms-and-changes-main' ],

  paths: {

    // Third-party libs
    text: '../../sherpa/lib/text-2.0.12',

    // PhET plugins
    sound: '../../chipper/js/requirejs-plugins/sound',
    image: '../../chipper/js/requirejs-plugins/image',
    mipmap: '../../chipper/js/requirejs-plugins/mipmap',
    string: '../../chipper/js/requirejs-plugins/string',
    ifphetio: '../../chipper/js/requirejs-plugins/ifphetio',

    // PhET libs, uppercase names to identify them in require.js imports.
    // IMPORTANT: DO NOT modify. This file is auto-generated. See documentation at the top.
    AXON: '../../axon/js',
    BRAND: '../../brand/' + phet.chipper.brand + '/js',
    DOT: '../../dot/js',
    ENERGY_FORMS_AND_CHANGES: '.',
    JOIST: '../../joist/js',
    KITE: '../../kite/js',
    PHETCOMMON: '../../phetcommon/js',
    PHET_CORE: '../../phet-core/js',
    PHET_IO: '../../phet-io/js',
    REPOSITORY: '..',
    SCENERY: '../../scenery/js',
    SCENERY_PHET: '../../scenery-phet/js',
    SUN: '../../sun/js',
    TAMBO: '../../tambo/js',
    TANDEM: '../../tandem/js',
    TWIXT: '../../twixt/js'
  },

  // optional cache buster to make browser refresh load all included scripts, can be disabled with ?cacheBuster=false
  urlArgs: phet.chipper.getCacheBusterArgs()
} );
<|endoftext|>


<commit_before>"use strict";

var app = angular.module('js110', []);

app.controller('controller', ['$scope', function($scope) {
  var synth = { };

  $scope.playing = false;
  $scope.amplitude = 0.5;
  $scope.tempo = 100;
  $scope.loop = true;
  $scope.downloadFileName = "js-110";

  $scope.instruments = [{
                          waveform:           'sawtooth',
                          lfoWaveform:        'sine',
                          lfoFrequency:       5,
                          lfoAmplitude:       0,
                          filterCutoff:       1000,
                          filterResonance:    0,
                          filterLFOWaveform:  'sine',
                          filterLFOFrequency: 5,
                          filterLFOAmplitude: 0,
                          envelopeAttack:     0.0,
                          envelopeDecay:      0.0,
                          envelopeSustain:    1.0,
                          envelopeRelease:    0.0,
                       },];

  $scope.tracks = [{
                     muted: false,
                     notes: [{name: 'E3'},
                             {name: ''},
                             {name: 'G3'},
                             {name: ''},
                             {name: 'A4'},
                             {name: ''},
                             {name: 'G3'},
                             {name: ''},
                             {name: 'E3'},
                             {name: ''},
                             {name: 'E3'},
                             {name: ''},
                             {name: 'E3'},
                             {name: 'D3'},
                             {name: 'C3'},
                             {name: '-'},],
                   },
                   {
                     muted: false,
                     notes: [{name: 'C3'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: ''},
                             {name: ''},
                             {name: 'C3'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: 'A3'},
                             {name: '-'},],
                   },
                   {
                     muted: false,
                     notes: [{name: 'G2'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: ''},
                             {name: ''},
                             {name: 'G2'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: 'F3'},
                             {name: '-'},],
                   },];

  var Serializer = function() {
    var serializer = {};

    var serializeInstrument = function() {
      var filterCutoff = parseInt($scope.instruments[0].filterCutoff, 10);

      return {
        waveform:  $scope.instruments[0].waveform,
        lfo: {
          waveform:  $scope.instruments[0].lfoWaveform,
          frequency: parseFloat($scope.instruments[0].lfoFrequency),
          amplitude: parseInt($scope.instruments[0].lfoAmplitude, 10),
        },
        filter: {
          cutoff:    filterCutoff,
          resonance: parseInt($scope.instruments[0].filterResonance, 10),
          lfo: {
            waveform:  $scope.instruments[0].filterLFOWaveform,
            frequency: parseFloat($scope.instruments[0].filterLFOFrequency),
            amplitude: parseFloat($scope.instruments[0].filterLFOAmplitude) * filterCutoff,
          },
        },
        envelope: {
          attack:  parseFloat($scope.instruments[0].envelopeAttack),
          decay:   parseFloat($scope.instruments[0].envelopeDecay),
          sustain: parseFloat($scope.instruments[0].envelopeSustain),
          release: parseFloat($scope.instruments[0].envelopeRelease),
        },
      };
    };

    var serializeTrackNotesIntoSequence = function(track) {
      var rawNotes = [];

      track.notes.forEach(function(note, index) {
        rawNotes[index] = note.name;
      });

      return rawNotes.join(' ');
    };

    serializer.serialize = function() {
      var instrument = new JSSynth.Instrument(serializeInstrument());

      var tracks = [];
      $scope.tracks.forEach(function(track) {
        var sequence = JSSynth.SequenceParser.parse(serializeTrackNotesIntoSequence(track));
        tracks.push(new JSSynth.Track(instrument, sequence, track.muted));
      });

      return tracks;
    };

    return serializer;
  };

  var syncPatternTracks = function(pattern) {
    var tracks = new Serializer().serialize();
    pattern.replaceTracks(tracks);
  };

  $scope.init = function() {
    var stopCallback = function() {
      $scope.playing = false;
      $scope.$digest();
    };

    synth.pattern = new JSSynth.Pattern();
    syncPatternTracks(synth.pattern);
    synth.transport = new JSSynth.Transport(synth.pattern, stopCallback);

    if (synth.transport) {
      synth.transport.setTempo(parseInt($scope.tempo, 10));
    }
    else {
      alert("Your browser doesn't appear to support WebAudio, and so won't be able to use the JS-110. Try a recent version of Chrome, Safari, or Firefox.");
      return;
    }
  };
  $scope.init();

  $scope.updateInstrument = function() {
    syncPatternTracks(synth.pattern);
  };

  $scope.addTrack = function() {
    var newTrack = {
                     muted: false,
                     notes: [{name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''}],
                   };
    $scope.tracks.push(newTrack);

    syncPatternTracks(synth.pattern);
  };

  $scope.removeTrack = function(index) {
    $scope.tracks.splice(index, 1);
    syncPatternTracks(synth.pattern);
  };

  $scope.toggleTrackMute = function(index) {
    $scope.tracks[index].muted = !$scope.tracks[index].muted;
    syncPatternTracks(synth.pattern);
  };

  $scope.updateNotes = function() {
    syncPatternTracks(synth.pattern);
  };

  $scope.updateTempo = function() {
    synth.transport.setTempo(parseInt($scope.tempo, 10));
  };

  $scope.updateAmplitude = function() {
    synth.transport.setAmplitude(parseFloat($scope.amplitude));
  };

  $scope.toggle = function() {
    synth.transport.toggle();
    $scope.playing = !$scope.playing;
  };

  $scope.updateLoop = function() {
    synth.transport.loop = $scope.loop;
  };

  $scope.export = function() {
    var pattern = new JSSynth.Pattern();
    syncPatternTracks(pattern);

    var exportCompleteCallback = function(blob) {
      var url  = window.URL.createObjectURL(blob);

      var hiddenDownloadLink = document.getElementById("hidden-download-link");
      if (typeof hiddenDownloadLink.download != "undefined") {
        hiddenDownloadLink.download = $scope.downloadFileName + ".wav";
        hiddenDownloadLink.href = url;
        hiddenDownloadLink.click();
      }
      else {
        alert("Downloading to Wave file is not supported in your browser.");
      }

      window.URL.revokeObjectURL(blob);
    };

    var offlineTransport = new JSSynth.OfflineTransport(pattern, parseInt($scope.tempo, 10), parseFloat($scope.amplitude), exportCompleteCallback);
    offlineTransport.tick();
  };
}]);

app.directive('noteInput', function () {
  return {
    require: 'ngModel',
    link: function (scope, element, attrs, ctrl) {
       if (!ctrl) return;

       var formatNoteValue = function(rawValue) {
         var formattedValue = rawValue;

         // Make first character uppercase (but not subsequent characters, to avoid
         // making a 'b' uppercase, which will mess with ♭ replacement).
         var firstCharacter = formattedValue.substr(0, 1);
         formattedValue = firstCharacter.toUpperCase() + formattedValue.substr(1);

         formattedValue = formattedValue.replace("##", "𝄪");
         formattedValue = formattedValue.replace("#", "♯");
         formattedValue = formattedValue.replace("bb", "𝄫");
         formattedValue = formattedValue.replace("b", "♭");
         formattedValue = formattedValue.replace("-", "—");

         return formattedValue;
       };

       ctrl.$formatters.push(function (a) {
         return formatNoteValue(ctrl.$modelValue);
       });

       ctrl.$parsers.unshift(function (viewValue) {
         var parsedValue = viewValue;

         // Make first character uppercase (but not subsequent characters, to avoid
         // making a 'b' uppercase, which will mess with ♭ replacement).
         var firstCharacter = viewValue.substr(0, 1);
         parsedValue = firstCharacter.toUpperCase() + viewValue.substr(1);
         parsedValue = parsedValue.replace("♯", "#");
         parsedValue = parsedValue.replace("𝄪", "##");
         parsedValue = parsedValue.replace("♭", "b");
         parsedValue = parsedValue.replace("𝄫", "bb");

         if (/^$|^-$|(^[A-G](b|bb|#|##){0,1}[0-7]$)/.test(parsedValue)) {
           ctrl.$setValidity('noteInput', true);
           return parsedValue;
         }
         else {
           ctrl.$setValidity('noteInput', false);
           return '';
         }
       });

       element.bind('blur', function(e) {
         element.val(formatNoteValue(element.val()));
       });

       element.bind('keydown', function(e) {
         var changeCurrentlySelectedNote = function(element, config) {
           var trackIndex = parseInt(element[0].id.split("-")[1], 10);
           var noteIndex = parseInt(element[0].id.split("-")[3], 10);
           var nextNoteId = 'track-' + (trackIndex + config.trackIndexDelta) + '-note-' + (noteIndex + config.noteIndexDelta);

           document.getElementById(nextNoteId).focus();
         };

         var currentValue = element.val();

         if (e.keyCode === 32) {  // Space bar
           element.val('');
         }
         else if (e.keyCode >= 48 && e.keyCode <= 57) {  // Numbers 0 through 9
           if (/^.*\d$/.test(currentValue)) {
             element.val(currentValue.slice(0, currentValue.length - 1));
           }
         }
         else if (e.keyCode === 189) {  // Dash
           element.val('');
         }
         else if (e.keyCode === 37) {  // Left arrow key
           if (element[0].selectionStart === 0 && !(element.hasClass('firstNote'))) {
             changeCurrentlySelectedNote(element, { trackIndexDelta: 0, noteIndexDelta: -1 });
           }
         }
         else if (e.keyCode === 39) {  // Right arrow key
           if (element[0].selectionEnd === currentValue.length && !(element.hasClass('lastNote'))) {
             changeCurrentlySelectedNote(element, { trackIndexDelta: 0, noteIndexDelta: 1 });
           }
         }
         else if (e.keyCode === 38) {  // Up arrow key
           if (!(element.hasClass('firstTrack'))) {
             changeCurrentlySelectedNote(element, { trackIndexDelta: -1, noteIndexDelta: 0 });
           }
         }
         else if (e.keyCode === 40) {  // Down arrow key
           if (!(element.hasClass('lastTrack'))) {
             changeCurrentlySelectedNote(element, { trackIndexDelta: 1, noteIndexDelta: 0 });
           }
         }
       });

       element.bind('keyup', function(e) {
         if (e.keyCode === 32) {  // Space bar
           element.val('');
         }
       });
    }
  };
});
<commit_msg>Experimental support for auto changing note value<commit_after>"use strict";

var app = angular.module('js110', []);

app.controller('controller', ['$scope', function($scope) {
  var synth = { };

  $scope.playing = false;
  $scope.amplitude = 0.5;
  $scope.tempo = 100;
  $scope.loop = true;
  $scope.downloadFileName = "js-110";

  $scope.instruments = [{
                          waveform:           'sawtooth',
                          lfoWaveform:        'sine',
                          lfoFrequency:       5,
                          lfoAmplitude:       0,
                          filterCutoff:       1000,
                          filterResonance:    0,
                          filterLFOWaveform:  'sine',
                          filterLFOFrequency: 5,
                          filterLFOAmplitude: 0,
                          envelopeAttack:     0.0,
                          envelopeDecay:      0.0,
                          envelopeSustain:    1.0,
                          envelopeRelease:    0.0,
                       },];

  $scope.tracks = [{
                     muted: false,
                     notes: [{name: 'E3'},
                             {name: ''},
                             {name: 'G3'},
                             {name: ''},
                             {name: 'A4'},
                             {name: ''},
                             {name: 'G3'},
                             {name: ''},
                             {name: 'E3'},
                             {name: ''},
                             {name: 'E3'},
                             {name: ''},
                             {name: 'E3'},
                             {name: 'D3'},
                             {name: 'C3'},
                             {name: '-'},],
                   },
                   {
                     muted: false,
                     notes: [{name: 'C3'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: ''},
                             {name: ''},
                             {name: 'C3'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: 'A3'},
                             {name: '-'},],
                   },
                   {
                     muted: false,
                     notes: [{name: 'G2'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: ''},
                             {name: ''},
                             {name: 'G2'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: '-'},
                             {name: 'F3'},
                             {name: '-'},],
                   },];

  var Serializer = function() {
    var serializer = {};

    var serializeInstrument = function() {
      var filterCutoff = parseInt($scope.instruments[0].filterCutoff, 10);

      return {
        waveform:  $scope.instruments[0].waveform,
        lfo: {
          waveform:  $scope.instruments[0].lfoWaveform,
          frequency: parseFloat($scope.instruments[0].lfoFrequency),
          amplitude: parseInt($scope.instruments[0].lfoAmplitude, 10),
        },
        filter: {
          cutoff:    filterCutoff,
          resonance: parseInt($scope.instruments[0].filterResonance, 10),
          lfo: {
            waveform:  $scope.instruments[0].filterLFOWaveform,
            frequency: parseFloat($scope.instruments[0].filterLFOFrequency),
            amplitude: parseFloat($scope.instruments[0].filterLFOAmplitude) * filterCutoff,
          },
        },
        envelope: {
          attack:  parseFloat($scope.instruments[0].envelopeAttack),
          decay:   parseFloat($scope.instruments[0].envelopeDecay),
          sustain: parseFloat($scope.instruments[0].envelopeSustain),
          release: parseFloat($scope.instruments[0].envelopeRelease),
        },
      };
    };

    var serializeTrackNotesIntoSequence = function(track) {
      var rawNotes = [];

      track.notes.forEach(function(note, index) {
        rawNotes[index] = note.name;
      });

      return rawNotes.join(' ');
    };

    serializer.serialize = function() {
      var instrument = new JSSynth.Instrument(serializeInstrument());

      var tracks = [];
      $scope.tracks.forEach(function(track) {
        var sequence = JSSynth.SequenceParser.parse(serializeTrackNotesIntoSequence(track));
        tracks.push(new JSSynth.Track(instrument, sequence, track.muted));
      });

      return tracks;
    };

    return serializer;
  };

  var syncPatternTracks = function(pattern) {
    var tracks = new Serializer().serialize();
    pattern.replaceTracks(tracks);
  };

  $scope.init = function() {
    var stopCallback = function() {
      $scope.playing = false;
      $scope.$digest();
    };

    synth.pattern = new JSSynth.Pattern();
    syncPatternTracks(synth.pattern);
    synth.transport = new JSSynth.Transport(synth.pattern, stopCallback);

    if (synth.transport) {
      synth.transport.setTempo(parseInt($scope.tempo, 10));
    }
    else {
      alert("Your browser doesn't appear to support WebAudio, and so won't be able to use the JS-110. Try a recent version of Chrome, Safari, or Firefox.");
      return;
    }
  };
  $scope.init();

  $scope.updateInstrument = function() {
    syncPatternTracks(synth.pattern);
  };

  $scope.addTrack = function() {
    var newTrack = {
                     muted: false,
                     notes: [{name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''},
                             {name: ''}],
                   };
    $scope.tracks.push(newTrack);

    syncPatternTracks(synth.pattern);
  };

  $scope.removeTrack = function(index) {
    $scope.tracks.splice(index, 1);
    syncPatternTracks(synth.pattern);
  };

  $scope.toggleTrackMute = function(index) {
    $scope.tracks[index].muted = !$scope.tracks[index].muted;
    syncPatternTracks(synth.pattern);
  };

  $scope.updateNotes = function() {
    syncPatternTracks(synth.pattern);
  };

  $scope.updateTempo = function() {
    synth.transport.setTempo(parseInt($scope.tempo, 10));
  };

  $scope.updateAmplitude = function() {
    synth.transport.setAmplitude(parseFloat($scope.amplitude));
  };

  $scope.toggle = function() {
    synth.transport.toggle();
    $scope.playing = !$scope.playing;
  };

  $scope.updateLoop = function() {
    synth.transport.loop = $scope.loop;
  };

  $scope.export = function() {
    var pattern = new JSSynth.Pattern();
    syncPatternTracks(pattern);

    var exportCompleteCallback = function(blob) {
      var url  = window.URL.createObjectURL(blob);

      var hiddenDownloadLink = document.getElementById("hidden-download-link");
      if (typeof hiddenDownloadLink.download != "undefined") {
        hiddenDownloadLink.download = $scope.downloadFileName + ".wav";
        hiddenDownloadLink.href = url;
        hiddenDownloadLink.click();
      }
      else {
        alert("Downloading to Wave file is not supported in your browser.");
      }

      window.URL.revokeObjectURL(blob);
    };

    var offlineTransport = new JSSynth.OfflineTransport(pattern, parseInt($scope.tempo, 10), parseFloat($scope.amplitude), exportCompleteCallback);
    offlineTransport.tick();
  };
}]);

app.directive('noteInput', function () {
  return {
    require: 'ngModel',
    link: function (scope, element, attrs, ctrl) {
       if (!ctrl) return;

       var formatNoteValue = function(rawValue) {
         var formattedValue = rawValue;

         // Make first character uppercase (but not subsequent characters, to avoid
         // making a 'b' uppercase, which will mess with ♭ replacement).
         var firstCharacter = formattedValue.substr(0, 1);
         formattedValue = firstCharacter.toUpperCase() + formattedValue.substr(1);

         formattedValue = formattedValue.replace("##", "𝄪");
         formattedValue = formattedValue.replace("#", "♯");
         formattedValue = formattedValue.replace("bb", "𝄫");
         formattedValue = formattedValue.replace("b", "♭");
         formattedValue = formattedValue.replace("-", "—");

         return formattedValue;
       };

       ctrl.$formatters.push(function (a) {
         return formatNoteValue(ctrl.$modelValue);
       });

       ctrl.$parsers.unshift(function (viewValue) {
         var parsedValue = viewValue;

         // Make first character uppercase (but not subsequent characters, to avoid
         // making a 'b' uppercase, which will mess with ♭ replacement).
         var firstCharacter = viewValue.substr(0, 1);
         parsedValue = firstCharacter.toUpperCase() + viewValue.substr(1);
         parsedValue = parsedValue.replace("♯", "#");
         parsedValue = parsedValue.replace("𝄪", "##");
         parsedValue = parsedValue.replace("♭", "b");
         parsedValue = parsedValue.replace("𝄫", "bb");

         if (/^$|^-$|(^[A-G](b|bb|#|##){0,1}[0-7]$)/.test(parsedValue)) {
           ctrl.$setValidity('noteInput', true);
           return parsedValue;
         }
         else {
           ctrl.$setValidity('noteInput', false);
           return '';
         }
       });

       element.bind('blur', function(e) {
         element.val(formatNoteValue(element.val()));
       });

       element.bind('keydown', function(e) {
         var changeCurrentlySelectedNote = function(element, config) {
           var trackIndex = parseInt(element[0].id.split("-")[1], 10);
           var noteIndex = parseInt(element[0].id.split("-")[3], 10);
           var nextNoteId = 'track-' + (trackIndex + config.trackIndexDelta) + '-note-' + (noteIndex + config.noteIndexDelta);

           document.getElementById(nextNoteId).focus();
         };

         var currentValue = element.val();

         if (e.keyCode === 32) {  // Space bar
           element.val('');
         }
         else if (e.keyCode >= 65 && e.keyCode <= 71) {  // Letters A through F
           if (/^[abcdefgABCDEFG].*$/.test(currentValue)) {
             element.val(currentValue.slice(1, currentValue.length));
             element[0].setSelectionRange(0, 0);
           }
         }
         else if (e.keyCode >= 48 && e.keyCode <= 57) {  // Numbers 0 through 9
           if (/^.*\d$/.test(currentValue)) {
             element.val(currentValue.slice(0, currentValue.length - 1));
           }
         }
         else if (e.keyCode === 189) {  // Dash
           element.val('');
         }
         else if (e.keyCode === 37) {  // Left arrow key
           if (element[0].selectionStart === 0 && !(element.hasClass('firstNote'))) {
             changeCurrentlySelectedNote(element, { trackIndexDelta: 0, noteIndexDelta: -1 });
           }
         }
         else if (e.keyCode === 39) {  // Right arrow key
           if (element[0].selectionEnd === currentValue.length && !(element.hasClass('lastNote'))) {
             changeCurrentlySelectedNote(element, { trackIndexDelta: 0, noteIndexDelta: 1 });
           }
         }
         else if (e.keyCode === 38) {  // Up arrow key
           if (!(element.hasClass('firstTrack'))) {
             changeCurrentlySelectedNote(element, { trackIndexDelta: -1, noteIndexDelta: 0 });
           }
         }
         else if (e.keyCode === 40) {  // Down arrow key
           if (!(element.hasClass('lastTrack'))) {
             changeCurrentlySelectedNote(element, { trackIndexDelta: 1, noteIndexDelta: 0 });
           }
         }
       });

       element.bind('keyup', function(e) {
         if (e.keyCode === 32) {  // Space bar
           element.val('');
         }
       });
    }
  };
});
<|endoftext|>