Commits

Michael Granger committed 760c5f6

Checkpoint commit

Comments (0)

Files changed (703)

 run/
 logs/
 \.esproj/
+ChangeLog
+app/lib/
+--- 
+exclude: !ruby/regexp /tmp$|Gemfile(\.lock)?|TAGS|tmtags|\.(hg|bowerrc|keep|hoerc|env|DS_Store|rvm(rc|\.gems)|irbrc|pryrc|tm_.*|rspec.*|rbx|travis\.yml)|(logs|coverage|benchmarks|experiments|manual|features|tmp\w*|wiki|\w+-\d+\.\d+\.\d+|app/js/vendor)\/|\.(nzb|bundle|sqlite|ru|graffle|espressostorage|esproj|ldif|log|sublime-\w+|pid|pem|gemspec)|bower\.json|config\.yml$/
+#!/usr/bin/ruby -*- ruby -*-
+
+require 'loggability'
+require 'pathname'
+
+CONFIGFILE = Pathname( 'etc/config.yml' )
+
+$LOAD_PATH.unshift( 'lib' )
+$LOAD_PATH.unshift( '../Strelka/lib' )
+$LOAD_PATH.unshift( '../Mongrel2/lib' )
+$LOAD_PATH.unshift( '../Thingfish/lib' )
+
+begin
+	require 'angelfish'
+	require 'angelfish/handler'
+
+	if CONFIGFILE.exist?
+		$stderr.puts "Installing the config in #{CONFIGFILE}..."
+		Strelka.load_config( CONFIGFILE )
+	end
+
+	Loggability.level = :debug
+	Loggability.format_with( :color )
+
+rescue Exception => e
+	$stderr.puts "Ack! Angelfish libraries failed to load: #{e.message}\n\t" +
+		e.backtrace.join( "\n\t" )
+end
+
+
 sequel -v4.0.0
 pluggability -v0.2.0
 simplecov -v0.7.1
