summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/assets/img/logo.svg49
-rw-r--r--public/assets/javascripts/app.js7
-rw-r--r--public/assets/javascripts/defaults.js12
-rw-r--r--public/assets/javascripts/mx/extensions/mx.movements.js6
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/_rooms.js9
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/_walls.js134
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/builder.js5
-rw-r--r--public/assets/javascripts/rectangles/engine/rooms/grouper.js25
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/_scenery.js2
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/resize.js6
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/undo.js83
-rw-r--r--public/assets/javascripts/rectangles/models/room.js35
-rw-r--r--public/assets/javascripts/rectangles/models/wall.js186
-rw-r--r--public/assets/javascripts/rectangles/util/colors.js5
-rw-r--r--public/assets/javascripts/rectangles/util/minotaur.js2
-rw-r--r--public/assets/javascripts/rectangles/util/mouse.js291
-rw-r--r--public/assets/javascripts/rectangles/util/undostack.js13
-rw-r--r--public/assets/javascripts/ui/builder/BuilderSettings.js2
-rw-r--r--public/assets/javascripts/ui/builder/BuilderView.js1
-rw-r--r--public/assets/javascripts/ui/editor/Collaborators.js128
-rw-r--r--public/assets/javascripts/ui/editor/EditorSettings.js25
-rw-r--r--public/assets/javascripts/ui/editor/EditorToolbar.js5
-rw-r--r--public/assets/javascripts/ui/editor/EditorView.js3
-rw-r--r--public/assets/javascripts/ui/editor/LightControl.js299
-rw-r--r--public/assets/javascripts/ui/editor/MediaEditor.js11
-rw-r--r--public/assets/javascripts/ui/editor/MediaUpload.js73
-rw-r--r--public/assets/javascripts/ui/editor/MediaViewer.js1
-rw-r--r--public/assets/javascripts/ui/editor/WallpaperPicker.js164
-rw-r--r--public/assets/javascripts/ui/lib/FormView.js12
-rw-r--r--public/assets/javascripts/ui/lib/ModalView.js9
-rw-r--r--public/assets/javascripts/ui/lib/UploadView.js90
-rw-r--r--public/assets/javascripts/ui/site/SignUpModal.js1
-rw-r--r--public/assets/javascripts/util.js5
-rwxr-xr-xpublic/assets/stylesheets/app.css285
-rw-r--r--server/index.js13
-rw-r--r--server/lib/api/collaborator.js89
-rw-r--r--server/lib/api/index.js1
-rw-r--r--server/lib/api/media.js13
-rw-r--r--server/lib/api/projects.js3
-rw-r--r--server/lib/auth/mail.js24
-rw-r--r--server/lib/middleware.js29
-rw-r--r--server/lib/schemas/Collaborator.js30
-rw-r--r--server/lib/schemas/Media.js3
-rw-r--r--server/lib/schemas/Project.js1
-rw-r--r--server/lib/views.js34
-rw-r--r--views/controls/editor/collaborators.ejs61
-rw-r--r--views/controls/editor/light-control.ejs16
-rw-r--r--views/controls/editor/settings.ejs8
-rw-r--r--views/controls/editor/wallpaper.ejs12
-rw-r--r--views/controls/reader/about-room.ejs7
-rwxr-xr-xviews/editor.ejs1
-rw-r--r--views/mail/collaborator.html.ejs20
-rw-r--r--views/mail/collaborator.text.ejs7
-rw-r--r--views/mail/welcome.html.ejs2
-rw-r--r--views/mail/welcome.text.ejs2
-rw-r--r--views/partials/meta.ejs17
-rw-r--r--views/partials/scripts.ejs4
-rw-r--r--views/reader.ejs2
58 files changed, 1680 insertions, 703 deletions
diff --git a/public/assets/img/logo.svg b/public/assets/img/logo.svg
new file mode 100644
index 0000000..72b904a
--- /dev/null
+++ b/public/assets/img/logo.svg
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
+]>
+<svg version="1.1"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
+ x="0px" y="0px" width="117px" height="44px" viewBox="-0.896 -0.441 117 44"
+ overflow="visible" enable-background="new -0.896 -0.441 117 44" xml:space="preserve">
+<defs>
+</defs>
+<path d="M0.54,38.759c0-1.44,0.66-3.72,1.56-6.18c1.38-3.84,3.42-8.22,4.5-11.16c0.48-1.32,0.78-2.34,0.78-2.88
+ c0-0.72-0.24-1.02-0.72-1.02c-1.38,0-4.74,5.16-5.4,6.24c-0.36,0.6-0.48,0.78-0.78,0.78c-0.3,0-0.48-0.06-0.48-0.36
+ c0-0.18,0.18-0.84,0.66-1.62c0.9-1.44,2.22-3.6,3.9-5.28c1.44-1.5,3.12-2.64,4.74-2.64c1.38,0,1.92,1.32,1.92,2.7
+ c0,1.02-0.42,2.58-1.02,4.32c-1.26,3.72-3.359,8.521-4.68,12.18c-0.66,1.92-1.14,3.54-1.14,4.62c0,1.38,0.48,2.459,1.979,2.459
+ c6.48,0,13.56-14.639,13.56-17.759c0-1.2-0.3-1.92-0.6-2.58c-0.42-0.9-0.9-1.62-0.9-3.18c0-1.86,0.84-2.76,1.8-2.76
+ c0.9,0,1.98,1.08,1.98,3.66c0,9.179-9.6,24.66-17.58,24.66c-2.82,0-4.08-1.561-4.08-4.141V38.759z"/>
+<path d="M23.879,38.759c0-1.44,0.66-3.72,1.56-6.18c1.38-3.84,3.42-8.22,4.5-11.16c0.48-1.32,0.78-2.34,0.78-2.88
+ c0-0.72-0.24-1.02-0.72-1.02c-1.38,0-4.74,5.16-5.4,6.24c-0.36,0.6-0.48,0.78-0.78,0.78c-0.3,0-0.48-0.06-0.48-0.36
+ c0-0.18,0.18-0.84,0.66-1.62c0.9-1.44,2.22-3.6,3.9-5.28c1.44-1.5,3.12-2.64,4.74-2.64c1.38,0,1.92,1.32,1.92,2.7
+ c0,1.02-0.42,2.58-1.02,4.32c-1.26,3.72-3.36,8.521-4.68,12.18c-0.66,1.92-1.14,3.54-1.14,4.62c0,1.38,0.48,2.459,1.98,2.459
+ c6.479,0,13.559-14.639,13.559-17.759c0-1.2-0.3-1.92-0.6-2.58c-0.42-0.9-0.9-1.62-0.9-3.18c0-1.86,0.84-2.76,1.8-2.76
+ c0.9,0,1.979,1.08,1.979,3.66c0,9.179-9.6,24.66-17.579,24.66c-2.82,0-4.08-1.561-4.08-4.141V38.759z"/>
+<path d="M44.339,37.799c0-5.22,2.76-10.92,6.36-15.299c3.66-4.44,8.159-7.5,11.759-7.5c2.7,0,3.78,1.44,3.9,1.44
+ s0.48-0.96,0.66-1.44c0.18-0.48,0.3-0.6,1.02-0.6h1.74c0.359,0,0.6,0.06,0.6,0.42c0,0.18-0.119,0.6-0.24,0.9
+ c-1.919,5.82-3.839,11.64-5.819,17.519c-1.14,3.48-1.26,4.381-1.26,5.041c0,0.659,0.3,0.84,0.66,0.84c0.54,0,1.68-1.141,3.72-4.26
+ c1.5-2.28,1.26-2.94,1.86-2.94c0.3,0,0.42,0.181,0.42,0.479c0,0.84-1.38,3.541-3.24,6c-1.8,2.52-4.14,4.8-6.12,4.8
+ c-1.38,0-1.62-0.96-1.62-2.159c0-0.961,0.24-2.101,0.72-3.66c0.54-1.98,1.5-4.62,2.82-8.52l-0.12-0.12
+ c-2.16,3.3-9.36,14.459-14.1,14.459c-2.76,0-3.72-2.159-3.72-5.339V37.799z M65.338,18.839c0-1.8-1.08-2.76-2.76-2.76
+ c-3,0-6.6,3.72-9.48,8.22c-2.82,4.5-4.919,9.839-4.919,13.019c0,1.561,0.54,2.641,1.86,2.641c2.22,0,6.06-4.26,9.3-9
+ c3.3-4.74,6-10.02,6-12.06V18.839z"/>
+<path d="M72.178,39.839c0-1.08,0.479-3.479,1.2-6.479c1.74-7.02,4.979-17.52,6.6-23.64c0.66-2.46,1.08-4.2,1.08-4.8
+ c0-0.78-0.24-1.32-1.62-1.44c-1.14-0.12-1.38-0.3-1.38-0.78c0.061-0.42,0.779-0.66,1.8-0.66c2.58,0,4.2-0.78,5.16-1.38
+ c0.6-0.36,0.96-0.66,1.14-0.66c0.3,0,0.42,0.12,0.42,0.48c0,0.3-0.6,1.56-1.14,3.479c-5.04,17.759-7.38,25.799-8.46,29.698
+ c-0.84,3.061-0.96,3.84-0.96,4.681c0,0.6,0.3,1.02,0.78,1.02c0.84,0,1.619-0.659,3.479-3.72c1.08-1.74,1.92-4.199,2.46-4.199
+ c0.301,0,0.42,0.239,0.42,0.539c0,0.42-0.84,2.521-2.16,4.74c-1.739,3-4.319,6.24-6.719,6.24c-1.681,0-2.101-1.32-2.101-3.061
+ V39.839z"/>
+<path d="M85.497,39.839c0-1.08,0.48-3.479,1.2-6.479c1.739-7.02,4.979-17.52,6.6-23.64c0.66-2.46,1.08-4.2,1.08-4.8
+ c0-0.78-0.24-1.32-1.62-1.44c-1.14-0.12-1.38-0.3-1.38-0.78c0.06-0.42,0.78-0.66,1.8-0.66c2.58,0,4.2-0.78,5.16-1.38
+ c0.6-0.36,0.96-0.66,1.14-0.66c0.3,0,0.42,0.12,0.42,0.48c0,0.3-0.6,1.56-1.14,3.479c-5.04,17.759-7.38,25.799-8.46,29.698
+ c-0.84,3.061-0.96,3.84-0.96,4.681c0,0.6,0.301,1.02,0.78,1.02c0.84,0,1.62-0.659,3.479-3.72c1.08-1.74,1.92-4.199,2.46-4.199
+ c0.3,0,0.42,0.239,0.42,0.539c0,0.42-0.84,2.521-2.159,4.74c-1.74,3-4.32,6.24-6.721,6.24c-1.68,0-2.1-1.32-2.1-3.061V39.839z"/>
+<path d="M111.416,34.619c0,2.34-0.96,4.439-2.46,5.939c-1.68,1.68-4.02,2.64-6.479,2.64c-1.86,0-3.84-0.78-5.04-1.56
+ c-0.6-0.42-1.02-0.84-1.02-1.2c0-0.96,0.359-3.12,0.959-5.819c0.42-1.98,0.48-2.34,1.08-2.34c0.541,0,0.601,0.359,0.601,2.16
+ c0,3.84,1.2,7.319,4.92,7.319c2.34,0,4.2-2.46,4.2-4.74c0-2.819-1.561-4.979-3.24-6.96c-1.68-1.979-3.24-4.08-3.24-6.899
+ c0-4.38,3.24-8.76,7.859-8.76c3,0,5.82,1.38,5.82,2.1c0,1.14-0.301,2.4-0.66,3.9c-0.72,2.76-0.779,3.06-1.38,3.06
+ c-0.899,0-0.78-1.92-1.08-3.84c-0.3-1.86-1.08-3.78-3.6-3.78c-2.34,0-4.08,2.04-4.08,4.439s1.62,4.44,3.24,6.6
+ c1.8,2.34,3.6,4.68,3.6,7.68V34.619z"/>
+</svg>
diff --git a/public/assets/javascripts/app.js b/public/assets/javascripts/app.js
index ad3c601..1dd8a5e 100644
--- a/public/assets/javascripts/app.js
+++ b/public/assets/javascripts/app.js
@@ -98,11 +98,4 @@ app.position = function(obj){
return pos
}
-app.defaults = {
- viewHeight: window.viewHeight = 186,
- units: app.units = "ft",
- footResolution: 36,
- meterResolution: 100,
-}
-
document.addEventListener('DOMContentLoaded', app.init)
diff --git a/public/assets/javascripts/defaults.js b/public/assets/javascripts/defaults.js
new file mode 100644
index 0000000..5573073
--- /dev/null
+++ b/public/assets/javascripts/defaults.js
@@ -0,0 +1,12 @@
+app.defaults = {
+ viewHeight: window.viewHeight = 186,
+ units: app.units = "ft",
+ footResolution: 36,
+ meterResolution: 100,
+ wallOpacity: 0.95,
+ wallColor: [255,255,255],
+ outlineWidth: 2,
+ outlineColor: [0,0,0],
+ floorColor: [246,246,246],
+ ceilingColor: [255,255,255],
+}
diff --git a/public/assets/javascripts/mx/extensions/mx.movements.js b/public/assets/javascripts/mx/extensions/mx.movements.js
index 3b7d3e2..669a7f4 100644
--- a/public/assets/javascripts/mx/extensions/mx.movements.js
+++ b/public/assets/javascripts/mx/extensions/mx.movements.js
@@ -34,8 +34,10 @@ MX.Movements = function (cam) {
init: function () {
document.addEventListener('keydown', function (e) {
- // console.log(e.keyCode)
- if (locked) return;
+ // console.log(e.keyCode)
+ if (locked || e.altKey || e.metaKey || e.ctrlKey) {
+ return
+ }
switch ( e.keyCode ) {
case 16: // shift
diff --git a/public/assets/javascripts/rectangles/engine/rooms/_rooms.js b/public/assets/javascripts/rectangles/engine/rooms/_rooms.js
index 29dee41..5ed7be8 100644
--- a/public/assets/javascripts/rectangles/engine/rooms/_rooms.js
+++ b/public/assets/javascripts/rectangles/engine/rooms/_rooms.js
@@ -35,7 +35,6 @@
var base = this
base.list = {}
- base.walls = {}
base.regions = []
base.uid = new UidGenerator(base.list)
@@ -110,14 +109,6 @@
Rooms.clipper.update()
}
- base.serializeWalls = function(){
- return []
- }
-
- base.deserializeWalls = function(walls_data){
- return []
- }
-
base.sorted_by_position = function(){
return sort.rooms_by_position( base.values() )
}
diff --git a/public/assets/javascripts/rectangles/engine/rooms/_walls.js b/public/assets/javascripts/rectangles/engine/rooms/_walls.js
new file mode 100644
index 0000000..f2f395b
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/rooms/_walls.js
@@ -0,0 +1,134 @@
+(function(){
+
+ var vec2, Rect, Room, sort, UidGenerator, _
+ if ('window' in this) {
+ vec2 = window.vec2
+ Rect = window.Rect
+ Room = window.Room
+ sort = window.sort
+ UidGenerator = window.UidGenerator
+ _ = window._
+ }
+ else {
+ vec2 = require('../../models/vec2')
+ Rect = require('../../models/rect')
+ Room = require('../../models/room')
+ UidGenerator = require('../../util/uid')
+ sort = require('../../util/sort')
+ _ = require('lodash')
+ FRONT = 0x1, BACK = 0x2, LEFT = 0x4, RIGHT = 0x8, FLOOR = 0x10, CEILING = 0x20
+ TOP = CEILING, BOTTOM = FLOOR
+ function sidesToString(sides){
+ var s = ""
+ if (sides & FRONT) s += "front "
+ if (sides & BACK) s += "back "
+ if (sides & LEFT) s += "left "
+ if (sides & RIGHT) s += "right "
+ if (sides & TOP) s += "top "
+ if (sides & BOTTOM) s += "bottom "
+ return s
+ }
+ }
+
+ var Walls = new function(){
+
+ var base = this
+
+ base.list = []
+ base.lookup = {}
+ base.colors = {}
+
+ base.first = function(){
+ for (var i in base.list) {
+ if (base.list.hasOwnProperty(i)) {
+ return base.list[i]
+ }
+ }
+ }
+
+ base.assign = function(list){
+ base.list = list
+ base.lookup = {}
+ list.forEach(function(wall){
+ base.lookup[wall.id] = wall
+ })
+ }
+
+ base.bind = function(){
+ base.list.forEach(function(wall){
+ wall.bind()
+ })
+ }
+
+ base.count = function(){
+ return this.list.length
+ }
+
+ base.forEach = function(f){
+ return base.list.forEach(f)
+ }
+
+ base.map = function(f){
+ return base.list.map(f)
+ }
+
+ base.serialize = function(){
+ var data = []
+ base.list.forEach(function(wall){
+ data.push(wall.serialize())
+ })
+ return data
+ }
+
+ base.deserialize = function(walls_data){
+ walls_data.forEach(function(wall_data){
+ if (! wall_data) { return }
+ var wall = base.lookup[ wall_data.id ]
+ wall.deserialize( wall_data )
+ })
+ },
+
+ base.setColor = {
+
+ wall: function(rgb){
+ var rgbaColor = rgba_string(rgb, app.defaults.wallOpacity)
+ Walls.colors.wall = rgb
+ Walls.forEach(function(wall){
+ wall.outline(rgbaColor, null)
+ })
+ },
+
+ outline: function(rgb){
+ var rgbColor = rgb_string(rgb)
+ Walls.colors.outline = rgb
+ Walls.forEach(function(wall){
+ wall.outline(null, rgbColor)
+ })
+ },
+
+ floor: function(rgb){
+ var rgbColor = rgb_string(rgb)
+ Walls.colors.floor = rgb
+ Rooms.forEach(function(room){
+ room.setFloorColor(rgbColor)
+ })
+ },
+
+ ceiling: function(rgb){
+ var rgbColor = rgb_string(rgb)
+ Walls.colors.ceiling = rgb
+ Rooms.forEach(function(room){
+ room.setCeilingColor(rgbColor)
+ })
+ },
+
+ }
+ }
+
+ if ('window' in this) {
+ window.Walls = Walls
+ }
+ else {
+ module.exports = Walls
+ }
+})()
diff --git a/public/assets/javascripts/rectangles/engine/rooms/builder.js b/public/assets/javascripts/rectangles/engine/rooms/builder.js
index f321f71..f0935d4 100644
--- a/public/assets/javascripts/rectangles/engine/rooms/builder.js
+++ b/public/assets/javascripts/rectangles/engine/rooms/builder.js
@@ -165,7 +165,6 @@
room.mx_ceiling.push(el)
}
}.bind(this))
-
}
else {
// render floor and ceiling for the entire rectangle
@@ -282,8 +281,8 @@
el.side = CEILING
return el
}
-
this.make_wall = function (klass) {
+ // klass += ".backface-hidden"
var el = new MX.Object3D(".face" + (klass || ""))
el.width = el.height = el.scaleX = el.scaleY = el.scaleZ = 1
el.z = el.y = el.x = 0
@@ -293,7 +292,7 @@
el.side = 0
el.rect = null
el.destroy = function(){
- this.el = this.rect = this.face = null
+ this.el = this.rect = this.face = null
}
// possible if walls are opaque
diff --git a/public/assets/javascripts/rectangles/engine/rooms/grouper.js b/public/assets/javascripts/rectangles/engine/rooms/grouper.js
index cde9fbb..ba510e1 100644
--- a/public/assets/javascripts/rectangles/engine/rooms/grouper.js
+++ b/public/assets/javascripts/rectangles/engine/rooms/grouper.js
@@ -52,8 +52,8 @@
base.group(walls, collections, BACK)
base.group(walls, collections, LEFT)
base.group(walls, collections, RIGHT)
- Rooms.walls = walls
- base.bind()
+ Walls.assign( walls )
+ Walls.bind()
}
base.collect = function(){
var collections = {}
@@ -83,7 +83,10 @@
collection.sort( useX ? sort.compare_zx : sort.compare_xz )
collection.forEach(function(mx){
- if (last_mx && last_mx.rect.eq(mx.rect)) {
+ if (mx.culled) {
+ return
+ }
+ if (last_mx && mx && last_mx.rect.eq(mx.rect)) {
// culls half-walls
if (last_mx.rect.id == mx.rect.id) {
last_mx.height += mx.height/2
@@ -91,9 +94,11 @@
last_mx.face.y.b += mx.height/2
}
last_mx.side = side
- mx.culled = true
- mx.destroy()
- scene.remove(mx)
+ if (! mx.culled) {
+ scene.remove(mx)
+ mx.destroy()
+ mx.culled = true
+ }
return
}
widthVec = mx.rect[useX ? 'x' : 'y'].clone()
@@ -142,7 +147,6 @@
}
}
wall = new Wall ({
- id: base.uid(),
side: side,
mx: [ mx ],
surface: new Surface( mx.face ),
@@ -155,13 +159,6 @@
return walls
}
-
- base.bind = function(){
- Rooms.walls.forEach(function(wall){
- wall.bind()
- wall.randomize_colors()
- })
- }
}
diff --git a/public/assets/javascripts/rectangles/engine/scenery/_scenery.js b/public/assets/javascripts/rectangles/engine/scenery/_scenery.js
index 2fd6122..4cf5b06 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/_scenery.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/_scenery.js
@@ -84,7 +84,7 @@ var Scenery = new function(){
base.deserialize = function(scenery_data){
scenery_data.forEach(function(data){
- var wall = Rooms.walls[data.wall_id] || Rooms.walls[0]
+ var wall = Walls.list[data.wall_id] || Walls.first()
var scene_media = base.add({
data: data,
wall: wall,
diff --git a/public/assets/javascripts/rectangles/engine/scenery/resize.js b/public/assets/javascripts/rectangles/engine/scenery/resize.js
index d9cce18..e26c0a7 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/resize.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/resize.js
@@ -109,6 +109,7 @@ Scenery.resize = new function(){
}
base.hide = function () {
+ if (! obj) return
obj = null
dots.forEach(function(dot){
scene.remove(dot)
@@ -175,7 +176,8 @@ Scenery.resize = new function(){
mag = y_sign * mag * sign(height)
}
- obj.mx.scale = ( dimension.a + mag ) / naturalDimension.a // dimension.a // scale * (old_width + mag) / old_width
+ obj.set_scale( ( dimension.a + mag ) / naturalDimension.a )
+ // dimension.a // scale * (old_width + mag) / old_width
// console.log(scale, obj.mx.scale, dimension.a + mag, naturalDimension.a)
@@ -188,6 +190,8 @@ Scenery.resize = new function(){
}
base.move_dots()
+
+ app.router.editorView.mediaEditor.setDimensions()
}
function up (e, cursor){
diff --git a/public/assets/javascripts/rectangles/engine/scenery/undo.js b/public/assets/javascripts/rectangles/engine/scenery/undo.js
index 54ab755..e5624a0 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/undo.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/undo.js
@@ -1,7 +1,7 @@
(function(){
- UndoStack.register([
- {
- type: "create-scenery",
+ UndoStack.register([
+ {
+ type: "create-scenery",
undo: function(state){
Scenery.remove(state.id)
@@ -14,13 +14,13 @@
// TODO: watch individual scenery object here
Minotaur.watch( app.router.editorView.settings )
},
- },
- {
- type: "update-scenery",
+ },
+ {
+ type: "update-scenery",
undo: function(state){
var scenery = Scenery.find(state.id)
scenery.deserialize(state)
- scenery.set_wall(Rooms.walls[ state.wall_id ])
+ scenery.set_wall(Walls.find( state.wall_id ))
if (editor.permissions.resize) {
Scenery.resize.show(scenery)
@@ -32,7 +32,7 @@
redo: function(state){
var scenery = Scenery.find(state.id)
scenery.deserialize(state)
- scenery.set_wall(Rooms.walls[ state.wall_id ])
+ scenery.set_wall(Walls.find( state.wall_id ))
if (editor.permissions.resize) {
Scenery.resize.show(scenery)
@@ -43,9 +43,9 @@
// TODO: watch individual scenery object here
Minotaur.watch( app.router.editorView.settings )
},
- },
- {
- type: "destroy-scenery",
+ },
+ {
+ type: "destroy-scenery",
undo: function(state){
Scenery.deserialize([ state ])
@@ -58,12 +58,12 @@
// TODO: watch individual scenery object here
Minotaur.watch( app.router.editorView.settings )
},
- },
-
- //
-
- {
- type: "create-room",
+ },
+
+ //
+
+ {
+ type: "create-room",
undo: function(room){
Rooms.remove(room)
Rooms.clipper.update()
@@ -71,48 +71,59 @@
redo: function(room){
Rooms.add(new Room(room))
Rooms.clipper.update()
- app.tube("builder-pick-room", room)
+ app.tube("builder-pick-room", room)
},
- },
- {
- type: "update-room",
+ },
+ {
+ type: "update-room",
undo: function(state){
var room = Rooms.list[state.id]
room.rect.assign( state.rect )
room.height = state.height
Rooms.clipper.update()
- app.tube("builder-pick-room", room)
+ app.tube("builder-pick-room", room)
},
redo: function(state){
var room = Rooms.list[state.id]
room.rect.assign( state.rect )
room.height = state.height
Rooms.clipper.update()
- app.tube("builder-pick-room", room)
+ app.tube("builder-pick-room", room)
},
- },
- {
- type: "destroy-room",
+ },
+ {
+ type: "destroy-room",
undo: function(room){
Rooms.add(new Room(room))
Rooms.clipper.update()
- app.tube("builder-pick-room", room)
+ app.tube("builder-pick-room", room)
},
redo: function(room){
Rooms.remove(room)
Rooms.clipper.update()
},
- },
-
- //
+ },
+
+ //
- {
- type: "update-wallpaper",
+ {
+ type: "update-wallpaper",
undo: function(state){
+ var wall = Walls.lookup[state.id]
+ wall.deserialize(state)
+
+ Minotaur.watch( app.router.editorView.settings )
},
- redo: function(state){
+ },
+ {
+ type: "update-colors",
+ undo: function(state){
+ Walls.setColor[ state.mode ]( state.rgb )
+ app.router.editorView.lightControl.setSwatchColor( state.mode, state.rgb )
+
+ Minotaur.watch( app.router.editorView.settings )
},
- },
-
- ])
+ },
+
+ ])
})()
diff --git a/public/assets/javascripts/rectangles/models/room.js b/public/assets/javascripts/rectangles/models/room.js
index 33a94d0..0f09325 100644
--- a/public/assets/javascripts/rectangles/models/room.js
+++ b/public/assets/javascripts/rectangles/models/room.js
@@ -32,9 +32,6 @@
this.id = opt.id || Rooms.uid("room_")
this.rect = opt.rect
this.regions = []
- this.walls = []
- this.floor = []
- this.ceiling = []
this.height = opt.height || 200
this.focused = false
@@ -69,30 +66,10 @@
this.intersects = []
this.constructed = false
- this.walls = []
-
this.mx_walls = []
this.mx_floor = []
this.mx_ceiling = []
}
-
- Room.prototype.bind = function(){
- var base = this
- base.mx_walls.forEach(function(wall){
- $(wall.el).bind({
- mouseover: function(){
- },
- mousemove: function(e){
- var color = choice(window.palettes.colors)
- base.mx_walls.forEach(function(wall){
- $(wall.el).css("background-color", color)
- })
- },
- mousedown: function(){
- }
- })
- })
- }
Room.prototype.clipTo = function(r){
// for each of this rect's regions split the region if necessary
@@ -182,6 +159,18 @@
return collision
}
+ Room.prototype.setFloorColor = function(rgbColor) {
+ this.mx_floor.map(function(mx){
+ mx.el.style.backgroundColor = rgbColor
+ })
+ }
+
+ Room.prototype.setCeilingColor = function(rgbColor) {
+ this.mx_ceiling.map(function(mx){
+ mx.el.style.backgroundColor = rgbColor
+ })
+ }
+
if ('window' in this) {
window.Room = Room
}
diff --git a/public/assets/javascripts/rectangles/models/wall.js b/public/assets/javascripts/rectangles/models/wall.js
index 8723c3c..6043181 100644
--- a/public/assets/javascripts/rectangles/models/wall.js
+++ b/public/assets/javascripts/rectangles/models/wall.js
@@ -13,12 +13,13 @@
}
var Wall = function(opt){
- this.id = opt.id
+ this.id = [ opt.side, opt.edge, opt.vec.a ].join("_")
this.vec = opt.vec
this.edge = opt.edge
this.side = opt.side
this.surface = opt.surface
this.mx = opt.mx
+ this.background = ""
}
Wall.prototype.toString = function(){
@@ -63,26 +64,49 @@
e.stopPropagation()
return
}
-
+
UndoStack.push({
type: 'create-scenery',
undo: { id: scenery.id },
redo: scenery.serialize(),
})
-
- // TODO: watch individual scenery object here
- Minotaur.watch( app.router.editorView.settings )
}
else if (Scenery.nextWallpaper) {
- base.wallpaper()
- }
+ var oldState = base.serialize()
+ base.wallpaper(Scenery.nextWallpaper)
+ Scenery.nextWallpaper = null
+
+ UndoStack.push({
+ type: 'update-wallpaper',
+ undo: oldState,
+ redo: base.serialize(),
+ })
+ }
else {
app.controller.hideExtras()
}
}
})
})
- this.outline()
+
+ // flip the mx order
+ var shouldFlip = this.side & (LEFT | BACK)
+ if (! shouldFlip) {
+ this.mx.reverse()
+ }
+
+ // this.outline(wallColor, outlineColor)
+ }
+
+ Wall.prototype.serialize = function(){
+ return {
+ id: this.id,
+ background: this.background,
+ }
+ }
+
+ Wall.prototype.deserialize = function(data){
+ this.wallpaper( data.background )
}
@@ -140,143 +164,63 @@
position.a -= dimension.a / 2
position.b -= dimension.b / 2
}
-// if (mx.width) { position.a -= mx.width / 2 }
-// if (mx.height) { position.b -= mx.height / 2 }
return position
}
- Wall.prototype.bounds_for = function(img, scale) {
- scale = scale || 1
- var coord = this.side & FRONT_BACK ? this.rect.x : this.rect.y
- var halfWidth = img.width/2 * scale
- var halfHeight = img.height/2 * scale
-
- return new Rect( new vec2( coord.a + halfWidth, coord.b - halfWidth ),
- new vec2( halfHeight, Rooms.list[this.room].height - halfHeight ) )
- }
-
- Wall.prototype.fits = function(img, scale){
- if (this.side & FRONT_BACK && this.rect.x.length() < img.width * scale) {
- return false
- }
- if (this.side & LEFT_RIGHT && this.rect.y.length() < img.width * scale) {
- return false
- }
- return true
- }
-
- Wall.prototype.center = function(offset){
-
- switch (this.side) {
- case FRONT:
- x = this.vec.midpoint()
- z = this.edge + painting_distance_from_wall
- break
- case BACK:
- x = this.vec.midpoint()
- z = this.edge - painting_distance_from_wall
- break
- case LEFT:
- x = this.edge + painting_distance_from_wall
- z = this.vec.midpoint()
- break
- case RIGHT:
- x = this.edge - painting_distance_from_wall
- z = this.vec.midpoint()
- break
- }
-
- return new vec2 (x, z)
- }
-
Wall.prototype.color = function(color){
- this.$walls && this.$walls.css("background-color", color)
+ this.$walls.css("background-color", color)
}
- Wall.prototype.wallpaper = function(){
+ Wall.prototype.wallpaper = function(background){
var useX = this.side & FRONT_BACK
var shouldFlip = this.side & (LEFT | BACK)
+
+ this.background = background || "none"
+
this.mx.forEach(function(mx){
var partitionOffset = useX ? mx.x : mx.z
if (shouldFlip) partitionOffset *= -1
partitionOffset += mx.width/2
var floorOffset = mx.y + mx.height/2
- mx.el.style.backgroundImage = Scenery.nextWallpaper
+ mx.el.style.backgroundImage = background
mx.el.style.backgroundPosition = (~~partitionOffset) + "px " + (~~floorOffset) + "px"
})
}
- Wall.prototype.outline = function(){
+ Wall.prototype.outline = function(wallColor, outlineColor){
var useX = this.side & FRONT_BACK
- var shouldFlip = this.side & (LEFT | BACK)
var mx = this.mx
- if (! shouldFlip) {
- mx = mx.reverse()
- }
-
var len = this.mx.length
-
- var backgroundColor = "rgba(255,255,255,0.95)"
- var borderColor = "rgba(0,0,0,1.0)"
-
- zz = window.zz || 0
+ var outlineString = app.defaults.outlineWidth + "px solid " + outlineColor
+ zz = 0
mx.forEach(function(mx, i){
- if (mx.outlined) return
- mx.outlined = true
- mx.el.style.backgroundColor = backgroundColor
-
- // all walls get bottom lines
- mx.el.style.borderBottom = "1px solid " + borderColor
-
- // all walls get top lines
- mx.el.style.borderTop = "1px solid " + borderColor
-
- // walls on initial sides get left lines
- // if their left edge lines up with the rect edge
- if (i == 0) {
- mx.el.style.borderLeft = "1px solid " + borderColor
- }
-
- // walls on terminal sides get right lines....
- // if their right edge lines up with the rect edge
- if (i == len-1) {
- mx.el.style.borderRight = "1px solid " + borderColor
- }
- })
- }
-
- Wall.prototype.siblings = function(){
- return this
-// var base = this
-// var match = base.side | base.half_side
-// var walls = Rooms.list[this.room].walls.filter(function(w){
-// return (w.side | w.half_side) & match && w.$walls
-// })
-// return walls
- }
-
- Wall.prototype.randomize_colors = function(){
- var color = window.grayColors[ this.side | this.half_side ]
- // this.color(color)
- }
-
- Wall.prototype.stroke_colors = function(){
- var color = "#fff"
- var siblings = this.siblings()
- siblings.forEach(function(w, i){
- if (! w.$walls) return
- w.color(color)
- if (i == 0) {
- w.$walls.css("border-left", "1px solid #000")
- }
- if (i == siblings.length-1) {
- w.$walls.css("border-right", "1px solid #000")
- }
- w.$walls.css("border-top", "1px solid #000")
- w.$walls.css("border-bottom", "1px solid #000")
+ // if (mx.outlined) return
+ // mx.outlined = true
+ if (wallColor) {
+ mx.el.style.backgroundColor = wallColor
+ }
+ if (outlineColor) {
+ // all walls get bottom lines
+ mx.el.style.borderBottom = outlineString
+
+ // all walls get top lines
+ mx.el.style.borderTop = outlineString
+
+ // walls on initial sides get left lines
+ // if their left edge lines up with the rect edge
+ if (i == 0) {
+ mx.el.style.borderLeft = outlineString
+ }
+
+ // walls on terminal sides get right lines....
+ // if their right edge lines up with the rect edge
+ if (i == len-1) {
+ mx.el.style.borderRight = outlineString
+ }
+ }
})
}
diff --git a/public/assets/javascripts/rectangles/util/colors.js b/public/assets/javascripts/rectangles/util/colors.js
index 16d34dd..4ad96fc 100644
--- a/public/assets/javascripts/rectangles/util/colors.js
+++ b/public/assets/javascripts/rectangles/util/colors.js
@@ -50,11 +50,6 @@
var select = document.querySelector("#palette")
select && select.addEventListener("change", function(){
colors = color_palettes[select.value]
- Rooms.forEach(function(room){
- room.walls.forEach(function(wall){
- wall.randomize_colors()
- })
- })
select.blur()
})
diff --git a/public/assets/javascripts/rectangles/util/minotaur.js b/public/assets/javascripts/rectangles/util/minotaur.js
index 039a053..e6a37e0 100644
--- a/public/assets/javascripts/rectangles/util/minotaur.js
+++ b/public/assets/javascripts/rectangles/util/minotaur.js
@@ -4,7 +4,7 @@
var base = this
base.$el = $("#minotaur")
base.timeout = null
- base.delay = 500
+ base.delay = 1000
base.objects = {}
base.init = function () {
diff --git a/public/assets/javascripts/rectangles/util/mouse.js b/public/assets/javascripts/rectangles/util/mouse.js
index 34d3f5e..cb36038 100644
--- a/public/assets/javascripts/rectangles/util/mouse.js
+++ b/public/assets/javascripts/rectangles/util/mouse.js
@@ -1,65 +1,66 @@
/*
- usage:
-
- base.mouse = new mouse({
- el: document.querySelector("#map"),
- down: function(e, cursor){
- // do something with val
- // cursor.x.a
- // cursor.y.a
- },
- move: function(e, cursor){
- // delta.a (x)
- // delta.b (y)
- },
- up: function(e, cursor, new_cursor){
- // cursor.x.a
- // cursor.y.a
- },
- })
+ usage:
+
+ base.mouse = new mouse({
+ el: document.querySelector("#map"),
+ down: function(e, cursor){
+ // do something with val
+ // cursor.x.a
+ // cursor.y.a
+ },
+ move: function(e, cursor){
+ // var delta = cursor.delta()
+ // delta.a (x)
+ // delta.b (y)
+ },
+ up: function(e, cursor, new_cursor){
+ // cursor.x.a
+ // cursor.y.a
+ },
+ })
*/
function mouse (opt) {
- var base = this
+ var base = this
- opt = defaults(opt, {
- el: null,
- down: null,
- move: null,
- drag: null,
- enter: null,
- up: null,
- rightclick: null,
- propagate: false,
- locked: false,
- use_offset: true,
- val: 0,
- })
-
- base.down = false
+ opt = defaults(opt, {
+ el: null,
+ down: null,
+ move: null,
+ drag: null,
+ enter: null,
+ up: null,
+ rightclick: null,
+ propagate: false,
+ locked: false,
+ use_offset: true,
+ val: 0,
+ })
+
+ base.down = false
- base.creating = false
- base.dragging = false
+ base.creating = false
+ base.dragging = false
- base.cursor = new Rect(0,0,0,0)
+ base.cursor = new Rect(0,0,0,0)
- base.tube = new Tube ()
- opt.down && base.tube.on("down", opt.down)
- opt.move && base.tube.on("move", opt.move)
- opt.drag && base.tube.on("drag", opt.drag)
- opt.enter && base.tube.on("enter", opt.enter)
- opt.leave && base.tube.on("leave", opt.leave)
- opt.up && base.tube.on("up", opt.up)
- opt.rightclick && base.tube.on("rightclick", opt.rightclick)
-
- var offset = (opt.use_offset && opt.el) ? opt.el.getBoundingClientRect() : null
-
- base.init = function (){
- base.bind()
- }
+ base.tube = new Tube ()
+ opt.down && base.tube.on("down", opt.down)
+ opt.move && base.tube.on("move", opt.move)
+ opt.drag && base.tube.on("drag", opt.drag)
+ opt.enter && base.tube.on("enter", opt.enter)
+ opt.leave && base.tube.on("leave", opt.leave)
+ opt.up && base.tube.on("up", opt.up)
+ opt.rightclick && base.tube.on("rightclick", opt.rightclick)
+
+ var offset = (opt.use_offset && opt.el) ? opt.el.getBoundingClientRect() : null
+
+ base.init = function (){
+ base.bind()
+ }
- base.on = function(){
+ base.on = function(){
base.tube.on.apply(base.tube, arguments)
}
@@ -67,104 +68,104 @@ function mouse (opt) {
base.tube.off.apply(base.tube, arguments)
}
- base.bind = function(){
- if (opt.el) {
- opt.el.addEventListener("mousedown", base.mousedown)
- opt.el.addEventListener("contextmenu", base.contextmenu)
- }
- window.addEventListener("mousemove", base.mousemove)
- window.addEventListener("mouseup", base.mouseup)
- }
+ base.bind = function(){
+ if (opt.el) {
+ opt.el.addEventListener("mousedown", base.mousedown)
+ opt.el.addEventListener("contextmenu", base.contextmenu)
+ }
+ window.addEventListener("mousemove", base.mousemove)
+ window.addEventListener("mouseup", base.mouseup)
+ }
- base.bind_el = function(el){
- el.addEventListener("mousedown", base.mousedown)
- el.addEventListener("mousemove", base.mousemove)
- }
- base.unbind_el = function(el){
- el.removeEventListener("mousedown", base.mousedown)
- el.removeEventListener("mousemove", base.mousemove)
- }
+ base.bind_el = function(el){
+ el.addEventListener("mousedown", base.mousedown)
+ el.addEventListener("mousemove", base.mousemove)
+ }
+ base.unbind_el = function(el){
+ el.removeEventListener("mousedown", base.mousedown)
+ el.removeEventListener("mousemove", base.mousemove)
+ }
- function positionFromMouse(e) {
- if (offset) {
- return new vec2(offset.left - e.pageX, e.pageY - offset.top)
- }
- else {
- return new vec2(e.pageX, e.pageY)
- }
- }
-
- base.mousedown = function(e){
- if (opt.use_offset) {
- offset = this.getBoundingClientRect()
- }
-
- var pos = positionFromMouse(e)
-
- var x = pos.a, y = pos.b
- base.cursor = new Rect (x,y, x,y)
- base.down = true
- e.clickAccepted = true
-
- base.tube("down", e, base.cursor)
+ function positionFromMouse(e) {
+ if (offset) {
+ return new vec2(offset.left - e.pageX, e.pageY - offset.top)
+ }
+ else {
+ return new vec2(e.pageX, e.pageY)
+ }
+ }
+
+ base.mousedown = function(e){
+ if (opt.use_offset) {
+ offset = this.getBoundingClientRect()
+ }
+
+ var pos = positionFromMouse(e)
+
+ var x = pos.a, y = pos.b
+ base.cursor = new Rect (x,y, x,y)
+ base.down = true
+ e.clickAccepted = true
+
+ base.tube("down", e, base.cursor)
- if (e.clickAccepted) {
- e.stopPropagation()
- }
- else {
- base.down = false
- }
- }
- base.mousemove = function(e){
- if (opt.use_offset && ! offset) return
-
- var pos = positionFromMouse(e)
+ if (e.clickAccepted) {
+ e.stopPropagation()
+ }
+ else {
+ base.down = false
+ }
+ }
+ base.mousemove = function(e){
+ if (opt.use_offset && ! offset) return
+
+ var pos = positionFromMouse(e)
- if (e.shiftKey) {
- pos.quantize(10)
- }
+ if (e.shiftKey) {
+ pos.quantize(10)
+ }
- var x = pos.a, y = pos.b
-
- if (base.down) {
- base.cursor.x.b = x
- base.cursor.y.b = y
- base.tube("drag", e, base.cursor)
- e.stopPropagation()
- }
- else {
- base.cursor.x.a = base.cursor.x.b = x
- base.cursor.y.a = base.cursor.y.b = y
- base.tube("move", e, base.cursor)
- }
- }
- base.mouseenter = function(e, target, index){
- if (! base.down) return
- if (opt.use_offset && ! offset) return
- base.tube("enter", e, target, base.cursor)
- }
- base.mouseleave = function(e, target){
- if (! base.down) return
- if (opt.use_offset && ! offset) return
- base.tube("leave", e, target, base.cursor)
- }
- base.mouseup = function(e){
- var pos, new_cursor
-
- if (base.down) {
- e.stopPropagation()
- base.down = false
- pos = positionFromMouse(e)
- new_cursor = new Rect (pos.a, pos.b)
- base.tube("up", e, base.cursor, new_cursor)
- base.cursor = new_cursor
- }
- }
- base.contextmenu = function(e){
- e.preventDefault()
- base.tube("rightclick", e, base.cursor)
- }
+ var x = pos.a, y = pos.b
+
+ if (base.down) {
+ base.cursor.x.b = x
+ base.cursor.y.b = y
+ base.tube("drag", e, base.cursor)
+ e.stopPropagation()
+ }
+ else {
+ base.cursor.x.a = base.cursor.x.b = x
+ base.cursor.y.a = base.cursor.y.b = y
+ base.tube("move", e, base.cursor)
+ }
+ }
+ base.mouseenter = function(e, target, index){
+ if (! base.down) return
+ if (opt.use_offset && ! offset) return
+ base.tube("enter", e, target, base.cursor)
+ }
+ base.mouseleave = function(e, target){
+ if (! base.down) return
+ if (opt.use_offset && ! offset) return
+ base.tube("leave", e, target, base.cursor)
+ }
+ base.mouseup = function(e){
+ var pos, new_cursor
+
+ if (base.down) {
+ e.stopPropagation()
+ base.down = false
+ pos = positionFromMouse(e)
+ new_cursor = new Rect (pos.a, pos.b)
+ base.tube("up", e, base.cursor, new_cursor)
+ base.cursor = new_cursor
+ }
+ }
+ base.contextmenu = function(e){
+ e.preventDefault()
+ base.tube("rightclick", e, base.cursor)
+ }
- base.init()
+ base.init()
}
diff --git a/public/assets/javascripts/rectangles/util/undostack.js b/public/assets/javascripts/rectangles/util/undostack.js
index b93c79e..959e3d1 100644
--- a/public/assets/javascripts/rectangles/util/undostack.js
+++ b/public/assets/javascripts/rectangles/util/undostack.js
@@ -31,16 +31,17 @@
this.types[ action.type ].redo(action.redo)
return this.pointer < this.stack.length-1
}
- UndoStack.prototype.register = function(actionType){
- if (actionType.length) {
- actionType.forEach(this.registerOne.bind(this))
+ UndoStack.prototype.register = function(actions){
+ if (actions.length) {
+ actions.forEach(this.registerOne.bind(this))
}
else {
- this.registerOne(actionType)
+ this.registerOne(actions)
}
}
- UndoStack.prototype.registerOne = function(actionType){
- this.types[ actionType.type ] = actionType
+ UndoStack.prototype.registerOne = function(action){
+ if (! action.redo) { action.redo = action.undo }
+ this.types[ action.type ] = action
}
if ('window' in this) {
window.UndoStack = new UndoStack
diff --git a/public/assets/javascripts/ui/builder/BuilderSettings.js b/public/assets/javascripts/ui/builder/BuilderSettings.js
index 0091454..796c398 100644
--- a/public/assets/javascripts/ui/builder/BuilderSettings.js
+++ b/public/assets/javascripts/ui/builder/BuilderSettings.js
@@ -29,6 +29,8 @@ var BuilderSettings = FormView.extend({
this.$id.val(data._id)
this.$name.val(data.name)
+ this.parent.lightControl.loadDefaults()
+
data.rooms && Rooms.deserialize(data.rooms)
data.startPosition && scene.camera.move(data.startPosition)
data.privacy && this.$privacy.find("[value=" + data.privacy + "]").prop('checked', "checked")
diff --git a/public/assets/javascripts/ui/builder/BuilderView.js b/public/assets/javascripts/ui/builder/BuilderView.js
index 555cd58..a89111f 100644
--- a/public/assets/javascripts/ui/builder/BuilderView.js
+++ b/public/assets/javascripts/ui/builder/BuilderView.js
@@ -11,6 +11,7 @@ var BuilderView = View.extend({
this.info = new BuilderInfo ({ parent: this })
this.toolbar = new BuilderToolbar ({ parent: this })
this.settings = new BuilderSettings ({ parent: this })
+ this.lightControl = new LightControl ({ parent: this })
},
load: function(name){
diff --git a/public/assets/javascripts/ui/editor/Collaborators.js b/public/assets/javascripts/ui/editor/Collaborators.js
new file mode 100644
index 0000000..452ad15
--- /dev/null
+++ b/public/assets/javascripts/ui/editor/Collaborators.js
@@ -0,0 +1,128 @@
+
+var Collaborators = ModalFormView.extend({
+ el: ".mediaDrawer.collaborators",
+
+ template: $("#collaborator-template").html(),
+
+ indexAction: function(){ return "/api/collaborator/" + this.parent.data.slug + "/index" },
+ createAction: function(){ return "/api/collaborator/" + this.parent.data.slug + "/create" },
+ destroyAction: function(){ return "/api/collaborator/" + this.parent.data.slug + "/destroy" },
+
+ events: {
+ "keydown [name=email]": "enterSubmit",
+ "click [data-role=destroy-collaborator]": "destroy",
+ },
+
+ show: function(){
+ this.action = this.createAction
+ this.$("#collaborator-url-rapper").hide()
+
+ if (! this.loaded) {
+ this.load()
+ }
+ else {
+ this.__super__.show.call(this)
+ }
+ },
+
+ load: function(){
+ $.get(this.indexAction(), this.populate.bind(this))
+ },
+
+ populate: function(collaborators){
+ this.loaded = true
+ collaborators.forEach(function(collab){
+ var $el = $( this.template )
+ $el.data("collaborator-id", collab._id)
+
+ if (collab.user) {
+ $el.find(".email").remove()
+
+ $el.find(".user")
+ .attr("href", "/profile/" + collab.user.username)
+
+ $el.find(".avatar")
+ .css("background-image", "url(" + collab.user.photo + ")")
+
+ $el.find(".username")
+ .html( collab.user.displayName )
+
+ if (collab.owner) {
+ $el.find("button").remove()
+ }
+ else {
+ $el.find(".role").remove()
+ }
+ }
+ else {
+ $el.find(".user").remove()
+ $el.find(".role").remove()
+ $el.find(".email").html( collab.email )
+ }
+
+ $("#collaborator-list").append($el)
+ }.bind(this))
+
+ this.__super__.show.call(this)
+ },
+
+ enterSubmit: function (e) {
+ e.stopPropagation()
+ var base = this
+ if (e.keyCode == 13) {
+ setTimeout(function(){
+ base.save(e)
+ base.reset()
+ }, 100)
+ }
+ },
+
+ validate: function(){
+ var errors = []
+
+ var email = this.$("[name=email]").val()
+ if (! email.length) {
+ errors.push("Please enter an email address");
+ }
+ else if (email.indexOf("@") === -1) {
+ errors.push("Please enter a valid email address");
+ }
+
+ return errors
+ },
+
+ success: function(data){
+ this.reset()
+ this.populate([data])
+ // weird! this.$("#collaborator-url") not working here, but works without this
+ setTimeout(function(){
+ $("#collaborator-url").val("http://vvalls.com/join/" + data.nonce)
+ }, 100)
+ this.$("#collaborator-dummy-email").html(data.email)
+ this.$("#collaborator-url-rapper").slideDown(300)
+ },
+
+ destroy: function(e){
+ var base = this
+ var $el = $(e.currentTarget).closest("li")
+
+ if ($el.find(".user").length) {
+ var name = $el.find(".username").html()
+ ConfirmModal.confirm("Are you sure you want to remove " + name + " from this project?", _destroy)
+ }
+ else {
+ _destroy()
+ }
+
+ function _destroy () {
+ var _id = $el.data("collaborator-id")
+ $el.remove()
+ $.ajax({
+ type: "DELETE",
+ url: base.destroyAction(),
+ data: { _id: _id, _csrf: $("[name=_csrf]").val() },
+ })
+ }
+ },
+
+})
diff --git a/public/assets/javascripts/ui/editor/EditorSettings.js b/public/assets/javascripts/ui/editor/EditorSettings.js
index e9239e4..2a3929a 100644
--- a/public/assets/javascripts/ui/editor/EditorSettings.js
+++ b/public/assets/javascripts/ui/editor/EditorSettings.js
@@ -9,6 +9,7 @@ var EditorSettings = FormView.extend({
events: {
"keydown": 'stopPropagation',
"keydown [name=name]": 'enterSubmit',
+ "click [data-role='show-collaborators']": 'showCollaborators',
"click [data-role='save-project']": 'save',
"click [data-role='clone-project']": 'clone',
"click [data-role='clear-project']": 'clear',
@@ -28,15 +29,23 @@ var EditorSettings = FormView.extend({
load: function(data){
this.action = data.isNew ? this.createAction : this.updateAction
-
+ this.parent.data = data
+
data.rooms && Rooms.deserialize(data.rooms)
+ data.walls && Walls.deserialize(data.walls)
data.startPosition && scene.camera.move(data.startPosition)
-
+
+ if (data.colors && data.colors.wall) {
+ this.parent.lightControl.load(data.colors)
+ }
+ else {
+ this.parent.lightControl.loadDefaults()
+ }
+
if (data.isNew) {
this.$name.val( "Room " + moment().format("DD/MM/YYYY ha") )
}
else {
- // console.log(data)
this.thumbnailIsStale()
this.$id.val( data._id )
@@ -48,6 +57,11 @@ var EditorSettings = FormView.extend({
}
},
+ showCollaborators: function(e){
+ e && e.preventDefault()
+ this.parent.collaborators.show()
+ },
+
clone: function(){
var names = this.$name.val().split(" ")
if ( ! isNaN(Number( names[names.length-1] )) ) {
@@ -122,7 +136,8 @@ var EditorSettings = FormView.extend({
fd.append( "description", this.$description.val() )
fd.append( "privacy", this.$privacy.filter(":checked").val() == "private" )
fd.append( "rooms", JSON.stringify( Rooms.serialize() ) )
- fd.append( "walls", JSON.stringify( Rooms.serializeWalls() ) )
+ fd.append( "walls", JSON.stringify( Walls.serialize() ) )
+ fd.append( "colors", JSON.stringify( Walls.colors ) )
fd.append( "media", JSON.stringify( Scenery.serialize() ) )
fd.append( "startPosition", JSON.stringify( app.position(scene.camera) ) )
@@ -152,6 +167,8 @@ var EditorSettings = FormView.extend({
Minotaur.hide()
window.history.pushState(null, document.title, "/project/" + data.slug + "/edit")
+
+ this.parent.data = data
},
})
diff --git a/public/assets/javascripts/ui/editor/EditorToolbar.js b/public/assets/javascripts/ui/editor/EditorToolbar.js
index 5e0da7e..c631317 100644
--- a/public/assets/javascripts/ui/editor/EditorToolbar.js
+++ b/public/assets/javascripts/ui/editor/EditorToolbar.js
@@ -56,6 +56,11 @@ var EditorToolbar = View.extend({
$(".inuse").removeClass("inuse")
$("[data-role='resize-media']").toggleClass("inuse", state)
if (state) {
+ if (this.parent.mediaEditor.scenery) {
+ Scenery.resize.show( this.parent.mediaEditor.scenery )
+ }
+ }
+ else {
Scenery.resize.hide()
}
},
diff --git a/public/assets/javascripts/ui/editor/EditorView.js b/public/assets/javascripts/ui/editor/EditorView.js
index 4067c4d..e11f189 100644
--- a/public/assets/javascripts/ui/editor/EditorView.js
+++ b/public/assets/javascripts/ui/editor/EditorView.js
@@ -16,6 +16,7 @@ var EditorView = View.extend({
this.mediaEditor = new MediaEditor ({ parent: this })
this.wallpaperPicker = new WallpaperPicker ({ parent: this })
this.lightControl = new LightControl ({ parent: this })
+ this.collaborators = new Collaborators ({ parent: this })
},
load: function(name){
@@ -30,7 +31,7 @@ var EditorView = View.extend({
ready: function(data){
$("#map").hide()
-
+
this.settings.load(data)
},
diff --git a/public/assets/javascripts/ui/editor/LightControl.js b/public/assets/javascripts/ui/editor/LightControl.js
index c3e80c2..bd09dc2 100644
--- a/public/assets/javascripts/ui/editor/LightControl.js
+++ b/public/assets/javascripts/ui/editor/LightControl.js
@@ -1,43 +1,272 @@
var LightControl = View.extend({
- el: ".lightcontrol",
-
- events: {
- "mousedown": "stopPropagation",
- },
+ el: ".lightcontrol",
+
+ events: {
+ "mousedown": "stopPropagation",
+ "click .swatch": "clickSwatch",
+ "click label": "clickLabel",
+ "input #shadow-control": "updateShadow",
+ "mousedown #brightness-control": "beginBrightness",
+ "input #brightness-control": "updateBrightness",
+ "input #outline-hue": "updateShadow",
+ "input #wall-hue": "updateShadow",
+ },
+
+ initialize: function(){
+ this.colorPicker = new LabColorPicker(this, 180, 180)
+ this.$el.prepend( this.colorPicker.canvas )
+
+ this.$swatches = this.$(".swatch")
+ this.$labels = this.$(".swatch + label")
+ this.$swatch = {
+ wall: this.$("#wall-color"),
+ outline: this.$("#outline-color"),
+ floor: this.$("#floor-color"),
+ ceiling: this.$("#ceiling-color"),
+ }
+ this.$brightnessControl = this.$("#brightness-control")
+ },
- toggle: function(state){
+ modes: [ "wall", "outline", "floor", "ceiling" ],
+
+ load: function(data){
+ this.modes.forEach(function(mode){
+ Walls.setColor[mode](data[mode])
+ this.$swatch[ mode ].css("background-color", rgb_string(data[mode]))
+ }.bind(this))
+ this.setMode("wall")
+ },
+
+ loadDefaults: function(){
+ var colors = {
+ wall: app.defaults.wallColor.slice(),
+ outline: app.defaults.outlineColor.slice(),
+ floor: app.defaults.floorColor.slice(),
+ ceiling: app.defaults.ceilingColor.slice(),
+ }
+ this.load(colors)
+ },
+
+ toggle: function(state){
this.$el.toggleClass("active", state);
+ },
+
+ show: function(){
+ this.toggle(true)
+ },
+
+ hide: function(){
+ this.toggle(false)
+ },
+
+ pick: function(rgb, Lab){
+ this.labColor = Lab
+ this.setSwatchColor(this.mode, rgb)
+ Walls.setColor[ this.mode ](rgb)
+ },
+
+ setSwatchColor: function(mode, rgb) {
+ this.$swatch[ mode ].css("background-color", rgb_string(rgb))
+ },
+
+ initialState: null,
+
+ begin: function(){
+ this.initialState = this.serialize()
+ },
+
+ serialize: function(){
+ return {
+ mode: this.mode,
+ rgb: Walls.colors[ this.mode ]
+ }
+ },
+
+ finalize: function(){
+ if (! this.initialState) { return }
+ UndoStack.push({
+ type: 'update-colors',
+ undo: this.initialState,
+ redo: this.serialize(),
+ })
+ this.initialState = null
+ },
+
+ setMode: function (mode) {
+ var color, brightness
+ this.mode = mode
+ this.$swatches.removeClass("selected")
+ this.$labels.removeClass("selected")
+ this.$swatch[ mode ].addClass("selected")
+ color = Walls.colors[ mode ]
+
+ this.$(".swatch.selected").next("label").addClass("selected")
+ this.labColor = this.colorPicker.load(color)
+ this.$brightnessControl.val( this.labColor[0] )
+ },
+
+ clickLabel: function(e){
+ $(e.currentTarget).prev(".swatch").trigger("click")
+ },
+ clickSwatch: function(e){
+ var mode = $(e.currentTarget).data('mode')
+ this.setMode(mode)
+ },
+
+ beginBrightness: function(){
+ this.begin()
+ $(window).one("mouseup", this.finalize.bind(this))
+ },
+
+ updateBrightness: function(){
+ this.labColor[0] = parseFloat( this.$brightnessControl.val() )
+ var rgb = this.colorPicker.setLab( this.labColor )
+ this.pick(rgb, this.labColor)
+ },
- // toggle the class that makes the cursor a paintbucket
- // $("body").removeClass("pastePaper");
- },
- show: function(){
- this.toggle(true)
- },
- hide: function(){
- this.toggle(false)
- },
+})
-/*
- $("#shadow-control").on({
- mousedown: function(){ app.dragging = true },
- change: function(){
- var hex = (~~($(this).int() / 100 * 0xff)).toString(10)
- if (hex.length == 1) hex = "0" + hex;
- var color = "rgba(" + [hex, hex, hex, "1.0"] + ")"
- $(".face").css("border-color", color)
- }
- })
+var LabColorPicker = function (parent, w, h) {
+ var base = this
+ var canvas = this.canvas = document.createElement('canvas')
+ var ctx = this.ctx = canvas.getContext('2d')
+ var imageData = ctx.createImageData(w,h)
+ var data = imageData.data
- $("#brightness-control").on({
- mousedown: function(){ app.dragging = true },
- change: function(){
- var hex = (~~($(this).int() / 100 * 0xff)).toString(10)
- var color = "rgba(" + [hex, hex, hex, "0.9"] + ")"
- $("body,.face").css("background-color", color)
- }
- })
-*/
+// var cursor = this.cursor = document.createElement("div")
+// cursor.className = "colorPickerCursor"
-})
+ canvas.width = w
+ canvas.height = h
+ canvas.className = "colorPicker"
+
+ var ww = w-1
+ var hh = h-1
+
+ var L_range = [0, 110]
+ var a_range = [-86.185, 98.254]
+ var b_range = [-107.863, 94.482]
+
+ var rgb = [0,0,0]
+
+ var val = 80
+
+ this.mouse = new mouse({
+ el: canvas,
+ down: function(e, cursor){
+ parent.begin()
+ cursor.x.a = -cursor.x.a
+ base.pick(cursor.x.a, cursor.y.a)
+ },
+ drag: function(e, cursor){
+ cursor.x.b = -cursor.x.b
+ base.pick(cursor.x.b, cursor.y.b)
+ },
+ up: function(){
+ parent.finalize()
+ }
+ })
+
+ this.setLab = function(Lab) {
+ val = Lab[0]
+ this.paint()
+ var rgb = xyz2rgb(hunterlab2xyz(Lab[0], Lab[1], Lab[2])).map(Math.round)
+ return rgb
+ }
+ this.pick = function(i, j){
+ var x = mix( i/ww, a_range[0], a_range[1] )
+ var y = mix( j/hh, b_range[0], b_range[1] )
+ var rgb = xyz2rgb(hunterlab2xyz(val, x, y)).map(Math.round)
+ parent.pick( rgb, [val,x,y] )
+ }
+ this.load = function(rgba){
+ var Lab = xyz2hunterlab(rgb2xyz(rgba))
+ var val = clamp( Lab[0], L_range[0], L_range[1] )
+ var x = mix( norm(Lab[1], a_range[0], a_range[1]), 0, ww )
+ var y = mix( norm(Lab[2], b_range[0], b_range[1]), 0, hh )
+ // move the cursor
+ this.setLab(Lab)
+ return Lab
+ }
+ this.paint = function() {
+ val = clamp(val, L_range[0], L_range[1])
+ var x, y, t
+ for (var i = 0; i < w; i++) {
+ for (var j = 0; j < h; j++) {
+ x = mix( i/ww, a_range[0], a_range[1] )
+ y = mix( j/hh, b_range[0], b_range[1] )
+ t = (j*w + i) * 4
+ rgb = xyz2rgb(hunterlab2xyz(val, x, y))
+ data[t] = Math.round( rgb[0] )
+ data[t+1] = Math.round( rgb[1] )
+ data[t+2] = Math.round( rgb[2] )
+ data[t+3] = 255
+ }
+ }
+ ctx.putImageData(imageData,0,0)
+ }
+
+ function hunterlab2xyz (L,a,b) {
+ var_Y = L / 10
+ var_X = a / 17.5 * L / 10
+ var_Z = b / 7 * L / 10
+
+ Y = Math.pow(var_Y, 2)
+ X = ( var_X + Y ) / 1.02
+ Z = -( var_Z - Y ) / 0.847
+ xyz = [X,Y,Z]
+ }
+ function xyz2rgb(){
+ var var_X = xyz[0] / 100 //X from 0 to 95.047 (Observer = 2°, Illuminant = D65)
+ var var_Y = xyz[1] / 100 //Y from 0 to 100.000
+ var var_Z = xyz[2] / 100 //Z from 0 to 108.883
+
+ var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986
+ var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415
+ var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570
+
+ if ( var_R > 0.0031308 ) var_R = 1.055 * Math.pow( var_R, 1 / 2.4 ) - 0.055
+ else var_R = 12.92 * var_R
+ if ( var_G > 0.0031308 ) var_G = 1.055 * Math.pow( var_G, 1 / 2.4 ) - 0.055
+ else var_G = 12.92 * var_G
+ if ( var_B > 0.0031308 ) var_B = 1.055 * Math.pow( var_B, 1 / 2.4 ) - 0.055
+ else var_B = 12.92 * var_B
+
+ rgb[0] = clamp(var_R * 255, 0, 255)
+ rgb[1] = clamp(var_G * 255, 0, 255)
+ rgb[2] = clamp(var_B * 255, 0, 255)
+ return rgb
+ }
+ function rgb2xyz(RGB){
+ var var_R = ( RGB[0] / 255 ) // R from 0 to 255
+ var var_G = ( RGB[1] / 255 ) // G from 0 to 255
+ var var_B = ( RGB[2] / 255 ) // B from 0 to 255
+
+ if ( var_R > 0.04045 ) var_R = ( ( var_R + 0.055 ) / 1.055 ) ^ 2.4
+ else var_R = var_R / 12.92
+ if ( var_G > 0.04045 ) var_G = ( ( var_G + 0.055 ) / 1.055 ) ^ 2.4
+ else var_G = var_G / 12.92
+ if ( var_B > 0.04045 ) var_B = ( ( var_B + 0.055 ) / 1.055 ) ^ 2.4
+ else var_B = var_B / 12.92
+
+ var_R = var_R * 100
+ var_G = var_G * 100
+ var_B = var_B * 100
+
+ //Observer. = 2°, Illuminant = D65
+ var x = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805
+ var y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722
+ var z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505
+ return [x,y,z]
+ }
+ function xyz2hunterlab (XYZ) {
+ var X = XYZ[0]
+ var Y = XYZ[1] || 1e-6 // otherwise divide-by-zero error when converting rgb(0,0,0)
+ var Z = XYZ[2]
+ var L = 10 * sqrt( Y )
+ var a = 17.5 * ( ( ( 1.02 * X ) - Y ) / sqrt( Y ) )
+ var b = 7 * ( ( Y - ( 0.847 * Z ) ) / sqrt( Y ) )
+ return [L,a,b]
+ }
+}
diff --git a/public/assets/javascripts/ui/editor/MediaEditor.js b/public/assets/javascripts/ui/editor/MediaEditor.js
index cc924da..750cf41 100644
--- a/public/assets/javascripts/ui/editor/MediaEditor.js
+++ b/public/assets/javascripts/ui/editor/MediaEditor.js
@@ -116,17 +116,18 @@ var MediaEditor = FormView.extend({
},
setDimensions: function(){
- this.$width.unitVal( Number(this.scenery.dimensions.a * this.scenery.scale) || "" )
- this.$height.unitVal( Number(this.scenery.dimensions.b * this.scenery.scale) || "" )
+ if (! this.scenery) return
+ this.$width.unitVal( Number(this.scenery.naturalDimensions.a * this.scenery.scale) || "" )
+ this.$height.unitVal( Number(this.scenery.naturalDimensions.b * this.scenery.scale) || "" )
},
changeWidth: function(e){
e.stopPropagation()
- this.scenery.set_scale( this.$width.unitVal() / this.scenery.dimensions.a )
+ this.scenery.set_scale( this.$width.unitVal() / this.scenery.naturalDimensions.a )
this.setDimensions()
},
changeHeight: function(e){
e.stopPropagation()
- this.scenery.set_scale( this.$height.unitVal() / this.scenery.dimensions.b )
+ this.scenery.set_scale( this.$height.unitVal() / this.scenery.naturalDimensions.b )
this.setDimensions()
},
changeUnits: function(){
@@ -137,11 +138,13 @@ var MediaEditor = FormView.extend({
bind: function(scenery){
this.scenery = scenery
this.scenery.mx.bound = true
+ this.scenery.mx.el.classList.add("picked")
},
unbind: function(){
if (this.scenery && this.scenery.mx) {
this.scenery.mx.bound = false
+ this.scenery.mx.el.classList.remove("picked")
}
this.scenery = null
},
diff --git a/public/assets/javascripts/ui/editor/MediaUpload.js b/public/assets/javascripts/ui/editor/MediaUpload.js
index 86bf767..92cf2bd 100644
--- a/public/assets/javascripts/ui/editor/MediaUpload.js
+++ b/public/assets/javascripts/ui/editor/MediaUpload.js
@@ -1,5 +1,5 @@
-var MediaUpload = View.extend({
+var MediaUpload = UploadView.extend({
el: ".fileUpload",
createAction: "/api/media/new",
@@ -7,16 +7,12 @@ var MediaUpload = View.extend({
events: {
"keydown .url": "enterSubmit",
- "change .file": "handleFileSelect",
- "submit form": "preventDefault",
},
initialize: function(opt){
+ this.__super__.initialize.call(this)
this.parent = opt.parent
- this.$csrf = this.$("[name=_csrf]")
this.$url = this.$(".url")
- this.$file = this.$(".file")
- this.$upload = this.$(".upload-icon")
},
show: function(){
@@ -45,7 +41,7 @@ var MediaUpload = View.extend({
return
}
- media._csrf = this.$csrf.val()
+ media._csrf = $("[name=_csrf]").val()
console.log(media)
var request = $.ajax({
@@ -57,68 +53,13 @@ var MediaUpload = View.extend({
}.bind(this))
},
- handleFileSelect: function(e) {
- e.stopPropagation();
- e.preventDefault();
-
- this.parent.mediaViewer.deleteArmed(false)
-
- var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
-
- for (var i = 0, f; f = files[i]; i++) {
- if ( ! f.type.match('image.*')) {
- continue;
- }
-
- this.getImageDimensions(f)
- }
- },
-
- getImageDimensions: function(f){
- var base = this
-
- this.$upload.addClass('uploading')
-
- var reader = new FileReader();
-
- reader.onload = function(e) {
- var image = new Image()
- image.onload = function(){
- var width = image.naturalWidth,
- height = image.naturalHeight
- base.upload(f, width, height)
- }
- image.src = e.target.result
- }
-
- reader.readAsDataURL(f);
- },
-
- upload: function(f, width, height){
- var fd = new FormData()
- fd.append('image', f)
- fd.append('width', width)
- fd.append('height', height)
- fd.append('_csrf', this.$csrf.val())
-
- var request = $.ajax({
- url: this.uploadAction,
- type: "post",
- data: fd,
- dataType: "json",
- processData: false,
- contentType: false,
- })
- request.done(this.add.bind(this))
- },
-
add: function(media){
console.log(media)
- if (media.error) {
- return
- }
- this.$upload.removeClass('uploading')
this.parent.mediaViewer.add(media)
+ },
+
+ beforeUpload: function(){
+ this.parent.mediaViewer.deleteArmed(false)
}
})
diff --git a/public/assets/javascripts/ui/editor/MediaViewer.js b/public/assets/javascripts/ui/editor/MediaViewer.js
index 40bfe80..7cfa863 100644
--- a/public/assets/javascripts/ui/editor/MediaViewer.js
+++ b/public/assets/javascripts/ui/editor/MediaViewer.js
@@ -2,6 +2,7 @@
var MediaViewer = ModalView.extend({
el: ".mediaDrawer.mediaViewer",
destroyAction: "/api/media/destroy",
+ usesFileUpload: true,
events: {
'click .foundToggle': "foundToggle",
diff --git a/public/assets/javascripts/ui/editor/WallpaperPicker.js b/public/assets/javascripts/ui/editor/WallpaperPicker.js
index 9ee441b..61ecb78 100644
--- a/public/assets/javascripts/ui/editor/WallpaperPicker.js
+++ b/public/assets/javascripts/ui/editor/WallpaperPicker.js
@@ -1,40 +1,70 @@
-var WallpaperPicker = View.extend({
+var WallpaperPicker = UploadView.extend({
el: ".wallpaper",
+ mediaTag: "wallpaper",
+ uploadAction: "/api/media/upload",
+
events: {
"click .swatch": 'pick',
- },
+ },
initialize: function(){
- var wm = new WallpaperManager()
- app.on('wallpaper-ready', function(){
- var black = [0,0,0,1.0]
- var white = [255,255,255,1.0]
- var swatches = wm.buildSwatches(black, white, 2)
- swatches.forEach(function(swatch){
- var dataUrl = swatch.toDataURL()
- var span = document.createElement('span')
- span.style.backgroundImage = "url(" + dataUrl + ")"
- span.className = "swatch"
- this.$el.append(span)
- }.bind(this))
- }.bind(this))
- wm.init()
+ this.__super__.initialize.call(this)
+ this.$swatches = this.$(".swatches")
+ },
+
+ loaded: false,
+ show: function(){
+ if (! this.loaded) {
+ this.load()
+ }
+ else {
+ this.toggle(true)
+ }
+ },
+
+ hide: function(){
+ this.__super__.hide.call(this)
},
- toggle: function(state){
- this.$el.toggleClass("active", state);
- // toggle the class that makes the cursor a paintbucket
- // $("body").removeClass("pastePaper");
+ load: function(){
+ $.get("/api/media/user", { tag: this.mediaTag }, this.populate.bind(this))
},
- show: function(){
- this.toggle(true)
+
+ populate: function(data){
+ console.log(data)
+ this.loaded = true
+ data && data.forEach(this.add.bind(this))
+ this.toggle(true)
+ },
+
+ add: function (media) {
+ if (media.type !== "image") { return }
+ var swatch = document.createElement("div")
+ swatch.className = "swatch"
+ swatch.style.backgroundImage = "url(" + media.url + ")"
+ this.$swatches.append(swatch)
+ },
+
+ toggle: function (state) {
+ if (state && ! this.loaded) {
+ this.show()
+ }
+ else {
+ this.$el.toggleClass("active", state)
+ }
+ // toggle the class that makes the cursor a paintbucket
+ // $("body").removeClass("pastePaper")
},
+
hide: function(){
this.toggle(false)
},
+ beforeUpload: function(){
+ },
+
pick: function(e){
var $swatch = $(e.currentTarget)
var $floatingSwatch = $(".floatingSwatch")
@@ -58,94 +88,6 @@ var WallpaperPicker = View.extend({
$floatingSwatch.show()
_followCursor(e);
})
- }
+ },
})
-
-// pattern
-// scale
-// foreground
-// background
-
-var WallpaperManager = function () {
-
- var image = new Image ()
- var imageData
- var w, h
-
- this.masks = []
-
- this.init = function(){
- this.load()
- }
-
- this.load = function(){
- image.onload = function(){
- this.loadImageData()
- this.buildMasks()
- app.tube('wallpaper-ready')
- }.bind(this)
-
- image.src = "/assets/img/palette.gif"
- }
-
- this.loadImageData = function(){
- var canvas = document.createElement('canvas')
- var ctx = canvas.getContext('2d')
- w = canvas.width = image.naturalWidth
- h = canvas.height = image.naturalHeight
- ctx.drawImage(image, 0,0)
- imageData = ctx.getImageData(0,0,image.naturalWidth,image.naturalHeight).data
- }
-
- this.buildMasks = function(){
- var mask
- for (var y = 0; y < 6; y++) {
- for (var x = 0; x < 16; x++) {
- mask = this.buildMask(x,y)
- this.masks.push(mask)
- }
- }
- }
-
- this.buildMask = function(x,y){
- // add the offset of the top-left swatch
- x = (x * 18) + 15
- y = (y * 16) + 5
-
- var mask = new Array(64)
- var t = 0
- for (var i = 0; i < 8; i++) {
- for (var j = 0; j < 8; j++) {
- t = ( w*(y+j) + x+i ) * 4
- mask[j*8+i] = imageData[t] === 0
- }
- }
- return mask
- }
-
- this.buildSwatches = function(black, white, scale) {
- var swatches = this.masks.map(function(mask){
- return this.buildSwatch(mask,black,white,scale)
- }.bind(this))
-
- return swatches
- }
-
- this.buildSwatch = function(mask,black,white,scale){
- black = 'rgba(' + black.join(',') + ')'
- white = 'rgba(' + white.join(',') + ')'
- var canvas = document.createElement("canvas")
- canvas.width = 8*scale
- canvas.height = 8*scale
- var ctx = canvas.getContext('2d')
- for (var i = 0; i < 8; i++) {
- for (var j = 0; j < 8; j++) {
- ctx.fillStyle = mask[j*8+i] ? black : white
- ctx.fillRect(i*scale, j*scale, scale, scale)
- }
- }
- return canvas
- }
-
-} \ No newline at end of file
diff --git a/public/assets/javascripts/ui/lib/FormView.js b/public/assets/javascripts/ui/lib/FormView.js
index ab33bc0..17b748a 100644
--- a/public/assets/javascripts/ui/lib/FormView.js
+++ b/public/assets/javascripts/ui/lib/FormView.js
@@ -33,7 +33,7 @@ var FormView = View.extend({
},
serialize: function(){
- var fd = new FormData()
+ var fd = new FormData(), hasCSRF = false
this.$("input[name], select[name], textarea[name]").each( function(){
if (this.type == "file") {
@@ -48,9 +48,14 @@ var FormView = View.extend({
}
else {
fd.append(this.name, this.value);
+ hasCSRF = hasCSRF || this.name == "_csrf"
}
});
+ if (! hasCSRF) {
+ fd.append("_csrf", $("[name=_csrf]").val())
+ }
+
return fd
},
@@ -71,9 +76,12 @@ var FormView = View.extend({
return
}
}
+
+ var action = typeof this.action == "function" ? this.action() : this.action
+ if (! action) return
var request = $.ajax({
- url: this.action,
+ url: action,
type: this.method,
data: this.serialize(),
dataType: "json",
diff --git a/public/assets/javascripts/ui/lib/ModalView.js b/public/assets/javascripts/ui/lib/ModalView.js
index 937c1e9..d9b518a 100644
--- a/public/assets/javascripts/ui/lib/ModalView.js
+++ b/public/assets/javascripts/ui/lib/ModalView.js
@@ -10,9 +10,15 @@ var ModalView = View.extend({
}
},
+ usesFileUpload: false,
+
show: function(){
$(".mediaDrawer").removeClass("active")
- $(".fileUpload").removeClass("active")
+
+ if (! this.usesFileUpload) {
+ $(".fileUpload").removeClass("active")
+ }
+
this.$el.addClass("active")
$("body").addClass("noOverflow")
},
@@ -32,4 +38,5 @@ var ModalView = View.extend({
this.hide()
}
}
+
})
diff --git a/public/assets/javascripts/ui/lib/UploadView.js b/public/assets/javascripts/ui/lib/UploadView.js
new file mode 100644
index 0000000..efaa8c9
--- /dev/null
+++ b/public/assets/javascripts/ui/lib/UploadView.js
@@ -0,0 +1,90 @@
+
+var UploadView = View.extend({
+
+ // define uploadAction
+
+ events: {
+ "change .file": "handleFileSelect",
+ "submit form": "preventDefault",
+ },
+
+ initialize: function(){
+ this.$file = this.$(".file")
+ this.$upload = this.$(".upload-icon")
+ },
+
+ beforeUpload: function(){
+ },
+
+ handleFileSelect: function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ this.beforeUpload()
+
+ var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
+
+ for (var i = 0, f; f = files[i]; i++) {
+ if ( ! f.type.match('image.*')) {
+ continue;
+ }
+
+ this.getImageDimensions(f)
+ }
+ },
+
+ getImageDimensions: function(f){
+ var base = this
+
+ this.$upload.addClass('uploading')
+
+ var reader = new FileReader();
+
+ reader.onload = function(e) {
+ var image = new Image()
+ image.onload = function(){
+ var width = image.naturalWidth,
+ height = image.naturalHeight
+ base.upload(f, width, height)
+ }
+ image.src = e.target.result
+ }
+
+ reader.readAsDataURL(f);
+ },
+
+ upload: function(f, width, height){
+ var fd = new FormData()
+ fd.append('image', f)
+ fd.append('width', width)
+ fd.append('height', height)
+ fd.append('_csrf', $("[name=_csrf]").val())
+
+ if (this.mediaTag) {
+ fd.append('tag', this.mediaTag)
+ }
+
+ var request = $.ajax({
+ url: this.uploadAction,
+ type: "post",
+ data: fd,
+ dataType: "json",
+ processData: false,
+ contentType: false,
+ })
+ request.done(this.success.bind(this))
+ },
+
+ success: function(media){
+ if (media.error) {
+ return
+ }
+ this.$upload.removeClass('uploading')
+ this.add(media)
+ },
+
+ add: function(media){
+ console.log(media)
+ },
+
+})
diff --git a/public/assets/javascripts/ui/site/SignUpModal.js b/public/assets/javascripts/ui/site/SignUpModal.js
index 5c651ee..a452023 100644
--- a/public/assets/javascripts/ui/site/SignUpModal.js
+++ b/public/assets/javascripts/ui/site/SignUpModal.js
@@ -34,4 +34,3 @@ var SignUpModal = ModalFormView.extend({
}
})
-
diff --git a/public/assets/javascripts/util.js b/public/assets/javascripts/util.js
index b92dcf3..7812a4d 100644
--- a/public/assets/javascripts/util.js
+++ b/public/assets/javascripts/util.js
@@ -12,7 +12,10 @@ function sanitize (s){ return (s || "").replace(new RegExp("[<>&]", 'g'), "") }
function capitalize (s){ return s.split(" ").map(capitalizeWord).join(" ") }
function capitalizeWord (s){ return s.charAt(0).toUpperCase() + s.slice(1) }
function slugify (s){ return (s || "").toLowerCase().replace(/\s/g,"-").replace(/[^-_a-zA-Z0-9]/g, '-').replace(/-+/g,"-") }
-
+function rgb_string (rgb) { return "rgb(" + rgb.map(Math.round).join(",") + ")" }
+function rgba_string (rgb,a) { return "rgba(" + rgb.map(Math.round).join(",") + "," + a + ")" }
+function hex_string (rgb) { return "#" + rgb.map(Math.round).map(function(n){ var s = n.toString(16); return s.length == 1 ? "0"+s : s }).join("") }
+function parse_rgba_string (s) { return s.match(/(\d+)/g).slice(0,3) }
var E = Math.E
var PI = Math.PI
diff --git a/public/assets/stylesheets/app.css b/public/assets/stylesheets/app.css
index 1c48eee..c2e7119 100755
--- a/public/assets/stylesheets/app.css
+++ b/public/assets/stylesheets/app.css
@@ -544,6 +544,13 @@ iframe.embed {
.mx-scenery {
cursor: pointer;
}
+.editing .mx-scenery:hover,
+.editing .mx-scenery.picked {
+ border: 1px dashed #000;
+ -moz-box-sizing: content-box;
+ -webkit-box-sizing: content-box;
+ box-sizing: content-box;
+}
.mx-scenery:active {
cursor: pointer;
}
@@ -629,6 +636,9 @@ iframe.embed {
background-size: 100% 100%;
}
+
+/* AUTOSAVE MONITOR */
+
#minotaur {
position: absolute;
top: 26px;
@@ -648,6 +658,7 @@ iframe.embed {
content: 'SAVING';
}
+
.rapper {
position:relative;
}
@@ -757,13 +768,13 @@ iframe.embed {
content:"show map";
}
.fixed {
- position:fixed;
- top:0;
- left:0;
- width:100%;
- height:100%;
- z-index:3;
- overflow-y:scroll;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 3;
+ overflow-y: scroll;
}
.fixed::-webkit-scrollbar {
@@ -783,7 +794,7 @@ iframe.embed {
}
.fixed::-moz-scrollbar-track {
- background:white;
+ background: white;
}
.fixed::-moz-scrollbar-thumb{
@@ -1085,14 +1096,17 @@ iframe.embed {
transition:opacity 0.3s ease-in-out;
}
-.wallpaper{
+
+/* WALLPAPER PICKER */
+
+.wallpaper {
right: 80px;
margin-top: 77px;
width: 162px;
z-index: 1;
-webkit-transition: -webkit-transform 0.1s ease-in-out;
-webkit-transform: translateX(400px);
- transition: -webkit-transform 0.1s ease-in-out;
+ transition: transform 0.1s ease-in-out;
transform: translateX(400px);
}
.wallpaper.active {
@@ -1100,48 +1114,73 @@ iframe.embed {
-webkit-transform: translateX(0px);
transform: translateX(0px);
}
-.wallpaper.active span {
+.wallpaper.active .swatches .swatch {
width: 40px;
- height: 35px;
+ height: 40px;
display: inline-block;
- float: left;
- border:1px solid;
- background: url(../img/MacPaint.gif);
+ border: 1px solid;
+ background-size: contain;
-webkit-transition: -webkit-transform 0.1s ease-in-out;
+ line-height: 0;
+ vertical-align: text-bottom;
-webkit-user-drag: element;
}
-
-.wallpaper.active span:nth-child(1){
- background-position:103px 70px;
+.wallpaper.active .swatches .swatch:hover {
+ cursor: pointer;
+ -webkit-transform: translateX(3px) translateY(-3px);
+ transform: translateX(3px) translateY(-3px);
}
-.wallpaper.active span:nth-child(2){
- background-position:200px -98px;
+.wallpaper .swatches {
+ width: 100%;
+ border-bottom: 1px solid black;
+ min-height: 30px;
}
-.wallpaper.active span:nth-child(3){
- background-position:200px -260px;
+.wallpaperUpload {
+ font-size: 14px;
+ font-weight: 300;
}
-.wallpaper.active span:nth-child(4){
- background-position:200px -350px;
+.wallpaperUpload label {
+ position: relative;
+ top: -6px;
+ float: none;
}
-.wallpaper.active span:nth-child(5){
- background-position:200px -484px;
+.wallpaperUpload .icon-ios7-upload-outline {
+ font-size: 26px;
}
-.wallpaper.active span:nth-child(6){
- background-position:200px -581px;
+.wallpaperUpload .upload-icon.uploading {
}
-.wallpaper.active span:nth-child(7){
- background-position:200px -645px;
+.wallpaperUpload .upload-icon.uploading:before {
+ content: ' ' !important;
+ background-image: url("/assets/img/loader.gif");
+ background-repeat: no-repeat;
+ width: 40px;
+ height: 40px;
}
-.wallpaper.active span:nth-child(8){
- background-position:200px -772px;
+.wallpaperUpload input[type="text"]{
+ border: 1px solid #ccc;
+ font-size: 15px;
+ padding: 5px;
+ width: 100px;
+ text-align: center;
+ border-radius: 20px;
}
-
-.wallpaper.active span:hover {
- cursor: pointer;
- -webkit-transform: translateX(3px) translateY(-3px);
- transform: translateX(3px) translateY(-3px);
+.wallpaperUpload input[type="text"]:focus{
+ border: 1px solid #000;
+}
+.wallpaperUpload input[type="file"]{
+ position: absolute;
+ margin-left: -134px;
+ background: blue;
+ height: 28px;
+ width: 100%;
+ margin-top: 0px;
+ opacity: 0;
+ cursor:pointer;
}
+
+/* COLOR PICKER */
+
.lightcontrol {
margin-top: 13%;
right: 80px;
@@ -1151,35 +1190,61 @@ iframe.embed {
transform: translateX(400px);
transition: -webkit-transform 0.2s ease-in-out;
}
-
.lightcontrol.active {
-webkit-transform: translateX(0px);
transform: translateX(0px);
}
-
-.lightcontrol .slider {
-
+.lightcontrol .slider {
}
h4 {
font-weight:300;
font-size:11px;
}
input[type=range] {
- -webkit-appearance: none;
- -moz-appearance: none;
- background-color: black;
- width: 180px;
- height:3px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background-color: black;
+ width: 180px;
+ height:3px;
}
-
input[type="range"]::-webkit-slider-thumb {
- -webkit-appearance: none;
- background-color: #000;
- width: 10px;
- height: 10px;
- border-radius:10px;
- cursor:pointer;
+ -webkit-appearance: none;
+ background-color: #000;
+ width: 10px;
+ height: 10px;
+ border-radius:10px;
+ cursor:pointer;
}
+.colorPicker {
+ cursor: crosshair;
+}
+.swatch {
+ width: 20px;
+ height: 20px;
+ border: 1px solid black;
+ display: inline-block;
+ cursor: pointer;
+}
+.swatch.selected {
+ border-width: 2px;
+}
+.color-swatches {
+ margin-top: 10px;
+}
+.color-swatches label {
+ font-size: 11px;
+ font-weight: 300;
+ position: relative;
+ top: -6px;
+ padding-left: 5px;
+ display: inline-block;
+ min-width: 35px;
+ cursor: pointer;
+}
+.color-swatches label.selected {
+ font-weight: 500;
+}
+
.settings.info {
right: auto;
@@ -1202,13 +1267,13 @@ input[type="range"]::-webkit-slider-thumb {
transform: translateY(0px);
}
-#startpoint {
+.modalLink {
text-decoration: none;
}
-
-#startpoint:hover {
+.modalLink:hover {
text-decoration: underline;
}
+
@-webkit-keyframes fade {
50% {
opacity:0.6;
@@ -1270,7 +1335,7 @@ input[type="range"]::-webkit-slider-thumb {
width: 100%;
margin-top: 10px;
}
-.settings .subButtons a{
+.settings .subButtons a {
font-size: 12px;
font-weight: 300;
width: 40px;
@@ -1552,12 +1617,16 @@ form li textarea {
cursor: pointer;
position: fixed;
right: 20px;
+ top: 20px;
+ z-index: 20;
}
.close:hover {
color:lightgreen;
}
+
+
.facebook {
width: 100%;
display: inline-block;
@@ -1614,6 +1683,12 @@ form li textarea {
text-decoration:underline;
}
+.aboutRoom .editlink {
+ color: red;
+ text-decoration: none;
+ border-bottom: 1px dotted;
+}
+
.aboutRoom h2{
font-size: 13px;
margin: 5px 0;
@@ -1645,26 +1720,100 @@ form li textarea {
.share a:hover{
text-decoration:underline;
}
-@-webkit-keyframes borderanimation
-{
+
+/* COLLABORATORS */
+
+.collaborators > div {
+ width: 600px;
+ margin: 0 auto;
+ text-align: left;
+ background: white;
+ padding: 10px;
+ margin: 20px auto;
+}
+.collaborators button {
+ width: auto;
+ position: relative;
+ top: -2px;
+}
+.collaborators form {
+ max-width: none;
+}
+.collaborators form input[type=submit] {
+ float: none;
+ width: 150px;
+ position: relative;
+ top: -2px;
+ font-size: 11px;
+
+ padding: 8px;
+ border: 1px solid;
+ font-weight: 500;
+ background: white;
+ cursor: pointer;
+}
+.collaborators form input[type=submit]:hover {
+ background-color: black;
+ border-color: black;
+}
+.collaborators p {
+ margin: 20px 0;
+}
+.collaborators form input[type=text] {
+ font-size: 16px;
+ width: 300px;
+}
+.collaborators h2 {
+ margin: 20px auto 10px;
+}
+#collaborator-list {
+ margin-top: 20px;
+}
+#collaborator-list li {
+ list-style-type: none;
+ background: #fff;
+ padding-top: 4px;
+}
+#collaborator-list .avatar {
+ width: 32px;
+ height: 32px;
+ background-size: cover;
+ display: inline-block;
+ margin-right: 10px;
+}
+#collaborator-list .username {
+ position: relative;
+ top: -10px;
+}
+#collaborator-list .role {
+ float: right;
+ font-style: italic;
+ margin-top: 5px;
+ font-weight: 300;
+}
+#collaborator-list .email {
+ line-height: 31px;
+ position: relative;
+ left: 42px;
+ font-weight: 300;
+ font-style: italic;
+}
+
+/* MARCHING ANTS ANIMATION */
+
+@-webkit-keyframes borderanimation {
0%{width:600px;}
100%{width:750px;left:2px;}
}
-
-@-webkit-keyframes borderanimationleftright
-{
+@-webkit-keyframes borderanimationleftright {
0%{height:250px;}
100%{height:500px;top:2px;}
}
-
-@keyframes borderanimation
-{
+@keyframes borderanimation {
0%{width:500px;}
100%{width:750px;left:2px;}
}
-
-@keyframes borderanimationleftright
-{
+@keyframes borderanimationleftright {
0%{height:250px;}
100%{height:500px;top:2px;}
}
diff --git a/server/index.js b/server/index.js
index 1858825..e9efef0 100644
--- a/server/index.js
+++ b/server/index.js
@@ -117,12 +117,17 @@ site.route = function () {
app.get('/layout', middleware.ensureAuthenticated, middleware.ensureIsStaff, views.modal)
app.get('/layout/:name', middleware.ensureAuthenticated, middleware.ensureIsStaff, views.builder)
+ app.get('/join/:nonce', middleware.ensureAuthenticated, api.collaborator.join)
+ app.get('/api/collaborator/:slug/index', middleware.ensureAuthenticated, middleware.ensureProject, api.collaborator.index)
+ app.post('/api/collaborator/:slug/create', middleware.ensureAuthenticated, middleware.ensureProject, api.collaborator.create)
+ app.delete('/api/collaborator/:slug/destroy', middleware.ensureAuthenticated, middleware.ensureProject, api.collaborator.destroy)
+
app.get('/project', middleware.ensureAuthenticated, views.modal)
app.get('/project/new', middleware.ensureAuthenticated, views.modal)
- app.get('/project/new/:layout', middleware.ensureAuthenticated, views.editor)
- app.get('/project/:slug', middleware.ensureProject, views.reader)
- app.get('/project/:slug/view', middleware.ensureProject, views.reader)
- app.get('/project/:slug/edit', middleware.ensureProject, views.editor)
+ app.get('/project/new/:layout', middleware.ensureAuthenticated, views.editor_new)
+ app.get('/project/:slug', middleware.ensureProject, middleware.ensureIsCollaborator, views.reader)
+ app.get('/project/:slug/view', middleware.ensureProject, middleware.ensureIsCollaborator, views.reader)
+ app.get('/project/:slug/edit', middleware.ensureProject, middleware.ensureIsCollaborator, views.editor)
app.get('/api/layout', middleware.ensureAuthenticated, api.layouts.index)
app.get('/api/layout/:slug', middleware.ensureAuthenticated, api.layouts.show)
diff --git a/server/lib/api/collaborator.js b/server/lib/api/collaborator.js
new file mode 100644
index 0000000..1fda01b
--- /dev/null
+++ b/server/lib/api/collaborator.js
@@ -0,0 +1,89 @@
+/* jshint node: true */
+
+var _ = require('lodash'),
+ auth = require('../auth'),
+ util = require('../util'),
+ upload = require('../upload'),
+ config = require('../../../config.json'),
+ User = require('../schemas/User'),
+ Collaborator = require('../schemas/Collaborator'),
+ Project = require('../schemas/Project');
+
+var collaborator = {
+
+ join: function(req, res){
+ var nonce = req.params.nonce
+ if (! nonce || ! nonce.length) { return res.json({ error: "invalid invite code" }) }
+ Collaborator.findOne({ nonce: nonce }, function(err, collaborator){
+ if (err || ! collaborator) { return res.json({ error: "can't find collaborator" }) }
+ collaborator.user_id = req.user._id
+ collaborator.nonce = ""
+ collaborator.save(function(err, collaborator){
+ Project.findOne({ _id: collaborator.project_id }, function(err, project){
+ if (err || ! project) { return res.json({ error: err }) }
+ res.redirect("/project/" + project.slug + "/edit")
+ })
+ })
+ })
+ },
+
+ //
+
+ index: function(req, res){
+ if (! req.project) {
+ return res.json({ error: "can't find project" })
+ }
+ if (String(req.project.user_id) !== String(req.user._id)) { return res.json({ error: "insufficient permission" }) }
+ Collaborator.find({ project_id: req.project._id }, function(err, collaborators){
+ var user_ids = _.pluck(collaborators, "user_id").filter(function(id){ return !! id })
+ User.find({ _id: user_ids }, "username displayName photo", function(err, users){
+ if (! user_ids) {
+ return res.json(collaborators)
+ }
+ var userIndex = _.indexBy(users, '_id')
+ collaborators = collaborators.map(function(collaborator){
+ var obj = collaborator.toObject()
+ obj.user = userIndex[ obj.user_id ]
+ return obj
+ })
+ collaborators.unshift( { user: req.user.toObject(), owner: true } )
+ res.json(collaborators)
+ })
+ })
+ },
+
+ create: function(req, res){
+ if (! req.project) {
+ return res.json({ error: "can't find project" })
+ }
+ var data = util.cleanQuery(req.body)
+ data.email = util.trim( util.sanitize( data.email ) )
+ data.project_id = req.project._id
+ delete data.user_id
+
+ Collaborator.makeNonce(function(nonce){
+ data.nonce = nonce
+
+ new Collaborator(data).save(function(err, collaborator){
+ if (err || ! collaborator) { return res.json({ error: err }) }
+ console.log(collaborator)
+ res.json(collaborator)
+ auth.mail.collaborator(req.project, req.user, collaborator, function(){})
+ })
+ })
+ },
+
+ destroy: function(req, res){
+ if (! req.project) {
+ return res.json({ error: "can't find project" })
+ }
+ if (String(req.project.user_id) !== String(req.user._id)) {
+ return res.json({ error: "insufficient permission" })
+ }
+ Collaborator.remove({ _id: req.body._id }, function(err){
+ res.json({ status: "OK" })
+ })
+ }
+}
+
+module.exports = collaborator
diff --git a/server/lib/api/index.js b/server/lib/api/index.js
index bfe3632..ad86daa 100644
--- a/server/lib/api/index.js
+++ b/server/lib/api/index.js
@@ -6,6 +6,7 @@ var api = {
media: require('./media'),
profile: require('./profile'),
projects: require('./projects'),
+ collaborator: require('./collaborator'),
}
module.exports = api
diff --git a/server/lib/api/media.js b/server/lib/api/media.js
index 16f9d41..1eb08c1 100644
--- a/server/lib/api/media.js
+++ b/server/lib/api/media.js
@@ -8,8 +8,13 @@ var _ = require('lodash'),
Media = require('../schemas/Media');
var media = {
+
user: function(req, res){
- Media.find({ user_id: req.user._id }, function(err, media){
+ var query = { user_id: req.user._id }
+ if (req.query.tag) {
+ query.tag = req.query.tag
+ }
+ Media.find(query, function(err, media){
res.json(media || [])
})
},
@@ -18,10 +23,14 @@ var media = {
var data = util.cleanQuery(req.body)
data.user_id = req.user._id
data.created_at = new Date ()
+
+ if (data.tag) {
+ data.tag = util.sanitize(data.tag)
+ }
new Media(data).save(function(err, rec){
if (err || ! rec) { return res.json({ error: err }) }
- return res.json(rec)
+ return res.json(rec)
})
},
diff --git a/server/lib/api/projects.js b/server/lib/api/projects.js
index bd3cb81..2a5beff 100644
--- a/server/lib/api/projects.js
+++ b/server/lib/api/projects.js
@@ -39,6 +39,7 @@ var projects = {
data.rooms = JSON.parse(data.rooms)
data.walls = JSON.parse(data.walls)
data.media = JSON.parse(data.media)
+ data.colors = JSON.parse(data.colors)
data.startPosition = JSON.parse(data.startPosition)
upload.put("projects", req.files.thumbnail, {
@@ -91,8 +92,10 @@ var projects = {
Project.findOne({ _id: _id }, function(err, doc){
if (err || ! doc) { return res.json({ error: err }) }
_.extend(doc, data)
+
doc.rooms = JSON.parse(data.rooms)
doc.walls = JSON.parse(data.walls)
+ doc.colors = JSON.parse(data.colors)
doc.media = JSON.parse(data.media)
doc.startPosition = JSON.parse(data.startPosition)
diff --git a/server/lib/auth/mail.js b/server/lib/auth/mail.js
index a4abccd..0ba6d5d 100644
--- a/server/lib/auth/mail.js
+++ b/server/lib/auth/mail.js
@@ -10,7 +10,7 @@ var mail = {
templates: {},
init: function(){
- var names = ["welcome","password"].forEach(function(name){
+ var names = ["welcome","password","collaborator"].forEach(function(name){
mail.templates[name] = {};
var types = ["text","html"].forEach(function(type){
fs.readFile("views/mail/" + name + "." + type + ".ejs", function(err, data){
@@ -62,6 +62,28 @@ var mail = {
mail.send(message, cb)
console.log("sent password email to", user.email)
},
+
+ collaborator: function(project, user, collaborator, cb){
+ var data = {
+ projectSlug: project.slug,
+ projectName: project.name,
+ nonce: collaborator.nonce,
+ username: user.username,
+ }
+
+ var message = {
+ text: mail.templates.collaborator.text(data),
+ from: mail.from,
+ to: collaborator.email,
+ subject: "Join " + data.username + " on " + data.projectName,
+ attachment: [
+ { data: mail.templates.collaborator.html(data), alternative: true },
+ ]
+ }
+ mail.send(message, cb)
+ console.log("sent collaborator email to", collaborator.email)
+ },
+
}
module.exports = mail
diff --git a/server/lib/middleware.js b/server/lib/middleware.js
index 27b9c04..9d6236a 100644
--- a/server/lib/middleware.js
+++ b/server/lib/middleware.js
@@ -5,6 +5,7 @@ var passport = require('passport'),
_ = require('lodash'),
config = require('../../config.json'),
User = require('./schemas/User'),
+ Collaborator = require('./schemas/Collaborator'),
Project = require('./schemas/Project');
@@ -36,7 +37,7 @@ var middleware = {
ensureLocals: function (req, res, next) {
res.locals.token = req.csrfToken();
res.locals.logged_in = req.isAuthenticated()
- res.locals.user = req.user || { id: undefined }
+ res.locals.user = req.user || { _id: undefined }
res.locals.config = config
res.locals.profile = null
res.locals.opt = {}
@@ -63,8 +64,32 @@ var middleware = {
req.project = null
next()
}
- }
+ },
+
+ ensureIsCollaborator: function(req, res, next) {
+ req.isCollaborator = false
+ req.isOwner = false
+ if (! req.user || ! req.project) {
+ next()
+ }
+ else if (String(req.user._id) === String(req.project.user_id)) {
+ req.isOwner = true
+ next()
+ }
+ else {
+ Collaborator.findOne({ user_id: req.user._id, project_id: req.project._id }, function(err, collab) {
+ if (err || ! collab) {
+ next()
+ }
+ else {
+ req.isCollaborator = true
+ next()
+ }
+ })
+ }
+ },
+
}
module.exports = middleware
diff --git a/server/lib/schemas/Collaborator.js b/server/lib/schemas/Collaborator.js
new file mode 100644
index 0000000..6b3d452
--- /dev/null
+++ b/server/lib/schemas/Collaborator.js
@@ -0,0 +1,30 @@
+/* jshint node: true */
+
+var mongoose = require('mongoose'),
+ _ = require('lodash'),
+ crypto = require('crypto'),
+ config = require('../../../config.json'),
+ util = require('../util');
+
+var CollaboratorSchema = new mongoose.Schema({
+ email: { type: String, required: true},
+ project_id: { type: mongoose.Schema.ObjectId, index: true },
+ user_id: { type: mongoose.Schema.ObjectId, index: true },
+ nonce: {
+ type: String,
+ default: "",
+ },
+ created_at: { type: Date },
+ updated_at: { type: Date },
+})
+
+CollaboratorSchema.statics.makeNonce = function(cb){
+ crypto.pseudoRandomBytes(256, function (err, buf){
+ var shasum = crypto.createHash('sha1')
+ shasum.update(buf)
+ cb( shasum.digest('hex') )
+ })
+}
+
+module.exports = exports = mongoose.model('collaborator', CollaboratorSchema);
+exports.schema = CollaboratorSchema;
diff --git a/server/lib/schemas/Media.js b/server/lib/schemas/Media.js
index 1f26b8e..1de354d 100644
--- a/server/lib/schemas/Media.js
+++ b/server/lib/schemas/Media.js
@@ -41,7 +41,8 @@ var MediaSchema = new mongoose.Schema({
loop: { type: Boolean, default: false },
mute: { type: Boolean, default: true },
keyframe: { type: Number, default: 0.0 },
-
+ tag: { type: String, default: "" },
+
widthDimension: { type: Number },
heightDimension: { type: Number },
units: { type: String },
diff --git a/server/lib/schemas/Project.js b/server/lib/schemas/Project.js
index 0f54eaa..abf34fb 100644
--- a/server/lib/schemas/Project.js
+++ b/server/lib/schemas/Project.js
@@ -30,6 +30,7 @@ var ProjectSchema = new mongoose.Schema({
rooms: [mongoose.Schema.Types.Mixed],
walls: [mongoose.Schema.Types.Mixed],
media: [mongoose.Schema.Types.Mixed],
+ colors: mongoose.Schema.Types.Mixed,
startPosition: mongoose.Schema.Types.Mixed,
user_id: { type: mongoose.Schema.ObjectId, index: true },
created_at: { type: Date },
diff --git a/server/lib/views.js b/server/lib/views.js
index b776582..b3c1d18 100644
--- a/server/lib/views.js
+++ b/server/lib/views.js
@@ -3,6 +3,7 @@
var User = require('./schemas/User'),
Project = require('./schemas/Project'),
Documentation = require('./schemas/Documentation'),
+ Collaborator = require('./schemas/Collaborator'),
config = require('../../config'),
marked = require('marked'),
util = require('./util'),
@@ -19,29 +20,24 @@ marked.setOptions({
var views = {}
+views.editor_new = function (req, res) {
+ if (! req.user) {
+ res.redirect('/')
+ }
+ else {
+ res.render('editor')
+ }
+}
+
views.editor = function (req, res) {
- if (! req.user && ! req.project) {
+ if (! req.project) {
res.redirect('/')
}
- else if (! req.user || (req.project && String(req.user._id) !== String(req.project.user_id))) {
- User.findOne({ _id: req.project.user_id }, function(err, user) {
- if (err || ! user) {
- console.error(err)
- res.redirect('/')
- return
- }
- res.render('reader', {
- name: util.sanitize(req.project.name),
- description: util.sanitize(req.project.description),
- date: moment(req.project.updated_at).format("M/DD/YYYY"),
- author: user.displayName,
- authorlink: "/profile/" + user.username,
- noui: !! (req.query.noui === '1'),
- })
- })
+ else if (req.isOwner || req.isCollaborator) {
+ res.render('editor')
}
else {
- res.render('editor')
+ views.reader(req, res)
}
}
@@ -61,6 +57,8 @@ views.reader = function (req, res) {
date: moment(req.project.updated_at).format("M/DD/YYYY"),
author: user.displayName,
authorlink: "/profile/" + user.username,
+ canEdit: req.isOwner || req.isCollaborator,
+ editlink: "/project/" + req.project.slug + "/edit",
noui: !! (req.query.noui === '1'),
})
})
diff --git a/views/controls/editor/collaborators.ejs b/views/controls/editor/collaborators.ejs
new file mode 100644
index 0000000..69e5b64
--- /dev/null
+++ b/views/controls/editor/collaborators.ejs
@@ -0,0 +1,61 @@
+<div class="collaborators fixed mediaDrawer animate">
+ <span class="close">X</span>
+
+ <div>
+ <h2>Collaborators</h2>
+
+ <p>
+ To invite others to contribute to this project, submit their email address below. They'll receive an email with instructions to join this blog and register if they're not a Vvalls user yet.
+ </p>
+
+ <form>
+ <input type="text" id="collaborator-email" name="email">
+ <input type="submit" id="collaborator-invite" value="Invite to this project">
+ </form>
+
+ <div id="collaborator-url-rapper">
+ We've sent a link to join this project to <span id="collaborator-dummy-email"></span>.
+ You can also send this link yourself:
+ <input type="text" id="collaborator-url">
+ </div>
+
+ <ul id="collaborator-list">
+ </ul>
+
+ </div>
+
+</div>
+
+<script type="text/html" id="collaborator-template">
+ <li>
+ <a class="user">
+ <div class="avatar"></div><span class="username"></span>
+ </a>
+ <span class="email"></span>
+ <button data-role="destroy-collaborator" class="remove-user">Remove</button>
+ <span class="role">owner</span>
+ </li>
+</script>
+
+<style>
+#collaborator-url-rapper {
+ display: none;
+ background: #fff;
+ border: 1px solid;
+ box-shadow: -3px 3px 0;
+ padding: 10px;
+ font-weight: 300;
+ font-size: 14px;
+ margin: 10px 0;
+}
+#collaborator-url {
+ font-size: 16px;
+ width: 500px;
+ border: 1px solid;
+ font-size: 14px;
+ padding: 5px;
+ font-weight: 300;
+ margin-top: 5px;
+ display: block;
+}
+</style>
diff --git a/views/controls/editor/light-control.ejs b/views/controls/editor/light-control.ejs
index ddd282b..a67df34 100644
--- a/views/controls/editor/light-control.ejs
+++ b/views/controls/editor/light-control.ejs
@@ -1,8 +1,19 @@
<div class="vvbox lightcontrol">
+
<div class="slider">
- <input type="range" min="0" max="100" value="0" id="outline-hue" />
- <h4>Outline Hue</h4>
+ <input type="range" min="0" max="110" value="0" id="brightness-control" />
+ <h4>Brightness</h4>
</div>
+
+ <div class="color-swatches">
+ <div class="swatch" id="wall-color" data-mode="wall"></div><label>wall</label>
+ <div class="swatch" id="floor-color" data-mode="floor"></div><label>floor</label>
+ <div class="swatch" id="ceiling-color" data-mode="ceiling"></div><label>ceiling</label>
+ <br>
+ <div class="swatch" id="outline-color" data-mode="outline"></div><label>outlines</label>
+ </div>
+
+<!--
<div class="slider">
<input type="range" min="0" max="100" value="100" id="wall-hue" />
<h4>Wall Hue</h4>
@@ -15,4 +26,5 @@
<input type="range" min="0" max="100" value="0" id="shadow-control" />
<h4>Shadow</h4>
</div>
+-->
</div>
diff --git a/views/controls/editor/settings.ejs b/views/controls/editor/settings.ejs
index 6f46be3..e4ec7ee 100644
--- a/views/controls/editor/settings.ejs
+++ b/views/controls/editor/settings.ejs
@@ -3,11 +3,17 @@
<input type="hidden" name="_id" value="new">
<div class="setting">
- <a href="#" id="startpoint">
+ <a href="#" class="modalLink" id="startpoint">
<span class="icon-ios7-navigate-outline"></span>
<span id="startText">Select Startpoint</span>
<span id="moveText">Move to Desired Point</span></a>
</div>
+ <div class="setting">
+ <a href="#" class="modalLink" data-role='show-collaborators'>
+ <span class="icon-ios7-plus-outline"></span>
+ Add Collaborators
+ </a>
+ </div>
<div class="setting">
<input type="text" name="name" placeholder="room name">
diff --git a/views/controls/editor/wallpaper.ejs b/views/controls/editor/wallpaper.ejs
index 144e419..55ecf85 100644
--- a/views/controls/editor/wallpaper.ejs
+++ b/views/controls/editor/wallpaper.ejs
@@ -1,4 +1,16 @@
<div class="vvbox wallpaper">
+ <div class="swatches"></div>
+
+ <div class="wallpaperUpload">
+ <form>
+ <span class="icon-ios7-upload-outline upload-icon"></span>
+ <label>Upload wallpaper</label>
+ <input type="file" accept="image/*" class="file" multiple>
+ </form>
+<!--
+ <input type="text" placeholder="Enter Image URL" class="url">
+ -->
+ </div>
</div>
<div class="floatingSwatch"></div>
diff --git a/views/controls/reader/about-room.ejs b/views/controls/reader/about-room.ejs
index f990da8..2aa244b 100644
--- a/views/controls/reader/about-room.ejs
+++ b/views/controls/reader/about-room.ejs
@@ -3,7 +3,12 @@
[[- name ]],<br>
<a href="[[- authorlink ]]">[[- author ]]</a>
</h1>
- <h2>Last modified [[- date ]]</h2>
+ <h2>
+ Last modified [[- date ]]
+ [[ if (canEdit) { ]]
+ &middot; <a href="[[- editlink ]]" class="editlink">Edit</a>
+ [[ } ]]
+ </h2>
<span>[[- description ]]</span>
</div>
diff --git a/views/editor.ejs b/views/editor.ejs
index 5d1e052..9950878 100755
--- a/views/editor.ejs
+++ b/views/editor.ejs
@@ -17,6 +17,7 @@
[[ include controls/editor/media-editor ]]
[[ include controls/editor/wallpaper ]]
[[ include controls/editor/light-control ]]
+ [[ include controls/editor/collaborators ]]
[[ include controls/editor/settings ]]
</div>
diff --git a/views/mail/collaborator.html.ejs b/views/mail/collaborator.html.ejs
new file mode 100644
index 0000000..2a08a1c
--- /dev/null
+++ b/views/mail/collaborator.html.ejs
@@ -0,0 +1,20 @@
+<html>
+<body>
+
+<p>
+ <a href="http://vvalls.com/"><img src="http://vvalls.com/assets/img/logo.svg"></a>
+</p>
+
+<p>
+ <a href="http://vvalls.com/profile/[[- username ]]">[[- username ]]</a> has invited you to join the project
+ <a href="http://vvalls.com/project/[[- projectSlug ]]">[[- projectName ]]</a> on Vvalls.
+</p>
+
+<p>
+ Accept the invitation below:
+</p>
+
+<a href="http://vvalls.com/join/[[- nonce ]]">Join Project</a>
+
+</body>
+</html>
diff --git a/views/mail/collaborator.text.ejs b/views/mail/collaborator.text.ejs
new file mode 100644
index 0000000..52d39b6
--- /dev/null
+++ b/views/mail/collaborator.text.ejs
@@ -0,0 +1,7 @@
+
+[[- username ]] has invited you to join the project [[- projectName ]] on Vvalls.
+
+Accept the invitation below:
+
+http://vvalls.com/join/[[- nonce ]]
+
diff --git a/views/mail/welcome.html.ejs b/views/mail/welcome.html.ejs
index 8b7194b..b2c329f 100644
--- a/views/mail/welcome.html.ejs
+++ b/views/mail/welcome.html.ejs
@@ -2,7 +2,7 @@
<body>
<p>
- <a href="http://vvalls.com/"><img src="http://vvalls.com/img/logo.svg"></a>
+ <a href="http://vvalls.com/"><img src="http://vvalls.com/assets/img/logo.svg"></a>
</p>
<p>
diff --git a/views/mail/welcome.text.ejs b/views/mail/welcome.text.ejs
index cab9c15..02b449b 100644
--- a/views/mail/welcome.text.ejs
+++ b/views/mail/welcome.text.ejs
@@ -1,4 +1,4 @@
Welcome to Vvalls, [[- username ]]
-http://www.posthang.com
+http://www.vvalls.com
diff --git a/views/partials/meta.ejs b/views/partials/meta.ejs
index ceaaba1..9916b34 100644
--- a/views/partials/meta.ejs
+++ b/views/partials/meta.ejs
@@ -1,3 +1,20 @@
+
+<!-----+ +------+ +------+ +------+ +------+
+|`. `. |\ \ | | / /| .' .'|
+| `+------+ | +------+ +------+ +------+ | +------+' |
+| | | | | | | | | | | | | |
++ | | + | | | | | | + | | +
+ `. | | \| | | | | |/ | | .'
+ `+------+ +------+ +------+ +------+ +------+'
+ VVALLS - developed by okfoc.us
+ .+------+ +------+ +------+ +------+ +------+.
+ .' .'| / /| | | |\ \ |`. `.
++------+' | +------+ | +------+ | +------+ | `+------+
+| | | | | | | | | | | | | |
+| | + | | + | | + | | + | |
+| | .' | |/ | | \| | `. | |
++------+' +------+ +------+ +------+ `+------>
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
diff --git a/views/partials/scripts.ejs b/views/partials/scripts.ejs
index 6d85699..7d56b2e 100644
--- a/views/partials/scripts.ejs
+++ b/views/partials/scripts.ejs
@@ -41,6 +41,7 @@
<script type="text/javascript" src="/assets/javascripts/rectangles/models/wall.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/engine/rooms/_rooms.js"></script>
+<script type="text/javascript" src="/assets/javascripts/rectangles/engine/rooms/_walls.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/engine/rooms/builder.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/engine/rooms/clipper.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/engine/rooms/grouper.js"></script>
@@ -63,6 +64,7 @@
<script type="text/javascript" src="/assets/javascripts/ui/lib/Router.js"></script>
<script type="text/javascript" src="/assets/javascripts/ui/lib/ModalView.js"></script>
<script type="text/javascript" src="/assets/javascripts/ui/lib/FormView.js"></script>
+<script type="text/javascript" src="/assets/javascripts/ui/lib/UploadView.js"></script>
<script type="text/javascript" src="/assets/javascripts/ui/lib/AlertModal.js"></script>
<script type="text/javascript" src="/assets/javascripts/ui/lib/ConfirmModal.js"></script>
<script type="text/javascript" src="/assets/javascripts/ui/lib/ErrorModal.js"></script>
@@ -91,6 +93,7 @@
<script type="text/javascript" src="/assets/javascripts/ui/editor/EditorSettings.js"></script>
<script type="text/javascript" src="/assets/javascripts/ui/editor/EditorToolbar.js"></script>
<script type="text/javascript" src="/assets/javascripts/ui/editor/LightControl.js"></script>
+<script type="text/javascript" src="/assets/javascripts/ui/editor/Collaborators.js"></script>
<script type="text/javascript" src="/assets/javascripts/ui/editor/MediaEditor.js"></script>
<script type="text/javascript" src="/assets/javascripts/ui/editor/MediaUpload.js"></script>
<script type="text/javascript" src="/assets/javascripts/ui/editor/MediaViewer.js"></script>
@@ -102,6 +105,7 @@
<script type="text/javascript" src="/assets/javascripts/ui/_router.js"></script>
<script type="text/javascript" src="/assets/javascripts/app.js"></script>
+<script type="text/javascript" src="/assets/javascripts/defaults.js"></script>
<!-- external dependencies -->
<script src="http://www.youtube.com/player_api"></script>
diff --git a/views/reader.ejs b/views/reader.ejs
index 7c31766..ed5df1f 100644
--- a/views/reader.ejs
+++ b/views/reader.ejs
@@ -4,7 +4,7 @@
<title>vvalls</title>
[[ include partials/meta ]]
</head>
-<body class="editing loading">
+<body class="loading">
<div id="scene"></div>