From 927b6670e2da26bf1823efd49462c8bfb8317cb7 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Tue, 4 Aug 2015 21:56:04 -0400 Subject: mx libs --- public/js/lib/view/formview.js | 135 ---------- public/js/lib/view/router.js | 61 ----- public/js/lib/view/view.js | 142 ---------- public/js/mx/mx.js | 593 +++++++++++++++++++++++++++++++++++++++++ public/js/mx/mx.soundcloud.js | 130 +++++++++ public/js/mx/mx.video.js | 118 ++++++++ public/js/mx/mx.vimeo.js | 175 ++++++++++++ public/js/mx/mx.youtube.js | 204 ++++++++++++++ public/js/view/formview.js | 135 ++++++++++ public/js/view/router.js | 61 +++++ public/js/view/view.js | 142 ++++++++++ 11 files changed, 1558 insertions(+), 338 deletions(-) delete mode 100644 public/js/lib/view/formview.js delete mode 100644 public/js/lib/view/router.js delete mode 100644 public/js/lib/view/view.js create mode 100644 public/js/mx/mx.js create mode 100644 public/js/mx/mx.soundcloud.js create mode 100644 public/js/mx/mx.video.js create mode 100644 public/js/mx/mx.vimeo.js create mode 100644 public/js/mx/mx.youtube.js create mode 100644 public/js/view/formview.js create mode 100644 public/js/view/router.js create mode 100644 public/js/view/view.js (limited to 'public/js') diff --git a/public/js/lib/view/formview.js b/public/js/lib/view/formview.js deleted file mode 100644 index f5845e7..0000000 --- a/public/js/lib/view/formview.js +++ /dev/null @@ -1,135 +0,0 @@ -var FormView = View.extend({ - - method: "post", - useMinotaur: false, - - events: { - "submit form": "save" - }, - - initialize: function(opt){ - if (opt && opt.parent) { - this.parent = opt.parent - } - this.$form = this.$("form") - this.$errors = this.$(".errors") - this.$errorList = this.$(".errorList") - }, - - reset: function(){ - this.$("input,textarea").not("[type='submit']").not("[type='hidden']").val("") - }, - - showErrors: function(errors){ - if (errors && errors.length) { - this.$errorList.empty(); - for (var i in errors) { - this.$errorList.append('
' + errors[i] + '
'); - } - this.$errors.css("opacity", 1.0); - setTimeout(function(){ - this.$errors.show().css("opacity", 1.0); - }.bind(this), 200) - } - }, - - serialize: function(){ - var fd = new FormData(), hasCSRF = false - - this.$("input[name], select[name], textarea[name]").each( function(){ - if (this.type == "file") { - if (this.files.length > 0) { - fd.append(this.name, this.files[0]); - } - } - else if (this.type == "password") { - if (this.value.length > 0) { - fd.append(this.name, SHA1.hex('lol$' + this.value + '$vvalls')) - } - } - else { - fd.append(this.name, this.value); - hasCSRF = hasCSRF || this.name == "_csrf" - } - }); - - if (! hasCSRF) { - fd.append("_csrf", $("[name=_csrf]").val()) - } - - return fd - }, - - save: function(e, successCallback, errorCallback){ - e && e.preventDefault() - - this.$errors.hide().css("opacity", 0.0); - - if (this.validate) { - var errors = this.validate() - if (errors && errors.length) { - if (errorCallback) { - errorCallback(errors) - } - else { - this.showErrors(errors) - } - return - } - } - - var action = typeof this.action == "function" ? this.action() : this.action - if (! action) return - - var request = $.ajax({ - url: action, - type: this.method, - data: this.serialize(), - dataType: "json", - processData: false, - contentType: false, - }) - - if (this.useMinotaur) { - Minotaur.show() - } - - request.done($.proxy(function (response) { - if (this.useMinotaur) { - Minotaur.hide() - } - if (response.error) { - var errors = [] - for (var key in response.error.errors) { - errors.push(response.error.errors[key].message); - } - if (errorCallback) { - errorCallback(errors) - } - else { - this.showErrors(errors) - } - return - } - else { - if (successCallback) { - successCallback(response) - } - if (this.success) { - this.success(response) - } - } - }, this)); - } - -}) - - -var ModalFormView = ModalView.extend(FormView.prototype).extend({ - - load: function(){ - this.reset() - this.show() - } - -}) diff --git a/public/js/lib/view/router.js b/public/js/lib/view/router.js deleted file mode 100644 index 28905b2..0000000 --- a/public/js/lib/view/router.js +++ /dev/null @@ -1,61 +0,0 @@ -var Router = View.extend({ - - route: function(){ - - this.originalPath = window.location.pathname - - var routes = is_mobile ? this.mobileRoutes : this.routes, - pathname = window.location.pathname, - path = pathname.split("/"); - - for (var i = 0; i < path.length; i++) { - if (! path[i].length) { - path[i] = null - } - } - - if (pathname in routes) { - this[this.routes[pathname]](null) - return - } - - if (path[path.length-1] == null) { - path.pop() - } - - for (var route in routes) { - var routePath = route.split("/") - if (routePath[1] == path[1]) { - if (routePath[2] && routePath[2].indexOf(":") !== -1 && path[2] && (path[3] === routePath[3]) ) { - this[this.routes[route]](null, path[2]) - return - } - else if (routePath[2] == path[2]) { - if (routePath[3] && path[3]) { - if (routePath[3].indexOf(":") !== -1) { - this[this.routes[route]](null, path[3]) - return - } - else if (routePath[3] == path[3]) { - this[this.routes[route]](null) - return - } - } - else if (! routePath[3] && ! path[3]) { - this[this.routes[route]](null) - return - } - } - else if (! routePath[2] && (! path[2].length || ! path[2])) { - this[this.routes[route]](null) - return - } - } - } - - if (is_mobile) { - window.location.href = "/" - } - } - -}) diff --git a/public/js/lib/view/view.js b/public/js/lib/view/view.js deleted file mode 100644 index 87d6ee4..0000000 --- a/public/js/lib/view/view.js +++ /dev/null @@ -1,142 +0,0 @@ -var View = (function($, _){ - - var View = function(options) { - this._id = _.uniqueId('view') - this.type = "view" - options || (options = {}); - _.extend(this, _.pick(options, viewOptions)) - this._ensureElement() - this.initialize.apply(this, arguments) - this.delegateEvents() - } - - var delegateEventSplitter = /^(\S+)\s*(.*)$/; - - var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; - - _.extend(View.prototype, { - - // The default `tagName` of a View's element is `"div"`. - tagName: 'div', - - $: function(selector) { - return this.$el.find(selector); - }, - - initialize: function(){}, - - setElement: function(element, delegate) { - if (this.$el) this.undelegateEvents(); - this.$el = element instanceof $ ? element : $(element); - this.el = this.$el[0]; - if (delegate !== false) this.delegateEvents(); - return this; - }, - - // Set callbacks, where `this.events` is a hash of - // - // *{"event selector": "callback"}* - // - // { - // 'mousedown .title': 'edit', - // 'click .button': 'save', - // 'click .open': function(e) { ... } - // } - // - // pairs. Callbacks will be bound to the view, with `this` set properly. - // Uses event delegation for efficiency. - // Omitting the selector binds the event to `this.el`. - // This only works for delegate-able events: not `focus`, `blur`, and - // not `change`, `submit`, and `reset` in Internet Explorer. - delegateEvents: function(events) { - if (!(events || (events = _.result(this, 'events')))) return this; - this.undelegateEvents(); - for (var key in events) { - var method = events[key]; - if (!_.isFunction(method)) method = this[events[key]]; - if (!method) continue; - - var match = key.match(delegateEventSplitter); - var eventName = match[1], selector = match[2]; - method = _.bind(method, this); - eventName += '.delegateEvents' + this._id; - if (is_mobile && (selector === 'mouseenter' || selector === 'mouseleave')) { - continue - } - else if (selector === '') { - this.$el.on(eventName, method); - } else { - this.$el.on(eventName, selector, method); - } - } - return this; - }, - - // Clears all callbacks previously bound to the view with `delegateEvents`. - undelegateEvents: function() { - this.$el.off('.delegateEvents' + this._id); - return this; - }, - - // Ensure that the View has a DOM element to render into. - // If `this.el` is a string, pass it through `$()`, take the first - // matching element, and re-assign it to `el`. Otherwise, create - // an element from the `id`, `className` and `tagName` properties. - _ensureElement: function() { - this.setElement(_.result(this, 'el'), false); - }, - - preventDefault: function(e){ - e && e.preventDefault() - }, - - stopPropagation: function(e){ - e && e.stopPropagation() - }, - - }); - - - var extend = function(protoProps, staticProps) { - var staticProps = staticProps || {} - var parent = this; - var child; - var childEvents = {}; - - // The constructor function for the new subclass is either defined by you - // (the "constructor" property in your `extend` definition), or defaulted - // by us to simply call the parent's constructor. - if (protoProps && _.has(protoProps, 'constructor')) { - child = protoProps.constructor; - } else { - child = function(){ return parent.apply(this, arguments); }; - } - - // Extend events so we can subclass views - _.extend(childEvents, parent.prototype.events, protoProps.events) - - // Add static properties to the constructor function, if supplied. - _.extend(child, parent, staticProps); - - // Set the prototype chain to inherit from `parent`, without calling - // `parent`'s constructor function. - var Surrogate = function(){ this.constructor = child; }; - Surrogate.prototype = parent.prototype; - child.prototype = new Surrogate; - - // Add prototype properties (instance properties) to the subclass, - // if supplied. - if (protoProps) _.extend(child.prototype, protoProps); - - // Set a convenience property in case the parent's prototype is needed - // later. - child.prototype.__super__ = parent.prototype; - child.prototype.events = childEvents - - return child; - }; - - View.extend = extend; - - return View; -})(Zepto, _) diff --git a/public/js/mx/mx.js b/public/js/mx/mx.js new file mode 100644 index 0000000..60651eb --- /dev/null +++ b/public/js/mx/mx.js @@ -0,0 +1,593 @@ +/** + * Copyright (C) 2013 by Evan You + * + * 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. + */ + +var MX = MX || (function (undefined) { + + var MX = { + version: '0.1.0', + prefix: undefined, + rotationUnit: 'rad', + } + + var floatPrecision = 5 + + // ======================================================================== + // Setup & Compatibility + // ======================================================================== + + var transformProp, + transitionProp, + transformOriginProp, + transformStyleProp, + perspectiveProp, + transitionEndEvent + + var positionAtCenter = true, // whether to auto center objects + centeringCSS // styles to inject for center positioning + + document.addEventListener('DOMContentLoaded', setup) + + function setup () { + + // sniff prefix + + var s = document.body.style + + MX.prefix = + 'webkitTransform' in s ? 'webkit' : + 'mozTransform' in s ? 'moz' : + 'msTransform' in s ? 'ms' : '' + + transformProp = MX.transformProp = addPrefix('transform') + transitionProp = MX.transitionProp = addPrefix('transition') + transformOriginProp = MX.transformOriginProp = addPrefix('transformOrigin') + transformStyleProp = MX.transformStyleProp = addPrefix('transformStyle') + perspectiveProp = MX.perspectiveProp = addPrefix('perspective') + transitionEndEvent = MX.transitionEndEvent = MX.prefix === 'webkit' ? 'webkitTransitionEnd' : 'transitionend' + + // shiv rAF + + var vendors = ['webkit', 'moz', 'ms'] + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'] + window.cancelAnimationFrame = + window[vendors[x]+'CancelAnimationFrame'] || + window[vendors[x]+'CancelRequestAnimationFrame'] + } + + // inject centering css + + centeringCSS = document.createElement('style') + centeringCSS.type = 'text/css' + centeringCSS.innerHTML = + '.mx-object3d {' + + 'position: absolute;' + + 'top: 50%;' + + 'left: 50%;}' + injectCenteringCSS() + + window.scrollTo(0,0) + } + + function injectCenteringCSS () { + document.head.appendChild(centeringCSS) + } + + function removeCenteringCSS () { + document.head.removeChild(centeringCSS) + } + + // ======================================================================== + // Utils + // ======================================================================== + + function toDeg (rad) { + return rad / Math.PI * 180 + } + + function toRad (deg) { + return deg / 180 * Math.PI + } + + function buildRotationTranslation (obj) { + + // used when rotationOrigin is set + + var origin = obj.rotationOrigin + if (!origin) { + return + } else { + var dx = origin.x - obj.x, + dy = -(origin.y - obj.y), + dz = -(origin.z - obj.z) + return { + before: 'translate3d(' + dx.toFixed(floatPrecision) +'px,' + dy.toFixed(floatPrecision) + 'px,' + dz.toFixed(floatPrecision) + 'px) ', + after: 'translate3d(' + (-dx).toFixed(floatPrecision) + 'px,' + (-dy).toFixed(floatPrecision) + 'px,' + (-dz).toFixed(floatPrecision) + 'px) ' + } + } + } + + function addPrefix (string) { + if (MX.prefix) { + string = MX.prefix + string.charAt(0).toUpperCase() + string.slice(1) + } + return string + } + + // ======================================================================== + // Base Object3D + // ======================================================================== + + function Object3D (el) { + + this.setupDomElement(el) + this.setCSSTransformStyle('preserve-3d') + this.el.classList.add('mx-object3d') + + this.parent = undefined + this.children = [] + this.updateChildren = true + + this.inverseLookAt = false + + this.persisted = true + + this.reset() + +// this.quaternion = new MX.Quaternion () +// this.quaternion._euler = this + + var width, height, + self = this + + Object.defineProperty(this, 'width', { + get: function () { + return width + || parseInt(self.el.style.width, 10) * app_devicePixelRatio + || 0 + }, + set: function (val) { + width = val + this.el.style.width = (width/app_devicePixelRatio) + 'px' + } + }) + + Object.defineProperty(this, 'height', { + get: function () { + return height + || parseInt(self.el.style.height, 10) * app_devicePixelRatio + || 0 + }, + set: function (val) { + height = val + this.el.style.height = (height/app_devicePixelRatio) + 'px' + } + }) + } + + Object3D.prototype = { + + constructor: Object3D, + + reset: function () { + this.x = this.__x = 0 + this.y = this.__y = 0 + this.z = this.__z = 0 + this.rotationX = this.__rotationX = 0 + this.rotationY = this.__rotationY = 0 + this.rotationZ = this.__rotationZ = 0 + this.scaleX = this.__scaleX = 1 + this.scaleY = this.__scaleY = 1 + this.scaleZ = this.__scaleZ = 1 + this.scale = this.__scale = 1 + this.perspective = this.__perspective = 0 + this.rotationOrigin = undefined + this.followTarget = undefined +// this.quaternion = new MX.Quaternion() + this.dirty = true + this.update() + }, + + setupDomElement: function (el) { + this.el = undefined + if (el instanceof HTMLElement) { + this.el = el + } else if (typeof el === 'string') { + var tag = el.match(/^[^.#\s]*/)[1], + id = el.match(/#[^.#\s]*/), + classes = el.match(/\.[^.#\s]*/g) + this.el = document.createElement(tag || 'div') + if (id) { + this.el.id = id[0].slice(1) + } + if (classes) { + var i = classes.length + while (i--) { + this.el.classList.add(classes[i].slice(1)) + } + } + } else { + this.el = document.createElement('div') + } + }, + + update: function () { + + if (this.updateChildren) { + var i = this.children.length + while (i--) { + this.children[i].update() + } + } + + if (this.followTarget) { + this.lookAt(this.followTarget, false) + } + + if (this.scaleX !== this.__scaleX || + this.scaleY !== this.__scaleY || + this.scaleZ !== this.__scaleZ) { + this.__scaleX = this.scaleX + this.__scaleY = this.scaleY + this.__scaleZ = this.scaleZ + this.dirty = true + } + + if (this.scale !== this.__scale) { + this.scaleX = + this.scaleY = + this.scaleZ = + this.__scaleX = + this.__scaleY = + this.__scaleZ = + this.__scale = + this.scale + this.dirty = true + } + + if (this.rotationX !== this.__rotationX || + this.rotationY !== this.__rotationY || + this.rotationZ !== this.__rotationZ) { + this.__rotationX = this.rotationX + this.__rotationY = this.rotationY + this.__rotationZ = this.rotationZ + this.dirty = true + } + + if (this.x !== this.__x || + this.y !== this.__y || + this.z !== this.__z) { + this.__x = this.x + this.__y = this.y + this.__z = this.z + this.dirty = true + } + + if (this.perspective !== this.__perspective) { + this.__perspective = this.perspective + this.dirty = true + } + + if (this.dirty && this.el) { + + var rotationTranslation = buildRotationTranslation(this), + rotation = 'rotateX(' + this.rotationX.toFixed(floatPrecision) + MX.rotationUnit + ') ' + + 'rotateY(' + this.rotationY.toFixed(floatPrecision) + MX.rotationUnit + ') ' + + 'rotateZ(' + this.rotationZ.toFixed(floatPrecision) + MX.rotationUnit + ') ' + + var transformString = + (MX.positionAtCenter ? 'translate3d(-50%, -50%, 0) ' : '') + + (this.perspective ? 'perspective(' + this.perspective + 'px) ' : '') + + 'translate3d(' + + this.x.toFixed(floatPrecision || 0) + 'px,' + + (-this.y).toFixed(floatPrecision) + 'px,' + + (-this.z).toFixed(floatPrecision) + 'px) ' + + 'scale3d(' + + (app_devicePixelRatio * this.scaleX).toFixed(floatPrecision) + ',' + + (app_devicePixelRatio * this.scaleY).toFixed(floatPrecision) + ',' + + (app_devicePixelRatio * this.scaleZ).toFixed(floatPrecision) + ') ' + + if (rotationTranslation) { + transformString += rotationTranslation.before + + rotation + + rotationTranslation.after + + } else { + transformString += rotation + } + + this.el.style[transformProp] = transformString + this.dirty = false + } + + return this + + }, + + // taken from three.js + setFromQuaternion: function ( q, order, update ) { + // q is assumed to be normalized + + // http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m + + var sqx = q.x * q.x; + var sqy = q.y * q.y; + var sqz = q.z * q.z; + var sqw = q.w * q.w; + + this.rotationX = Math.atan2( 2 * ( q.x * q.w - q.y * q.z ), ( sqw - sqx - sqy + sqz ) ); + this.rotationY = Math.asin( clamp( 2 * ( q.x * q.z + q.y * q.w ), -1, 1 ) ); + this.rotationZ = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw + sqx - sqy - sqz ) ); + }, + + lookAt: function (target, update) { + var r = this.getLookAtEuler(target) + this.setRotation(r) + if (update !== false) this.update() + return this + }, + + getLookAtEuler: function (target) { + // euler order XYZ + var r = {}, + dx = target.x - this.x, + dy = target.y - this.y, + dz = target.z - this.z + if (this.inverseLookAt) { + dx = -dx + dy = -dy + dz = -dz + } + if (dz === 0) dz = 0.001 + r.x = -Math.atan2(dy, dz) + var flip = dz > 0 ? 1 : -1 + r.y = flip * Math.atan2(dx * Math.cos(r.x), dz * -flip) + r.z = Math.atan2(Math.cos(r.x), Math.sin(r.x) * Math.sin(r.y)) - Math.PI / 2 + if (MX.rotationUnit === 'deg') { + r.x = toDeg(r.x) + r.y = toDeg(r.y) + r.z = toDeg(r.z) + } + return r + }, + + add: function () { + if (!this.el) return + var parent = this + Array.prototype.forEach.call(arguments, function (child) { + if (!child instanceof Object3D) return + parent.el.appendChild(child.el) + if (!parent.children) parent.children = [] + parent.children.push(child) + child.parent = parent + }) + return this + }, + + remove: function () { + var parent = this + Array.prototype.forEach.call(arguments, function (child) { + var index = parent.children.indexOf(child) + if (index !== -1) { + parent.children.splice(index, 1) + parent.el.removeChild(child.el) + child.parent = undefined + } + }) + return this + }, + + addTo: function (target) { + if (typeof target === 'string') { + target = document.querySelector(target) + } + if (target instanceof HTMLElement && target.appendChild) { + target.appendChild(this.el) + } else if (target instanceof Object3D || target instanceof Scene) { + target.add(this) + } + return this + }, + + removeElement: function () { + if (this.el.parentNode) { + this.el.parentNode.removeChild(this.el) + } + }, + + setPosition: function (tar) { + this.x = (tar.x || tar.x === 0) ? tar.x : this.x + this.y = (tar.y || tar.y === 0) ? tar.y : this.y + this.z = (tar.z || tar.z === 0) ? tar.z : this.z + }, + + setRotation: function (tar) { + this.rotationX = (tar.x || tar.x === 0) ? tar.x : this.rotationX + this.rotationY = (tar.y || tar.y === 0) ? tar.y : this.rotationY + this.rotationZ = (tar.z || tar.z === 0) ? tar.z : this.rotationZ + }, + + setScale: function (tar) { + this.scaleX = (tar.x || tar.x === 0) ? tar.x : this.scaleX + this.scaleY = (tar.y || tar.y === 0) ? tar.y : this.scaleY + this.scaleZ = (tar.z || tar.z === 0) ? tar.z : this.scaleZ + }, + + setCSSTransformOrigin: function (origin) { + this.el && (this.el.style[transformOriginProp] = origin) + return this + }, + + setCSSTransformStyle: function (style) { + this.el && (this.el.style[transformStyleProp] = style) + return this + }, + + setCSSTransition: function (trans) { + this.el && (this.el.style[transitionProp] = trans) + return this + }, + + setCSSPerspective: function (pers) { + this.el && (this.el.style[perspectiveProp] = pers) + return this + }, + + move: function(ops){ + var layer = this + layer.ops = defaults(ops, layer.ops) + for (var i in ops) { + layer[i] = ops[i] + } + layer.dirty = true + layer.update() + }, + + onTransitionEnd: function (callback) { + this.cancelTransitionEnd() + var el = this.el + el.addEventListener(transitionEndEvent, onEnd) + function onEnd () { + el.removeEventListener(transitionEndEvent, onEnd) + callback() + } + }, + + cancelTransitionEnd: function () { + this.el.removeEventListener(transitionEndEvent) + }, + + toString: function(params){ + params = params || "id width height depth x y z rotationX rotationY rotationZ scale".split(" ") + return this.__toString(params) + }, + + __toString: function(params, func){ + this.id = this.id || _.uniqueId() + var list = [], + obj = {}, + type = this.type || "Object3d", + name = type.toLowerCase(), + val, + param + for (var i in params) { + param = params[i] + val = this[param] + if (val === 0 && ! func) continue; + if (typeof val == "number") { + if (param.indexOf("rotation") != -1) { + obj[param] = Number(val.toFixed(3)) + } + else { + obj[param] = ~~val + } + } + else { + obj[param] = val + } + } + + return (func || "var " + name + " = new MX." + type ) + "(" + + JSON.stringify(obj, undefined, 2) + + ")\n" + (func ? "" : "scene.add(" + name + ")") + }, + + contains: function(x,y,z){ + var containsX = false, + containsY = false, + containsZ = false + + if (x === null) { + containsX = true + } + else { + containsX = abs(this.x - x) <= this.width/2 + } + + if (y === null) { + containsY = true + } + else { + containsY = abs(this.y - y) <= this.height/2 + } + + if (z === null) { + containsZ = true + } + else { + containsZ = abs(this.z - z) <= this.depth/2 + } + + return (containsX && containsY && containsZ) + } + + } + + // ======================================================================== + // Inheritance + // ======================================================================== + + Object3D.extend = extend.bind(Object3D) + + function extend (props) { + var Super = this + var ExtendedObject3D = function () { + Super.call(this) + props.init && props.init.apply(this, arguments) + } + ExtendedObject3D.prototype = Object.create(Super.prototype) + for (var prop in props) { + if (props.hasOwnProperty(prop) && prop !== 'init') { + ExtendedObject3D.prototype[prop] = props[prop] + } + } + ExtendedObject3D.extend = extend.bind(ExtendedObject3D) + return ExtendedObject3D + } + + // ======================================================================== + // Expose API + // ======================================================================== + + MX.Object3D = Object3D + MX.toRad = toRad + MX.toDeg = toDeg + + // center positioning getter setter + Object.defineProperty(MX, 'positionAtCenter', { + get: function () { + return positionAtCenter + }, + set: function (val) { + if (typeof val !== 'boolean') return + positionAtCenter = val + if (positionAtCenter) { + injectCenteringCSS() + } else { + removeCenteringCSS() + } + } + }) + + return MX + +})() \ No newline at end of file diff --git a/public/js/mx/mx.soundcloud.js b/public/js/mx/mx.soundcloud.js new file mode 100644 index 0000000..fecb2f4 --- /dev/null +++ b/public/js/mx/mx.soundcloud.js @@ -0,0 +1,130 @@ +MX.Soundcloud = MX.Object3D.extend({ + init: function (ops) { + + this.type = "Soundcloud" + this.media = ops.media + this.width = 0 + this.height = 0 + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.scale = ops.scale || 1 + this.backface = ops.backface || false + + ops.className && this.el.classList.add(ops.className) + this.backface && this.el.classList.add("backface-visible") + this.el.classList.add("audio") + this.el.classList.add("mx-scenery") + + this.el.style.backgroundRepeat = 'no-repeat' + this.paused = true + + this.ops = ops + }, + + load: function(ops){ + if (ops) { + ops = this.ops = defaults(ops, this.ops) + } + else { + ops = this.ops + } + + this.width = ops.media.width + this.height = ops.media.height + + var tag = Parser.lookup.soundcloud.tag(ops.media) + var $iframe = $(tag) + var iframe = $iframe[0] + $iframe.css('z-index', '-1') + this.el.appendChild( iframe ) + + var overlay = this.overlay = document.createElement("div") + overlay.style.width = "100%" + overlay.style.height = "100%" + overlay.style.position = "absolute" + overlay.style.top = "0" + overlay.style.left = "0" + overlay.style.zIndex = "2" + overlay.className = "overlay" + this.el.appendChild(overlay) + + this.player = SC.Widget( iframe ) + this.player.setVolume(80) + + this.duration = 0 + + this.player.bind(SC.Widget.Events.READY, this.ready.bind(this)) +// this.player.bind(SC.Widget.Events.LOAD_PROGRESS, this.loadProgress.bind(this)) +// this.player.bind(SC.Widget.Events.PLAY_PROGRESS, this.playProgress.bind(this)) + this.player.bind(SC.Widget.Events.PLAY, this.didPlay.bind(this)) + this.player.bind(SC.Widget.Events.PAUSE, this.didPause.bind(this)) + this.player.bind(SC.Widget.Events.FINISH, this.finished.bind(this)) + }, + + ready: function(){ + this.seek( this.media.keyframe || 0 ) + + if (this.media.autoplay) { + this.play() + } + + this.player.getDuration(function(duration){ + this.duration = duration + }.bind(this)) + }, + + play: function(){ + this.player.play() + }, + + pause: function(){ + this.player.pause() + }, + + toggle: function(state){ + if (typeof state === "boolean") { + if (state) this.play() + else this.pause() + } + else { + this.player.toggle() + } + }, + + seek: function(n){ + if (n < 1) { + n = n * this.duration + } + this.player.seekTo(n) + }, + + setLoop: function(state){ + this.media.loop = state + }, + + setVolume: function(n){ + if (this.muted || ! this.player) return + this.player.setVolume(floor( n * 100 )) + }, + + didPlay: function(){ + this.paused = false + }, + + didPause: function(){ + this.paused = true + }, + + finished: function(){ + console.log("soundcloud finished") + if (this.media.loop) { + this.seek(0) + this.play() + } + else if (this.bound) { + $(".playButton").removeClass('playing') + } + }, + +}) diff --git a/public/js/mx/mx.video.js b/public/js/mx/mx.video.js new file mode 100644 index 0000000..53ccf2e --- /dev/null +++ b/public/js/mx/mx.video.js @@ -0,0 +1,118 @@ +MX.Video = MX.Object3D.extend({ + + init: function (ops) { + + this.type = "Video" + + this.media = ops.media + this.width = ops.media.width + this.height = ops.media.height + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.rotationX = ops.rotationX || 0 + this.rotationY = ops.rotationY || 0 + this.rotationZ = ops.rotationZ || 0 + this.scale = ops.scale || 1 + this.backface = ops.backface || false + + ops.className && this.el.classList.add(ops.className) + this.backface && this.el.classList.add("backface-visible") + this.el.classList.add("video") + this.el.classList.add("mx-scenery") + this.paused = !! this.media.autoplay + this.playing = false + this.muted = app.muted || !! this.media.mute + }, + + load: function(ops){ + this.paused = true + + this.player = document.createElement('video') + this.player.addEventListener("loadedmetadata", this.ready.bind(this)) + this.player.addEventListener("error", this.error.bind(this)) + this.player.addEventListener("ended", this.finished.bind(this)) + this.player.width = "100%" + this.player.height = "100%" + this.player.src = this.media.url + this.player.load() + + this.el.appendChild(this.player) + }, + + ready: function(){ + this.seek( this.media.keyframe || 0 ) + + if (this.media.mute) { + this.mute() + } + else { + this.unmute() + } + + if (this.media.autoplay) { + this.play() + } + }, + + error: function(err){ + console.log("video error", err) + }, + + play: function(){ + this.paused = false + this.playing = true + this.player.play() + }, + + pause: function(){ + this.paused = true + this.playing = false + this.player.pause() + }, + + seek: function(n){ + if (n < 1) { + n = n * this.duration() + } + this.player.currentTime = n + }, + + mute: function(){ + this.player.muted = true + this.player.volume = 0 + this.muted = true + }, + + unmute: function(){ + this.player.muted = false + this.player.volume = 0.8 + this.muted = false + }, + + setVolume: function(n){ + if (this.muted || ! this.player) return + this.player.volume = n + }, + + setLoop: function(state){ + this.media.loop = state + }, + + duration: function(){ + return this.player.duration + }, + + finished: function(){ + console.log("video finished") + if (this.media.loop) { + this.seek(0) + this.play() + } + else if (this.bound) { + $(".playButton").removeClass('playing') + } + }, + + +}) diff --git a/public/js/mx/mx.vimeo.js b/public/js/mx/mx.vimeo.js new file mode 100644 index 0000000..af138ae --- /dev/null +++ b/public/js/mx/mx.vimeo.js @@ -0,0 +1,175 @@ +MX.Vimeo = MX.Object3D.extend({ + + init: function (ops) { + + this.type = "Vimeo" + + this.media = ops.media + this.width = ops.media.width + this.height = ops.media.height + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.rotationX = ops.rotationX || 0 + this.rotationY = ops.rotationY || 0 + this.rotationZ = ops.rotationZ || 0 + this.scale = ops.scale || 1 + this.backface = ops.backface || false + + ops.className && this.el.classList.add(ops.className) + this.backface && this.el.classList.add("backface-visible") + this.el.classList.add("video") + this.el.classList.add("mx-scenery") + this.playing = false + this.paused = !! this.media.autoplay + this.muted = app.muted || !! this.media.mute + this.started = false + }, + + load: function (ops) { + var uid = 'player-' + Uid () + var loop = this.media.loop ? 'loop=1' : "" + var preload = document.createElement("iframe") + preload.id = uid + preload.setAttribute("src", "//player.vimeo.com/video/" + this.media.token + "?api=1&badge=0&controls=0branding=0&byline=0&portrait=0&title=0&" + loop + "&player_id=" + uid) + preload.style.backgroundImage = "url(" + this.media.thumbnail + ")" + preload.style.width = "100%" + preload.style.height = "100%" + preload.style.border = "0" + preload.style.pointerEvents = "none" + preload.className = "preload" + this.el.appendChild(preload) + this.player = $f(preload) + + this.player.addEvent('ready', $.proxy(this.ready, this)) + }, + + ready: function(){ + console.log("vimeo ready") + + this.started = true + + // wait until ready before binding events. other events: play, pause + this.player.addEvent('play', $.proxy(this.onPlay, this)) + this.player.addEvent('pause', $.proxy(this.onPause, this)) + this.player.addEvent('finish', $.proxy(this.finished, this)) + + // this is async on vimeo so call it asap + this.player.api('getDuration', $.proxy(function(n){ + console.log("vimeo duration", n) + this.player.duration = n + }, this)) + + if (this.media.mute) { + this.mute() + } + else { + this.unmute() + } + + this.seek( this.media.keyframe || 0 ) + + if (this.media.autoplay) { + this.play() + } + else { + this.pause() + } + }, + + error: function(err){ + console.log("vimeo error", err) + }, + + play: function(){ + this.paused = false + this.player.api('play') + }, + + pause: function(){ + this.paused = true + this.player.api('pause') + }, + + seek: function(n){ + // defer seek until we have duration + if (! this.duration()) { + setTimeout($.proxy(function(){ + this.seek(n) + }, this), 300) + return + } + + if (! this.started) { + return + } + + if (n < 1) { + n = n * this.duration() + } + this.player.api('seekTo', max(0, n-1)) + if (this.paused) { + this.paused = false + this.play() + this.pause() + setTimeout($.proxy(function(){ + this.pause() + }, this), 100) + } + }, + + duration: function(){ + return this.player.duration + }, + + mute: function(){ + this.player.api('setVolume', 0.0) + this.muted = true + }, + + unmute: function(){ + this.player.api('setVolume', 0.8) + this.muted = false + }, + + setVolume: function(n){ + if (this.muted || ! this.player) return + this.player.api('setVolume', n) + }, + + setLoop: function(state){ + this.media.loop = state + this.player.api('setLoop', state) + }, + + onPlay: function(){ + if (this.paused) { + this.pause() + } + else { + this.playing = true + } + }, + + onPause: function(){ + if (! this.paused) { + this.play() + } + else { + this.playing = false + } + }, + + finished: function(){ + console.log("vimeo finished") + if (this.media.loop) { + this.seek(0) + this.play() + } +// else if (this.bound) { + if (! this.media.loop && this.bound) { + $(".playButton").removeClass('playing') + } + } + +}) diff --git a/public/js/mx/mx.youtube.js b/public/js/mx/mx.youtube.js new file mode 100644 index 0000000..8cd9f59 --- /dev/null +++ b/public/js/mx/mx.youtube.js @@ -0,0 +1,204 @@ +MX.Youtube = MX.Object3D.extend({ + + init: function (ops) { + + this.type = "Youtube" + + this.media = ops.media + this.width = ops.media.width + this.height = ops.media.height + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.rotationX = ops.rotationX || 0 + this.rotationY = ops.rotationY || 0 + this.rotationZ = ops.rotationZ || 0 + this.scale = ops.scale || 1 + this.backface = ops.backface || false + + ops.className && this.el.classList.add(ops.className) + this.backface && this.el.classList.add("backface-visible") + this.el.classList.add("video") + this.el.classList.add("mx-scenery") + this.paused = !! this.media.autoplay + this.playing = false + this.muted = app.muted || !! this.media.mute + }, + + load: function (ops) { + var base = this + var uid = 'player-' + Uid () + var preload = document.createElement("div") + preload.id = uid + preload.style.backgroundImage = "url(" + this.media.thumbnail + ")" + preload.style.backgroundSize = "cover" + preload.style.width = "100%" + preload.style.height = "100%" + preload.style.pointerEvents = "none" + preload.style.position = "absolute" + preload.style.top = "0" + preload.style.left = "0" + preload.style.zIndex = "1" + preload.className = "preload" + this.el.appendChild(preload) + + var overlay = this.overlay = document.createElement("div") + overlay.style.width = "100%" + overlay.style.height = "100%" + overlay.style.position = "absolute" + overlay.style.top = "0" + overlay.style.left = "0" + overlay.style.zIndex = "2" + overlay.className = "overlay" + this.el.appendChild(overlay) + this.defer(uid) + }, + + defer: function (uid){ + if (! YT || ! YT.loaded) { + setTimeout(function(){ + this.defer(uid) + }.bind(this), 300) + } + else { + // not sure why i need to delay here.. + // stopped working until i added the setTimeout + setTimeout(function(){ + this.build(uid) + }.bind(this), 20) + } + }, + + build: function(uid){ + this.player = new YT.Player(uid, { + videoId: this.media.token, + width: this.width, + height: this.height, + events: { + onReady: this.ready.bind(this), + onError: this.error.bind(this), + onStateChange: this.statechange.bind(this), + }, + playerVars: { + autohide: 1, + autoplay: 0, + disablekb: 1, + controls: 0, + enablejsapi: 1, + origin: window.location.origin, + fs: 0, + modestbranding: 1, + iv_load_policy: 3, // no annotations + loop: 0, + showinfo: 0, + rel: 0, + wmode: 'opaque', + }, + }) + }, + + ready: function(){ + console.log("youtube ready") + + if (this.media.autoplay) { + this.play() + } + else { + this.pause() + } + + if (this.media.mute) { + this.mute() + } + else { + this.unmute() + } + + this.seek( this.media.keyframe || 0 ) + }, + + error: function(err){ + console.log("youtube error", err) + }, + + statechange: function(e){ + switch (e.data) { + case -1: // unstarted + break + case 0: // finished + this.finished() + break + case 1: // play + if (this.paused) { + this.pause() + } + break + case 2: // pause + break + case 3: // buffering + break + case 5: // cued + break + } + }, + + play: function(){ + this.paused = false + this.playing = true + this.player.playVideo() + }, + + pause: function(){ + this.paused = true + this.playing = false + this.player.pauseVideo() + }, + + seek: function(n, allowSeekAhead){ + if (n < 1) { + n = n * this.duration() + } + allowSeekAhead = typeof allowSeekAhead == "boolean" ? allowSeekAhead : true + this.player.seekTo(n, true) // set to false if seeking manually + }, + + duration: function(){ + return this.player.getDuration() + }, + + mute: function(){ + this.player.mute() + this.muted = true + }, + + unmute: function(){ + this.player.unMute() + this.player.setVolume(80) + this.muted = false + }, + + setVolume: function(n){ + if (this.muted || ! this.player || ! this.player.setVolume) return + this.player.setVolume( floor(n * 100) ) + }, + + setLoop: function(state){ + this.media.loop = state + }, + + finished: function(){ + console.log("youtube finished") + if (this.media.loop) { + this.seek(0) + this.play() + } + else if (this.bound) { + $(".playButton").removeClass('playing') + } + } + +}) + +window.onYouTubePlayerAPIReady = function(){ + // console.log("youtube api ready") +} diff --git a/public/js/view/formview.js b/public/js/view/formview.js new file mode 100644 index 0000000..f5845e7 --- /dev/null +++ b/public/js/view/formview.js @@ -0,0 +1,135 @@ +var FormView = View.extend({ + + method: "post", + useMinotaur: false, + + events: { + "submit form": "save" + }, + + initialize: function(opt){ + if (opt && opt.parent) { + this.parent = opt.parent + } + this.$form = this.$("form") + this.$errors = this.$(".errors") + this.$errorList = this.$(".errorList") + }, + + reset: function(){ + this.$("input,textarea").not("[type='submit']").not("[type='hidden']").val("") + }, + + showErrors: function(errors){ + if (errors && errors.length) { + this.$errorList.empty(); + for (var i in errors) { + this.$errorList.append('
' + errors[i] + '
'); + } + this.$errors.css("opacity", 1.0); + setTimeout(function(){ + this.$errors.show().css("opacity", 1.0); + }.bind(this), 200) + } + }, + + serialize: function(){ + var fd = new FormData(), hasCSRF = false + + this.$("input[name], select[name], textarea[name]").each( function(){ + if (this.type == "file") { + if (this.files.length > 0) { + fd.append(this.name, this.files[0]); + } + } + else if (this.type == "password") { + if (this.value.length > 0) { + fd.append(this.name, SHA1.hex('lol$' + this.value + '$vvalls')) + } + } + else { + fd.append(this.name, this.value); + hasCSRF = hasCSRF || this.name == "_csrf" + } + }); + + if (! hasCSRF) { + fd.append("_csrf", $("[name=_csrf]").val()) + } + + return fd + }, + + save: function(e, successCallback, errorCallback){ + e && e.preventDefault() + + this.$errors.hide().css("opacity", 0.0); + + if (this.validate) { + var errors = this.validate() + if (errors && errors.length) { + if (errorCallback) { + errorCallback(errors) + } + else { + this.showErrors(errors) + } + return + } + } + + var action = typeof this.action == "function" ? this.action() : this.action + if (! action) return + + var request = $.ajax({ + url: action, + type: this.method, + data: this.serialize(), + dataType: "json", + processData: false, + contentType: false, + }) + + if (this.useMinotaur) { + Minotaur.show() + } + + request.done($.proxy(function (response) { + if (this.useMinotaur) { + Minotaur.hide() + } + if (response.error) { + var errors = [] + for (var key in response.error.errors) { + errors.push(response.error.errors[key].message); + } + if (errorCallback) { + errorCallback(errors) + } + else { + this.showErrors(errors) + } + return + } + else { + if (successCallback) { + successCallback(response) + } + if (this.success) { + this.success(response) + } + } + }, this)); + } + +}) + + +var ModalFormView = ModalView.extend(FormView.prototype).extend({ + + load: function(){ + this.reset() + this.show() + } + +}) diff --git a/public/js/view/router.js b/public/js/view/router.js new file mode 100644 index 0000000..28905b2 --- /dev/null +++ b/public/js/view/router.js @@ -0,0 +1,61 @@ +var Router = View.extend({ + + route: function(){ + + this.originalPath = window.location.pathname + + var routes = is_mobile ? this.mobileRoutes : this.routes, + pathname = window.location.pathname, + path = pathname.split("/"); + + for (var i = 0; i < path.length; i++) { + if (! path[i].length) { + path[i] = null + } + } + + if (pathname in routes) { + this[this.routes[pathname]](null) + return + } + + if (path[path.length-1] == null) { + path.pop() + } + + for (var route in routes) { + var routePath = route.split("/") + if (routePath[1] == path[1]) { + if (routePath[2] && routePath[2].indexOf(":") !== -1 && path[2] && (path[3] === routePath[3]) ) { + this[this.routes[route]](null, path[2]) + return + } + else if (routePath[2] == path[2]) { + if (routePath[3] && path[3]) { + if (routePath[3].indexOf(":") !== -1) { + this[this.routes[route]](null, path[3]) + return + } + else if (routePath[3] == path[3]) { + this[this.routes[route]](null) + return + } + } + else if (! routePath[3] && ! path[3]) { + this[this.routes[route]](null) + return + } + } + else if (! routePath[2] && (! path[2].length || ! path[2])) { + this[this.routes[route]](null) + return + } + } + } + + if (is_mobile) { + window.location.href = "/" + } + } + +}) diff --git a/public/js/view/view.js b/public/js/view/view.js new file mode 100644 index 0000000..87d6ee4 --- /dev/null +++ b/public/js/view/view.js @@ -0,0 +1,142 @@ +var View = (function($, _){ + + var View = function(options) { + this._id = _.uniqueId('view') + this.type = "view" + options || (options = {}); + _.extend(this, _.pick(options, viewOptions)) + this._ensureElement() + this.initialize.apply(this, arguments) + this.delegateEvents() + } + + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + _.extend(View.prototype, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + $: function(selector) { + return this.$el.find(selector); + }, + + initialize: function(){}, + + setElement: function(element, delegate) { + if (this.$el) this.undelegateEvents(); + this.$el = element instanceof $ ? element : $(element); + this.el = this.$el[0]; + if (delegate !== false) this.delegateEvents(); + return this; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save', + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + // This only works for delegate-able events: not `focus`, `blur`, and + // not `change`, `submit`, and `reset` in Internet Explorer. + delegateEvents: function(events) { + if (!(events || (events = _.result(this, 'events')))) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[events[key]]; + if (!method) continue; + + var match = key.match(delegateEventSplitter); + var eventName = match[1], selector = match[2]; + method = _.bind(method, this); + eventName += '.delegateEvents' + this._id; + if (is_mobile && (selector === 'mouseenter' || selector === 'mouseleave')) { + continue + } + else if (selector === '') { + this.$el.on(eventName, method); + } else { + this.$el.on(eventName, selector, method); + } + } + return this; + }, + + // Clears all callbacks previously bound to the view with `delegateEvents`. + undelegateEvents: function() { + this.$el.off('.delegateEvents' + this._id); + return this; + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + this.setElement(_.result(this, 'el'), false); + }, + + preventDefault: function(e){ + e && e.preventDefault() + }, + + stopPropagation: function(e){ + e && e.stopPropagation() + }, + + }); + + + var extend = function(protoProps, staticProps) { + var staticProps = staticProps || {} + var parent = this; + var child; + var childEvents = {}; + + // The constructor function for the new subclass is either defined by you + // (the "constructor" property in your `extend` definition), or defaulted + // by us to simply call the parent's constructor. + if (protoProps && _.has(protoProps, 'constructor')) { + child = protoProps.constructor; + } else { + child = function(){ return parent.apply(this, arguments); }; + } + + // Extend events so we can subclass views + _.extend(childEvents, parent.prototype.events, protoProps.events) + + // Add static properties to the constructor function, if supplied. + _.extend(child, parent, staticProps); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function. + var Surrogate = function(){ this.constructor = child; }; + Surrogate.prototype = parent.prototype; + child.prototype = new Surrogate; + + // Add prototype properties (instance properties) to the subclass, + // if supplied. + if (protoProps) _.extend(child.prototype, protoProps); + + // Set a convenience property in case the parent's prototype is needed + // later. + child.prototype.__super__ = parent.prototype; + child.prototype.events = childEvents + + return child; + }; + + View.extend = extend; + + return View; +})(Zepto, _) -- cgit v1.2.3-70-g09d2