-strelka -v0.6.0
+strelka -v0.7.0
+rmagick -v2.13.2
+ruby-mp3info -v0.8.2
+fakefs -v0.5.0
+== v0.1.0 [2014-01-18] Michael Granger <ged@FaerieMUD.org>
+
+First release.
+
+
+
+ChangeLog
+History.rdoc
+Manifest.txt
+Procfile
+README.rdoc
+Rakefile
+app/404.html
+app/css/app.css
+app/css/bootstrap-theme.css
+app/css/bootstrap-theme.min.css
+app/css/bootstrap.css
+app/css/bootstrap.min.css
+app/css/filetypes.css
+app/css/glyphicons.css
+app/css/halflings.css
+app/css/social.css
+app/favicon.ico
+app/fonts/Lato-Bol.otf
+app/fonts/Lato-Bol.ttf
+app/fonts/Lato-Bol.woff
+app/fonts/Lato-Reg.otf
+app/fonts/Lato-Reg.ttf
+app/fonts/Lato-Reg.woff
+app/fonts/glyphicons-filetypes-regular.eot
+app/fonts/glyphicons-filetypes-regular.otf
+app/fonts/glyphicons-filetypes-regular.svg
+app/fonts/glyphicons-filetypes-regular.ttf
+app/fonts/glyphicons-filetypes-regular.woff
+app/fonts/glyphicons-halflings-regular.eot
+app/fonts/glyphicons-halflings-regular.otf
+app/fonts/glyphicons-halflings-regular.svg
+app/fonts/glyphicons-halflings-regular.ttf
+app/fonts/glyphicons-halflings-regular.woff
+app/fonts/glyphicons-regular.eot
+app/fonts/glyphicons-regular.otf
+app/fonts/glyphicons-regular.svg
+app/fonts/glyphicons-regular.ttf
+app/fonts/glyphicons-regular.woff
+app/fonts/glyphicons-social-regular.eot
+app/fonts/glyphicons-social-regular.otf
+app/fonts/glyphicons-social-regular.svg
+app/fonts/glyphicons-social-regular.ttf
+app/fonts/glyphicons-social-regular.woff
+app/images/glyphicons-halflings-white.png
+app/images/glyphicons-halflings.png
+app/images/icons/_blank.png
+app/images/icons/_page.png
+app/images/icons/aac.png
+app/images/icons/ai.png
+app/images/icons/aiff.png
+app/images/icons/avi.png
+app/images/icons/bmp.png
+app/images/icons/c.png
+app/images/icons/cpp.png
+app/images/icons/css.png
+app/images/icons/dat.png
+app/images/icons/dmg.png
+app/images/icons/doc.png
+app/images/icons/dotx.png
+app/images/icons/dwg.png
+app/images/icons/dxf.png
+app/images/icons/eps.png
+app/images/icons/exe.png
+app/images/icons/flv.png
+app/images/icons/gif.png
+app/images/icons/h.png
+app/images/icons/hpp.png
+app/images/icons/html.png
+app/images/icons/ics.png
+app/images/icons/iso.png
+app/images/icons/java.png
+app/images/icons/jpg.png
+app/images/icons/key.png
+app/images/icons/mid.png
+app/images/icons/mp3.png
+app/images/icons/mp4.png
+app/images/icons/mpg.png
+app/images/icons/odf.png
+app/images/icons/ods.png
+app/images/icons/odt.png
+app/images/icons/otp.png
+app/images/icons/ots.png
+app/images/icons/ott.png
+app/images/icons/pdf.png
+app/images/icons/php.png
+app/images/icons/png.png
+app/images/icons/ppt.png
+app/images/icons/psd.png
+app/images/icons/py.png
+app/images/icons/qt.png
+app/images/icons/rar.png
+app/images/icons/rb.png
+app/images/icons/rtf.png
+app/images/icons/sql.png
+app/images/icons/tga.png
+app/images/icons/tgz.png
+app/images/icons/tiff.png
+app/images/icons/txt.png
+app/images/icons/wav.png
+app/images/icons/xls.png
+app/images/icons/xlsx.png
+app/images/icons/xml.png
+app/images/icons/yml.png
+app/images/icons/zip.png
+app/images/no-thumb.svg
+app/images/noise.png
+app/index.html
+app/js/angelfish/filters.js
+app/js/angelfish/gallery.js
+app/js/angelfish/services.js
+app/js/angelfish/upload.js
+app/js/app.js
+app/js/boot.js
+app/js/navmenu.js
+app/robots.txt
+app/template/accordion/accordion-group.html
+app/template/accordion/accordion.html
+app/template/alert/alert.html
+app/template/carousel/carousel.html
+app/template/carousel/slide.html
+app/template/datepicker/datepicker.html
+app/template/datepicker/popup.html
+app/template/dialog/message.html
+app/template/modal/backdrop.html
+app/template/modal/window.html
+app/template/pagination/pager.html
+app/template/pagination/pagination.html
+app/template/popover/popover.html
+app/template/progressbar/bar.html
+app/template/progressbar/progress.html
+app/template/rating/rating.html
+app/template/tabs/tab.html
+app/template/tabs/tabset-titles.html
+app/template/tabs/tabset.html
+app/template/timepicker/timepicker.html
+app/template/tooltip/tooltip-html-unsafe-popup.html
+app/template/tooltip/tooltip-popup.html
+app/template/typeahead/typeahead-match.html
+app/template/typeahead/typeahead-popup.html
+app/views/gallery.html
+app/views/gallery/tile.html
+app/views/main.html
+app/views/navmenu.html
+app/views/resumable.html
+app/views/upload-button.html
+app/views/upload-form.html
+bin/angelfish
+bin/eventserver
+etc/mongrel2.rb
+lib/angelfish.rb
+lib/angelfish/eventserver.rb
+lib/angelfish/handler.rb
+lib/angelfish/processor/image.rb
+spec/angelfish/handler_spec.rb
+spec/angelfish/processor/image_spec.rb
+spec/angelfish_spec.rb
+spec/constants.rb
+spec/helpers.rb
+= Angelfish
+
+* http://bitbucket.org/ged/angelfish
+
+== Description
+
+Angelfish is a web interface for the Thingfish digital asset management
+server.
+
+
+== Authors
+
+* Michael Granger <ged@FaerieMUD.org>
+
+
+== Contributing
+
+You can check out the current development source
+{with Mercurial}[http://bitbucket.org/ged/angelfish], or
+if you prefer Git, via the project's
+{Github mirror}[https://github.com/ged/angelfish].
+
+After checking out the source, run:
+
+	$ rake newb
+
+This task will install any missing dependencies, run the tests/specs, and 
+generate the API documentation.
+
+You can submit bug reports, suggestions, and read more about future plans at
+{the project page}[http://bitbucket.org/ged/angelfish].
+
+
+== License
+
+Copyright (c) 2013-2014, Michael Granger
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+  this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of the author/s, nor the names of the project's
+  contributors may be used to endorse or promote products derived from this
+  software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+#!/usr/bin/env rake
+
+require 'rake/clean'
+
+begin
+	require 'hoe'
+rescue LoadError
+	abort "This Rakefile requires 'hoe' (gem install hoe)"
+end
+
+Hoe.plugin :mercurial
+Hoe.plugin :signing
+Hoe.plugin :manualgen
+Hoe.plugin :deveiate
+Hoe.plugin :bundler
+
+Hoe.plugins.delete :rubyforge
+
+hoespec = Hoe.spec 'angelfish' do
+	self.readme_file = 'README.rdoc'
+	self.history_file = 'History.rdoc'
+	self.extra_rdoc_files = FileList[ '*.rdoc' ]
+	self.license "BSD"
+
+	self.developer 'Michael Granger', 'ged@FaerieMUD.org'
+
+	self.dependency 'thingfish',    '~> 0.0'
+	self.dependency 'rmagick',      '~> 2.13'
+
+	self.dependency 'hoe-deveiate', '~> 0.3', :developer
+	self.dependency 'hoe-bundler',  '~> 1.2', :developer
+	self.dependency 'fakefs',       '~> 0.5', :developer
+
+	self.require_ruby_version( '>=2.0.0' )
+	self.hg_sign_tags = true if self.respond_to?( :hg_sign_tags= )
+	self.check_history_on_release = true if self.respond_to?( :check_history_on_release= )
+
+	self.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"
+end
+
+ENV['VERSION'] ||= hoespec.spec.version.to_s
+
+# Ensure the specs pass before checking in
+task 'hg:precheckin' => [ :check_history, :check_manifest, :spec ]
+
+
+desc "Build a coverage report"
+task :coverage do
+	ENV["COVERAGE"] = 'yes'
+	Rake::Task[:spec].invoke
+end
+
+if Rake::Task.task_defined?( '.gemtest' )
+	Rake::Task['.gemtest'].clear
+	task '.gemtest' do
+		$stderr.puts "Not including a .gemtest until I'm confident the test suite is idempotent."
+	end
+end
+
+
+# Add admin app testing directories to the clobber list
+CLOBBER.include( 'app/js/vendor', 'etc/mongrel2.sqlite', 'logs', 'run', 'var' )
+
+
+{
+  "directory": "lib",
+  "json": "bower.json"
+}
+{
+  "name": "angelfish",
+  "version": "0.0.1",
+  "authors": [
+    "Michael Granger <ged@FaerieMUD.org>"
+  ],
+  "description": "A web front end for Thingfish",
+  "main": "index.html",
+  "keywords": [
+    "dam",
+    "thingfish",
+    "digital",
+    "asset",
+    "manager"
+  ],
+  "license": "BSD",
+  "homepage": "http://bitbucket.org/ged/angelfish",
+  "private": true,
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ],
+  "dependencies": {
+    "angular": "~1.2.11",
+    "angular-animate": "~1.2.11",
+    "angular-bootstrap": "~0.10.0",
+    "angular-cookies": "~1.2.11",
+    "angular-resource": "~1.2.11",
+    "angular-route": "~1.2.11",
+    "angular-sanitize": "~1.2.11",
+    "holderjs": "~2.3.0",
+    "jquery": "~2.0",
+    "moment": "~2.5.1",
+    "requirejs": "~2.1.10",
+    "requirejs-domready": "~2.0.1",
+    "resumablejs": "*",
+    "imagesloaded-packaged": "~3.1.4",
+    "isotope": "#v2"
+  },
+  "resolutions": {
+    "angular": "~1.2.11"
+  },
+  "devDependencies": {
+    "angular-mocks": "~1.2.11",
+    "angular-scenario": "~1.2.11"
+  }
+}
 }
 
 #asset-grid {
-	overflow: scroll;
 	padding-bottom: 150px;
 }
 
 .masonry-brick .icon.filetype:before {
 	font-size: 35px;
 }
