diff options
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) { ]] + · <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> |