+
+
+/**** Isotope Filtering ****/
+
+.isotope-item {
+  z-index: 2;
+}
+
+.isotope-hidden.isotope-item {
+  pointer-events: none;
+  z-index: 1;
+}
+
+/**** Isotope CSS3 transitions ****/
+
+.isotope,
+.isotope .isotope-item {
+  -webkit-transition-duration: 0.8s;
+     -moz-transition-duration: 0.8s;
+      -ms-transition-duration: 0.8s;
+       -o-transition-duration: 0.8s;
+          transition-duration: 0.8s;
+}
+
+.isotope {
+  -webkit-transition-property: height, width;
+     -moz-transition-property: height, width;
+      -ms-transition-property: height, width;
+       -o-transition-property: height, width;
+          transition-property: height, width;
+}
+
+.isotope .isotope-item {
+  -webkit-transition-property: -webkit-transform, opacity;
+     -moz-transition-property:    -moz-transform, opacity;
+      -ms-transition-property:     -ms-transform, opacity;
+       -o-transition-property:      -o-transform, opacity;
+          transition-property:         transform, opacity;
+}
+
+/**** disabling Isotope CSS3 transitions ****/
+
+.isotope.no-transition,
+.isotope.no-transition .isotope-item,
+.isotope .isotope-item.no-transition {
+  -webkit-transition-duration: 0s;
+     -moz-transition-duration: 0s;
+      -ms-transition-duration: 0s;
+       -o-transition-duration: 0s;
+          transition-duration: 0s;
+}
+
 		[ng-cloak] { display: none; }
 	</style>
 
-  <script src="/js/boot.js"></script>
+	<script src="/lib/requirejs/require.js" data-main="/js/boot.js"></script>
 </head>
 <body>
     <!--[if lt IE 9]>

app/js/angelfish.js

+/**
+ * Angelfish Interface
+ * $Id$
+ */
+'use strict';
+
+define([
+	'require',
+	'jquery',
+	'angular',
+	'angular-animate',
+	'angular-route',
+	'angular-sanitize',
+	'angelfish/filters',
+	'angelfish/services',
+	'angelfish/gallery',
+	'angelfish/upload',
+	'navmenu',
+], function (require, jquery, angular) {
+	'use strict';
+
+	require(['domReady!'], function (document) {
+		console.debug( "Creating Angelfish app." );
+
+		angular.module('angelfish', [
+			'angelfish.filters',
+			'angelfish.services',
+			'angelfish.gallery',
+			'angelfish.upload',
+			'navmenu',
+			'ngAnimate',
+			'ngRoute',
+			'ngSanitize'
+		]).
+		config(['$routeProvider', function($routeProvider) {
+			console.debug( "Setting up route provider." );
+			$routeProvider.
+				when('/', {
+					templateUrl: '/views/gallery.html',
+					controller: 'GalleryController'
+				}).
+				otherwise({ redirectTo: '/' });
+		}]);
+
+		console.debug( "Bootstrapping Angelfish app." );
+		angular.bootstrap(document, ['angelfish']);
+	});
+});

app/js/angelfish/filters.js

-'use strict';
+/**
+ * Angelfish Angular filters
+ */
 
-/*
- * Angelfish Filters
- * $Id$
- *
- * Author/s:
- * - Michael Granger <ged@FaerieMUD.org>
- */
-angular.module('angelfish.filters', []).
-	filter('bytesize', function() {
-		return function(bytes) {
-			var Kilobyte = 1024.0;
-			var Megabyte = 1024 * Kilobyte;
-			var Gigabyte = 1024 * Megabyte;
-			var Terabyte = 1024 * Gigabyte;
-			var Petabyte = 1024 * Terabyte;
+define([
+    'angular',
+	'moment'
+], function (angular, moment) { 'use strict';
 
-			if ( typeof bytes === 'undefined' ) {
-				console.debug( "Bytes is undefined. Returning n/a." );
-				return '(n/a)';
+	angular.module('angelfish.filters', []).
+		filter('list', function() {
+			return function(input) {
+				if ( angular.isArray(input) ) {
+					return input.map( function(obj) {
+						return obj.toString();
+					} ).join(', ');
+				} else {
+					return input;
+				}
+			};
+		}).
+		filter('join', function() {
+			return function(items, joinchar) {
+				if ( !joinchar ) { joinchar = ' ' }
+				if ( angular.isArray(items) ) { return items.join(joinchar) }
+				return items;
 			}
+		}).
+		filter('bytesize', function() {
+			return function(bytes) {
+				var Kilobyte = 1024.0;
+				var Megabyte = 1024 * Kilobyte;
+				var Gigabyte = 1024 * Megabyte;
+				var Terabyte = 1024 * Gigabyte;
+				var Petabyte = 1024 * Terabyte;
 
-			if( bytes >= Petabyte ) {
-				return Math.round( bytes / Petabyte ).toString() + " PB";
-			} else if ( bytes >= Terabyte ) {
-				return Math.round( bytes / Terabyte ).toString() + " TB";
-			} else if ( bytes >= Gigabyte ) {
-				return Math.round( bytes / Gigabyte ).toString() + " GB";
-			} else if ( bytes >= Megabyte ) {
-				return Math.round( bytes / Megabyte ).toString() + " MB";
-			} else if ( bytes >= Kilobyte ) {
-				return Math.round( bytes / Kilobyte ).toString() + " KB";
-			} else {
-				return bytes.toString() + " B";
-			}
-		};
-	}).
-	filter('nullfilter', function() {
-		return function(val) {
-			return val || '—';
-		};
-	}).
-	filter('reldate', function() {
-		return function(date) {
-			if ( !date ) {
-				return '';
-			} else if ( typeof date != 'Date' ) {
-				// Munge the date into one Firefox doesn't choke on. Ugh.
-				date = date.replace(/(\d{4})-(\d{2})-(\d{2})/, "$1/$2/$3");
-				date = Date.fromString( date );
-			}
+				if ( typeof bytes === 'undefined' ) {
+					console.debug( "Bytes is undefined. Returning n/a." );
+					return '(n/a)';
+				}
 
-			return date.toRelativeTime();
-		};
-	}).
-	filter('groupsOf', function() {
-		return function(input, count) {
-			if ( typeof input === 'undefined' ) return [];
+				if( bytes >= Petabyte ) {
+					return Math.round( bytes / Petabyte ).toString() + " PB";
+				} else if ( bytes >= Terabyte ) {
+					return Math.round( bytes / Terabyte ).toString() + " TB";
+				} else if ( bytes >= Gigabyte ) {
+					return Math.round( bytes / Gigabyte ).toString() + " GB";
+				} else if ( bytes >= Megabyte ) {
+					return Math.round( bytes / Megabyte ).toString() + " MB";
+				} else if ( bytes >= Kilobyte ) {
+					return Math.round( bytes / Kilobyte ).toString() + " KB";
+				} else {
+					return bytes.toString() + " B";
+				}
+			};
+		}).
+		filter('nullfilter', function() {
+			return function(val) {
+				return val || '—';
+			};
+		}).
+		filter('reldate', function() {
+			return function(date) {
+				return moment( date ).fromNow();
+			};
+		});
 
-			var items = input.slice( 0 ); // copy to avoid digesting on every row
-			var groups = [];
-			var group = items.splice( 0, count );
+});
 
-			while ( group.length > 0 ) {
-				console.debug( "Pushing row %d in groupsOf %d", groups.length, count );
-				groups.push( group );
-				group = items.splice( 0, count );
-			}
-
-			console.debug( "Returning %d groups.", groups.length );
-			return groups;
-		};
-	});
-
-
-/*
-    JavaScript Relative Time Helpers
-
-	Copyright (c) 2009 James F. Herdman
-
-	Permission is hereby granted, free of charge, to any person obtaining a copy
-	of this software and associated documentation files (the "Software"), to deal
-	in the Software without restriction, including without limitation the rights
-	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-	copies of the Software, and to permit persons to whom the Software is
-	furnished to do so, subject to the following conditions:
-
-	The above copyright notice and this permission notice shall be included in
-	all copies or substantial portions of the Software.
-
-	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-	THE SOFTWARE.
-*/
-
-/**
- * Returns a description of this date in relative terms.
-
- * Examples, where new Date().toString() == "Mon Nov 23 2009 17:36:51 GMT-0500 (EST)":
- *
- * new Date().toRelativeTime()
- * --> 'Just now'
- *
- * new Date("Nov 21, 2009").toRelativeTime()
- * --> '2 days ago'
- *
- * new Date("Nov 25, 2009").toRelativeTime()
- * --> '2 days from now'
- *
- * // One second ago
- * new Date("Nov 23 2009 17:36:50 GMT-0500 (EST)").toRelativeTime()
- * --> '1 second ago'
- *
- * toRelativeTime() takes an optional argument - a configuration object.
- * It can have the following properties:
- * - now - Date object that defines "now" for the purpose of conversion.
- *         By default, current date & time is used (i.e. new Date())
- * - nowThreshold - Threshold in milliseconds which is considered "Just now"
- *                  for times in the past or "Right now" for now or the immediate future
- * - smartDays - If enabled, dates within a week of now will use Today/Yesterday/Tomorrow
- *               or weekdays along with time, e.g. "Thursday at 15:10:34"
- *               rather than "4 days ago" or "Tomorrow at 20:12:01"
- *               instead of "1 day from now"
- *
- * If a single number is given as argument, it is interpreted as nowThreshold:
- *
- * // One second ago, now setting a now_threshold to 5 seconds
- * new Date("Nov 23 2009 17:36:50 GMT-0500 (EST)").toRelativeTime(5000)
- * --> 'Just now'
- *
- * // One second in the future, now setting a now_threshold to 5 seconds
- * new Date("Nov 23 2009 17:36:52 GMT-0500 (EST)").toRelativeTime(5000)
- * --> 'Right now'
- *
- */
-Date.prototype.toRelativeTime = (function() {
-
-  var _ = function(options) {
-    var opts = processOptions(options);
-
-    var now = opts.now || new Date();
-    var delta = now - this;
-    var future = (delta <= 0);
-    delta = Math.abs(delta);
-
-    // special cases controlled by options
-    if (delta <= opts.nowThreshold) {
-      return future ? 'Right now' : 'Just now';
-    }
-    if (opts.smartDays && delta <= 6 * MS_IN_DAY) {
-      return toSmartDays(this, now);
-    }
-
-    var units = null;
-    for (var key in CONVERSIONS) {
-      if (delta < CONVERSIONS[key])
-        break;
-      units = key; // keeps track of the selected key over the iteration
-      delta = delta / CONVERSIONS[key];
-    }
-
-    // pluralize a unit when the difference is greater than 1.
-    delta = Math.floor(delta);
-    if (delta !== 1) { units += "s"; }
-    return [delta, units, future ? "from now" : "ago"].join(" ");
-  };
-
-  var processOptions = function(arg) {
-    if (!arg) arg = 0;
-    if (typeof arg === 'string') {
-      arg = parseInt(arg, 10);
-    }
-    if (typeof arg === 'number') {
-      if (isNaN(arg)) arg = 0;
-      return {nowThreshold: arg};
-    }
-    return arg;
-  };
-
-  var toSmartDays = function(date, now) {
-    var day;
-    var weekday = date.getDay(),
-        dayDiff = weekday - now.getDay();
-    if (dayDiff == 0)       day = 'Today';
-    else if (dayDiff == -1) day = 'Yesterday';
-    else if (dayDiff == 1 && date > now)  day = 'Tomorrow';
-    else                    day = WEEKDAYS[weekday];
-    return day + " at " + date.toLocaleTimeString();
-  };
-
-  var CONVERSIONS = {
-    millisecond: 1, // ms    -> ms
-    second: 1000,   // ms    -> sec
-    minute: 60,     // sec   -> min
-    hour:   60,     // min   -> hour
-    day:    24,     // hour  -> day
-    month:  30,     // day   -> month (roughly)
-    year:   12      // month -> year
-  };
-  var MS_IN_DAY = (CONVERSIONS.millisecond * CONVERSIONS.second * CONVERSIONS.minute * CONVERSIONS.hour * CONVERSIONS.day);
-
-  var WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
-
-  return _;
-
-})();
-
-
-
-/*
- * Wraps up a common pattern used with this plugin whereby you take a String
- * representation of a Date, and want back a date object.
- */
-Date.fromString = function(str) {
-  return new Date(Date.parse(str));
-};
-
-

app/js/angelfish/gallery.js

+/**
+ * Angelfish Resource Gallery
+ * $Id$
+ */
 
-'use strict';
+define([
+    'angular',
+	'isotope/js/isotope',
+	'angular-route',
+], function (angular, Isotope) { 'use strict';
 
-var THUMBNAIL_PLACEHOLDER = '/images/no-thumb.svg';
+	var THUMBNAIL_PLACEHOLDER = '/images/no-thumb.svg';
 
-var ICON_MAP = {
-	'application/java-archive': 'jar',
-	'application/msaccess': 'mdb',
-	'application/msword': 'doc',
-	'application/octet-stream': 'bin',
-	'application/pdf': 'pdf',
-	'application/pgp-keys': 'key',
-	'application/pics-rules': 'prf',
-	'application/postscript': 'ai',
-	'application/rar': 'rar',
-	'application/rss+xml': 'rss',
-	'application/shf+xml': 'shf',
-	'application/vnd.ms-excel': 'xls',
-	'application/vnd.ms-fontobject': 'eot',
-	'application/vnd.ms-powerpoint': 'pps',
-	'application/vnd.ms-works': 'wps',
-	'application/vnd.oasis.opendocument.formula-template': 'otf',
-	'application/wordperfect': 'wpd',
-	'application/x-apple-diskimage': 'dmg',
-	'application/x-cdlink': 'vcd',
-	'application/x-debian-package': 'deb',
-	'application/x-hdf': 'hdf',
-	'application/x-iso9660-image': 'iso',
-	'application/x-javascript': 'js',
-	'application/x-msdos-program': 'com',
-	'application/x-msi': 'msi',
-	'application/x-redhat-package-manager': 'rpm',
-	'application/x-stuffitx': 'sitx',
-	'application/x-tar': 'tar',
-	'application/x-trash': 'bak',
-	'application/xhtml+xml': 'xhtml',
-	'application/xml': 'xml',
-	'application/zip': 'zip',
-	'audio/midi': 'mid',
-	'audio/mpeg': 'mp3',
-	'audio/x-aiff': 'aif',
-	'audio/x-ms-wma': 'wma',
-	'audio/x-pn-realaudio': 'rm',
-	'audio/x-realaudio': 'ra',
-	'chemical/x-cerius': 'cer',
-	'chemical/x-mdl-sdfile': 'sdf',
-	'chemical/x-mopac-input': 'dat',
-	'chemical/x-pdb': 'pdb',
-	'image/gif': 'gif',
-	'image/jpeg': 'jpg',
-	'image/png': 'png',
-	'image/svg+xml': 'svg',
-	'image/tiff': 'tif',
-	'image/x-ms-bmp': 'bmp',
-	'image/x-photoshop': 'psd',
-	'image/x-pict': 'pct',
-	'text/calendar': 'ics',
-	'text/comma-separated-values': 'csv',
-	'text/css': 'css',
-	'text/html': 'html',
-	'text/plain': 'log',
-	'text/rtf': 'rtf',
-	'text/x-psp': 'psp',
-	'text/x-tex': 'tex',
-	'text/x-vcard': 'vcf',
-	'video/mp4': 'mp4',
-	'video/mpeg': 'mpg',
-	'video/quicktime': 'mov',
-	'video/x-flv': 'flv',
-	'video/x-ms-asf': 'asx',
-	'video/x-ms-wmv': 'wmv',
-	'video/x-msvideo': 'avi'
-};
+	var ICON_MAP = {
+		'application/java-archive': 'jar',
+		'application/msaccess': 'mdb',
+		'application/msword': 'doc',
+		'application/octet-stream': 'bin',
+		'application/pdf': 'pdf',
+		'application/pgp-keys': 'key',
+		'application/pics-rules': 'prf',
+		'application/postscript': 'ai',
+		'application/rar': 'rar',
+		'application/rss+xml': 'rss',
+		'application/shf+xml': 'shf',
+		'application/vnd.ms-excel': 'xls',
+		'application/vnd.ms-fontobject': 'eot',
+		'application/vnd.ms-powerpoint': 'pps',
+		'application/vnd.ms-works': 'wps',
+		'application/vnd.oasis.opendocument.formula-template': 'otf',
+		'application/wordperfect': 'wpd',
+		'application/x-apple-diskimage': 'dmg',
+		'application/x-cdlink': 'vcd',
+		'application/x-debian-package': 'deb',
+		'application/x-hdf': 'hdf',
+		'application/x-iso9660-image': 'iso',
+		'application/x-javascript': 'js',
+		'application/x-msdos-program': 'com',
+		'application/x-msi': 'msi',
+		'application/x-redhat-package-manager': 'rpm',
+		'application/x-stuffitx': 'sitx',
+		'application/x-tar': 'tar',
+		'application/x-trash': 'bak',
+		'application/xhtml+xml': 'xhtml',
+		'application/xml': 'xml',
+		'application/zip': 'zip',
+		'audio/midi': 'mid',
+		'audio/mpeg': 'mp3',
+		'audio/x-aiff': 'aif',
+		'audio/x-ms-wma': 'wma',
+		'audio/x-pn-realaudio': 'rm',
+		'audio/x-realaudio': 'ra',
+		'chemical/x-cerius': 'cer',
+		'chemical/x-mdl-sdfile': 'sdf',
+		'chemical/x-mopac-input': 'dat',
+		'chemical/x-pdb': 'pdb',
+		'image/gif': 'gif',
+		'image/jpeg': 'jpg',
+		'image/png': 'png',
+		'image/svg+xml': 'svg',
+		'image/tiff': 'tif',
+		'image/x-ms-bmp': 'bmp',
+		'image/x-photoshop': 'psd',
+		'image/x-pict': 'pct',
+		'text/calendar': 'ics',
+		'text/comma-separated-values': 'csv',
+		'text/css': 'css',
+		'text/html': 'html',
+		'text/plain': 'log',
+		'text/rtf': 'rtf',
+		'text/x-psp': 'psp',
+		'text/x-tex': 'tex',
+		'text/x-vcard': 'vcf',
+		'video/mp4': 'mp4',
+		'video/mpeg': 'mpg',
+		'video/quicktime': 'mov',
+		'video/x-flv': 'flv',
+		'video/x-ms-asf': 'asx',
+		'video/x-ms-wmv': 'wmv',
+		'video/x-msvideo': 'avi'
+	};
 
 
-// Controllers
-angular.module('angelfish.gallery', [
-	'ui.bootstrap.modal',
-	'ui.bootstrap.dropdownToggle',
-	'ui.bootstrap.accordion',
-	'wu.masonry',
-	'angelfish.services',
-	'angelfish.filters'
-	]).
-	controller('GalleryController', 
-		function($scope, $rootScope, $log, Asset, Metadata) {
-			$log.debug( "Starting up the Gallery." );
+	// Controllers
+	angular.module('angelfish.gallery', [
+		'ui.bootstrap.modal',
+		'ui.bootstrap.dropdownToggle',
+		'ui.bootstrap.accordion',
+		'angelfish.services',
+		'angelfish.filters'
+		]).
+		controller('GalleryController', 
+			function($scope, $rootScope, $log, Asset, Metadata) {
+				$log.debug( "Starting up the Gallery." );
 
-			$scope.inspected_asset = null;
-			$scope.metadata = null;
-			$scope.assets = Asset.query({ limit: 50, order: 'created' });
+				$scope.inspected_asset = null;
+				$scope.metadata = null;
+				$scope.assets = Asset.query({ limit: 50, order: 'created' });
+				$scope.iso = Isotope( angular.element('#asset-grid'), {});
 
-			$rootScope.$on( 'angelfish.newupload', function( event, asset_metadata ) {
-				$log.debug( "Adding new uploaded asset: %o", asset_metadata );
-				var asset = new Asset( asset_metadata );
-				$scope.assets.unshift( asset );
-				$scope.$digest();
-			});
+				$rootScope.$on( 'angelfish.newupload', function( event, asset_metadata ) {
+					$log.debug( "Adding new uploaded asset: %o", asset_metadata );
+					var asset = new Asset( asset_metadata );
+					$scope.assets.unshift( asset );
+					$scope.$digest();
+				});
 
-			$scope.inspect = function inspect( asset, event ) {
-				$log.debug( "Inspecting %o", asset );
-				angular.element('.masonry-brick').removeClass('selected');
-				$(event.delegateTarget).parent('.masonry-brick').addClass('selected');
-				$scope.inspected_asset = asset;
-				$scope.metadata = Metadata.get({ uuid: asset.uuid });
-			};
+				$scope.inspect = function inspect( asset, event ) {
+					$log.debug( "Inspecting %o", asset );
+					angular.element('.masonry-brick').removeClass('selected');
+					$(event.delegateTarget).parent('.masonry-brick').addClass('selected');
+					$scope.inspected_asset = asset;
+					$scope.metadata = Metadata.get({ uuid: asset.uuid });
+				};
 
-		}
-	).
-	directive( 'galleryTile', 
-		function() {
-			return {
-				restrict: 'E',
-				replace: false,
-				templateUrl: '/views/gallery/tile.html',
-				scope: {
-					asset: '='
-				},
-				controller: function( $scope, $element ) {
-				},
-				link: function( scope, element, attrs ) {
-					var asset = scope.asset;
-					var iconClasses = null;
-					var ext = ICON_MAP[ asset.format ];
+			}
+		).
+		directive( 'galleryTile', 
+			function() {
+				return {
+					restrict: 'E',
+					replace: false,
+					templateUrl: '/views/gallery/tile.html',
+					scope: {
+						asset: '='
+					},
+					controller: function( $scope, $element ) {
+					},
+					link: function( scope, element, attrs ) {
+						var asset = scope.asset;
+						var iconClasses = null;
+						var ext = ICON_MAP[ asset.format ];
 
-					if( typeof ext == 'undefined' ) {
-						iconClasses = 'glyphicons file';
-					} else {
-						iconClasses = 'filetype ' + ext;
+						if( typeof ext == 'undefined' ) {
+							iconClasses = 'glyphicons file';
+						} else {
+							iconClasses = 'filetype ' + ext;
+						}
+
+						element.find( 'span.icon' ).addClass( iconClasses );
+
+						console.debug( "adding preview of %s", asset.uri );
+						var preview = angular.element('<img>');
+						if ( asset.format.match(/^image\//) ) {
+							preview.attr( 'src', asset.uri + '/related/thumbnail' );
+						} else {
+							preview.attr( 'src', THUMBNAIL_PLACEHOLDER );
+						}
+						preview.prependTo( element );
 					}
-
-					element.find( 'span.icon' ).addClass( iconClasses );
-
-					console.debug( "adding preview of %s", asset.uri );
-					var preview = angular.element('<img>');
-					if ( asset.format.match(/^image\//) ) {
-						preview.attr( 'src', asset.uri );
-					} else {
-						preview.attr( 'src', THUMBNAIL_PLACEHOLDER );
-					}
-					preview.prependTo( element );
 				}
 			}
-		}
-	);
+		);
 
+});
+

app/js/angelfish/services.js

-// Services
-angular.module('angelfish.services', ['ngResource']).
+define([
+    'angular',
+	'angular-resource'
+], function (angular) { 'use strict';
 
-	// Newsgroups
-	factory('Asset', function($resource) {
-		return $resource( '/tf/v1/:uuid',
-			{
-				uuid: '@uuid'
-			}
-		);
-	}).
+	// Services
+	angular.module('angelfish.services', ['ngResource']).
 
-	factory('Metadata', function($resource) {
-		return $resource( '/tf/v1/:uuid/metadata',
-			{
-				uuid: '@uuid'
-			}
-		);
-	});
+		// Newsgroups
+		factory('Asset', function($resource) {
+			return $resource( '/tf/v1/:uuid',
+				{
+					uuid: '@uuid'
+				}
+			);
+		}).
 
+		factory('Metadata', function($resource) {
+			return $resource( '/tf/v1/:uuid/metadata',
+				{
+					uuid: '@uuid'
+				}
+			);
+		});
+
+});

app/js/angelfish/upload.js

 /**
- * Angelfish Directives
+ * Angelfish Resumable Uploader
  * $Id$