summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/assets/img/destroy-cursor.pngbin0 -> 1082 bytes
-rw-r--r--public/assets/javascripts/mx/extensions/mx.movements.js4
-rw-r--r--public/assets/javascripts/mx/primitives/mx.image.js1
-rw-r--r--public/assets/javascripts/mx/primitives/mx.video.js1
-rw-r--r--public/assets/javascripts/mx/primitives/mx.vimeo.js1
-rw-r--r--public/assets/javascripts/mx/primitives/mx.youtube.js1
-rw-r--r--public/assets/javascripts/rectangles/_env.js14
-rw-r--r--public/assets/javascripts/rectangles/engine/map/draw.js4
-rw-r--r--public/assets/javascripts/rectangles/engine/map/ui_editor.js106
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/types/_object.js4
-rw-r--r--public/assets/javascripts/rectangles/engine/scenery/undo.js80
-rw-r--r--public/assets/javascripts/rectangles/models/rect.js4
-rw-r--r--public/assets/javascripts/rectangles/models/room.js8
-rw-r--r--public/assets/javascripts/rectangles/models/vec2.js4
-rw-r--r--public/assets/javascripts/rectangles/models/wall.js2
-rw-r--r--public/assets/javascripts/rectangles/util/constants.js6
-rw-r--r--public/assets/javascripts/rectangles/util/keys.js6
-rw-r--r--public/assets/javascripts/rectangles/util/measurement.js82
-rw-r--r--public/assets/javascripts/rectangles/util/permissions.js17
-rw-r--r--public/assets/javascripts/rectangles/util/undo.js52
-rw-r--r--public/assets/javascripts/ui/builder/BuilderInfo.js83
-rw-r--r--public/assets/javascripts/ui/builder/BuilderToolbar.js21
-rw-r--r--public/assets/javascripts/ui/editor/MediaEditor.js34
-rwxr-xr-xpublic/assets/stylesheets/app.css59
-rw-r--r--server/lib/middleware.js2
-rw-r--r--server/lib/views.js2
-rw-r--r--test/00-setup.js1
-rw-r--r--test/09-test-undo.js164
-rw-r--r--views/controls/builder/toolbar.ejs2
-rw-r--r--views/controls/editor/media-editor.ejs20
-rw-r--r--views/partials/scripts.ejs3
-rw-r--r--views/projects/list-projects.ejs9
-rw-r--r--views/reader.ejs2
33 files changed, 656 insertions, 143 deletions
diff --git a/public/assets/img/destroy-cursor.png b/public/assets/img/destroy-cursor.png
new file mode 100644
index 0000000..eebe3cd
--- /dev/null
+++ b/public/assets/img/destroy-cursor.png
Binary files differ
diff --git a/public/assets/javascripts/mx/extensions/mx.movements.js b/public/assets/javascripts/mx/extensions/mx.movements.js
index 191088f..3b7d3e2 100644
--- a/public/assets/javascripts/mx/extensions/mx.movements.js
+++ b/public/assets/javascripts/mx/extensions/mx.movements.js
@@ -149,7 +149,8 @@ MX.Movements = function (cam) {
case 32: // space
moveUp = moveDown = false
break
-
+
+/*
case 48: // 0
cam.rotationX = 0
cam.rotationY = 0
@@ -157,6 +158,7 @@ MX.Movements = function (cam) {
cam.y = viewHeight
cam.z = 0
break
+*/
}
})
diff --git a/public/assets/javascripts/mx/primitives/mx.image.js b/public/assets/javascripts/mx/primitives/mx.image.js
index 278fa1e..a640620 100644
--- a/public/assets/javascripts/mx/primitives/mx.image.js
+++ b/public/assets/javascripts/mx/primitives/mx.image.js
@@ -14,6 +14,7 @@ MX.Image = MX.Object3D.extend({
ops.className && this.el.classList.add(ops.className)
this.backface && this.el.classList.add("backface-visible")
this.el.classList.add("image")
+ this.el.classList.add("mx-scenery")
this.el.style.backgroundRepeat = 'no-repeat'
diff --git a/public/assets/javascripts/mx/primitives/mx.video.js b/public/assets/javascripts/mx/primitives/mx.video.js
index cdb92c8..7c0cd33 100644
--- a/public/assets/javascripts/mx/primitives/mx.video.js
+++ b/public/assets/javascripts/mx/primitives/mx.video.js
@@ -19,6 +19,7 @@ MX.Video = MX.Object3D.extend({
ops.className && this.el.classList.add(ops.className)
this.backface && this.el.classList.add("backface-visible")
this.el.classList.add("video")
+ this.el.classList.add("mx-scenery")
this.paused = !! this.media.autoplay
this.muted = app.muted || !! this.media.mute
diff --git a/public/assets/javascripts/mx/primitives/mx.vimeo.js b/public/assets/javascripts/mx/primitives/mx.vimeo.js
index 64d9103..5b22821 100644
--- a/public/assets/javascripts/mx/primitives/mx.vimeo.js
+++ b/public/assets/javascripts/mx/primitives/mx.vimeo.js
@@ -19,6 +19,7 @@ MX.Vimeo = MX.Object3D.extend({
ops.className && this.el.classList.add(ops.className)
this.backface && this.el.classList.add("backface-visible")
this.el.classList.add("video")
+ this.el.classList.add("mx-scenery")
this.paused = !! this.media.autoplay
this.muted = app.muted || !! this.media.mute
this.started = false
diff --git a/public/assets/javascripts/mx/primitives/mx.youtube.js b/public/assets/javascripts/mx/primitives/mx.youtube.js
index f7f00aa..7846649 100644
--- a/public/assets/javascripts/mx/primitives/mx.youtube.js
+++ b/public/assets/javascripts/mx/primitives/mx.youtube.js
@@ -19,6 +19,7 @@ MX.Youtube = MX.Object3D.extend({
ops.className && this.el.classList.add(ops.className)
this.backface && this.el.classList.add("backface-visible")
this.el.classList.add("video")
+ this.el.classList.add("mx-scenery")
this.paused = !! this.media.autoplay
this.muted = app.muted || !! this.media.mute
diff --git a/public/assets/javascripts/rectangles/_env.js b/public/assets/javascripts/rectangles/_env.js
index 3cfe969..4b14a21 100644
--- a/public/assets/javascripts/rectangles/_env.js
+++ b/public/assets/javascripts/rectangles/_env.js
@@ -35,6 +35,20 @@ environment.init = function(){
zoom: -4.8
})
}
+
+ keys.on("z", function(e){
+ e.preventDefault()
+ if (e.ctrlKey || e.metaKey) {
+ if (e.shiftKey) {
+ var canRedo = UndoStack.redo()
+ console.log("can redo", canRedo)
+ }
+ else {
+ var canUndo = UndoStack.undo()
+ console.log("can undo", canUndo)
+ }
+ }
+ })
}
environment.update = function(t){
map.update()
diff --git a/public/assets/javascripts/rectangles/engine/map/draw.js b/public/assets/javascripts/rectangles/engine/map/draw.js
index 8e1fe5a..3e185d2 100644
--- a/public/assets/javascripts/rectangles/engine/map/draw.js
+++ b/public/assets/javascripts/rectangles/engine/map/draw.js
@@ -29,7 +29,7 @@ Map.Draw = function(map, opt){
ctx.translate( map.center.a, map.center.b )
ctx.scale( -1, 1 )
- draw.regions(Rooms.regions, colors)
+ draw.regions(Rooms.regions, [ "#f8f8f8" ])
draw.mouse(map.ui.mouse.cursor)
draw.coords()
scene && draw.camera(scene.camera)
@@ -55,7 +55,7 @@ Map.Draw = function(map, opt){
}
draw.clear = function(){
- ctx.fillStyle = "rgba(255,255,255,0.9)"
+ ctx.fillStyle = "rgba(255,255,255,0.98)"
ctx.clearRect(0, 0, map.dimensions.a, map.dimensions.b)
ctx.fillRect(0, 0, map.dimensions.a, map.dimensions.b)
}
diff --git a/public/assets/javascripts/rectangles/engine/map/ui_editor.js b/public/assets/javascripts/rectangles/engine/map/ui_editor.js
index 577ea32..c5c996c 100644
--- a/public/assets/javascripts/rectangles/engine/map/ui_editor.js
+++ b/public/assets/javascripts/rectangles/engine/map/ui_editor.js
@@ -30,6 +30,7 @@ Map.UI.Editor = function(map){
//
function down (e, cursor){
+ var room
cursor.x.div(map.dimensions.a).add(0.5).mul(map.dimensions.a / map.zoom).add(map.center.a)
cursor.y.div(map.dimensions.b).sub(0.5).mul(map.dimensions.b / map.zoom).sub(map.center.b)
@@ -52,22 +53,28 @@ Map.UI.Editor = function(map){
if (intersects.length && base.permissions.destroy) {
base.mouse.down = false
- Rooms.remove(intersects[0])
- app.tube("builder-destroy-room", intersects[0])
+
+ room = intersects[0]
+
+ UndoStack.push({
+ type: "destroy-room",
+ prev: room.copy(),
+ next: { id: room.id },
+ })
+
+ Rooms.remove(room)
+ app.tube("builder-destroy-room", room)
return
}
- else if (intersects.length && (base.permissions.move || base.permissions.resize)) {
+ else if (intersects.length) {
base.dragging = intersects[0]
- base.resizing = base.permissions.resize && base.dragging.rect.nearEdge(cursor.x.a, cursor.y.a, resize_margin / map.zoom)
+ base.resizing = base.dragging.rect.nearEdge(cursor.x.a, cursor.y.a, resize_margin / map.zoom)
base.dragging.rect.translation.sides = base.resizing
app.tube("builder-pick-room", intersects[0])
}
else if (base.permissions.create) {
base.creating = true
}
- else if (intersects.length) {
- app.tube("builder-pick-room", intersects[0])
- }
if (e.shiftKey && base.dragging) {
base.dragging.rect.quantize(10/map.zoom)
@@ -77,6 +84,45 @@ Map.UI.Editor = function(map){
function move (e, cursor) {
cursor.x.div(map.dimensions.a).add(0.5).mul(map.dimensions.a / map.zoom).add(map.center.a)
cursor.y.div(map.dimensions.b).sub(0.5).mul(map.dimensions.b / map.zoom).sub(map.center.b)
+
+ var intersects = Rooms.filter(function(r){
+ return r.rect.contains(cursor.x.a, cursor.y.a)
+ })
+
+ if (base.permissions.destroy) {
+ map.el.className = "destroy"
+ }
+ else if (intersects.length) {
+ var edges = intersects[0].rect.nearEdge(cursor.x.a, cursor.y.a, resize_margin / map.zoom)
+ switch (edges) {
+ case FRONT_LEFT:
+ case BACK_RIGHT:
+ map.el.className = "nesw-resize"
+ break
+
+ case FRONT_RIGHT:
+ case BACK_LEFT:
+ map.el.className = "nwse-resize"
+ break
+
+ case FRONT:
+ case BACK:
+ map.el.className = "ns-resize"
+ break
+
+ case LEFT:
+ case RIGHT:
+ map.el.className = "ew-resize"
+ break
+
+ default:
+ map.el.className = "move"
+ break
+ }
+ }
+ else {
+ map.el.className = ""
+ }
}
function drag (e, cursor) {
@@ -115,20 +161,40 @@ Map.UI.Editor = function(map){
cursor.x.abs().quantize(1)
cursor.y.abs().quantize(1)
var room = Rooms.add_with_rect( cursor )
+
+ UndoStack.push({
+ type: "create-room",
+ prev: { id: room.id },
+ next: room.copy()
+ })
+
app.tube("builder-pick-room", room)
}
}
- if (base.resizing) {
- base.dragging.rect.resize()
- }
- else if (base.dragging) {
- base.dragging.rect.translate()
- }
+ if (base.resizing || base.dragging) {
+ var oldState = base.dragging.copy()
+
+ if (base.resizing) {
+ base.dragging.rect.resize()
+ }
+ else if (base.dragging) {
+ base.dragging.rect.translate()
+ }
+
+ UndoStack.push({
+ type: "update-room",
+ prev: oldState,
+ next: base.dragging.copy()
+ })
+ }
+
base.creating = base.dragging = base.resizing = false
}
+ var wheelState, wheelTimeout
+
function mousewheel (e, val, delta){
var cursor = base.mouse.cursor
@@ -137,8 +203,20 @@ Map.UI.Editor = function(map){
})
if (intersects.length) {
+ wheelState = wheelState || intersects[0].copy()
+
intersects[0].height = clamp( ~~(intersects[0].height - delta), height_min, height_max )
- Rooms.clipper.update()
+
+ clearTimeout(wheelTimeout)
+ wheelTimeout = setTimeout(function(){
+ UndoStack.push({
+ type: "update-room",
+ prev: wheelState,
+ next: intersects[0].copy()
+ })
+ Rooms.clipper.update()
+ wheelState = null
+ }, 500)
}
else {
map.set_zoom(map.zoom_exponent - delta/20)
diff --git a/public/assets/javascripts/rectangles/engine/scenery/types/_object.js b/public/assets/javascripts/rectangles/engine/scenery/types/_object.js
index 7202ce0..66e0faf 100644
--- a/public/assets/javascripts/rectangles/engine/scenery/types/_object.js
+++ b/public/assets/javascripts/rectangles/engine/scenery/types/_object.js
@@ -66,6 +66,10 @@ Scenery.types.base = Fiber.extend(function(base){
this.center = this.wall.center()
},
+ set_scale: function(scale){
+ this.scale = this.mx.scale = this.mx.ops.scale = scale || 1.0
+ },
+
recenter: function(){
this.mx.move({
x: this.center.a,
diff --git a/public/assets/javascripts/rectangles/engine/scenery/undo.js b/public/assets/javascripts/rectangles/engine/scenery/undo.js
new file mode 100644
index 0000000..4bdb2c4
--- /dev/null
+++ b/public/assets/javascripts/rectangles/engine/scenery/undo.js
@@ -0,0 +1,80 @@
+(function(){
+ UndoStack.register([
+ {
+ type: "create-scenery",
+ undo: function(state){
+ },
+ redo: function(state){
+ },
+ },
+ {
+ type: "update-scenery",
+ undo: function(state){
+ },
+ redo: function(state){
+ },
+ },
+ {
+ type: "destroy-scenery",
+ undo: function(state){
+ },
+ redo: function(state){
+ },
+ },
+
+ //
+
+ {
+ type: "create-room",
+ undo: function(room){
+ Rooms.remove(room)
+ Rooms.clipper.update()
+ },
+ redo: function(room){
+ Rooms.add(new Room(room))
+ Rooms.clipper.update()
+ app.tube("builder-pick-room", 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)
+ },
+ 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)
+ },
+ },
+ {
+ type: "destroy-room",
+ undo: function(room){
+ Rooms.add(new Room(room))
+ Rooms.clipper.update()
+ app.tube("builder-pick-room", room)
+ },
+ redo: function(room){
+ Rooms.remove(room)
+ Rooms.clipper.update()
+ },
+ },
+
+ //
+
+ {
+ type: "update-wallpaper",
+ undo: function(state){
+ },
+ redo: function(state){
+ },
+ },
+
+ ])
+})()
diff --git a/public/assets/javascripts/rectangles/models/rect.js b/public/assets/javascripts/rectangles/models/rect.js
index 58469bc..3f94740 100644
--- a/public/assets/javascripts/rectangles/models/rect.js
+++ b/public/assets/javascripts/rectangles/models/rect.js
@@ -39,6 +39,10 @@
Rect.prototype.clone = function(){
return new Rect( this.x.clone(), this.y.clone() )
}
+ Rect.prototype.assign = function(r) {
+ this.x.assign(r.x)
+ this.y.assign(r.y)
+ }
Rect.prototype.center = function(){
return new vec2(this.x.midpoint(), this.y.midpoint())
}
diff --git a/public/assets/javascripts/rectangles/models/room.js b/public/assets/javascripts/rectangles/models/room.js
index c2850ba..aa18f6d 100644
--- a/public/assets/javascripts/rectangles/models/room.js
+++ b/public/assets/javascripts/rectangles/models/room.js
@@ -40,6 +40,14 @@
this.focused = false
}
+ Room.prototype.copy = function(){
+ return {
+ id: this.id,
+ rect: this.rect.clone(),
+ height: this.height,
+ }
+ }
+
Room.prototype.toString = function(){
return this.rect.toString()
}
diff --git a/public/assets/javascripts/rectangles/models/vec2.js b/public/assets/javascripts/rectangles/models/vec2.js
index 104c6f7..214feb9 100644
--- a/public/assets/javascripts/rectangles/models/vec2.js
+++ b/public/assets/javascripts/rectangles/models/vec2.js
@@ -17,6 +17,10 @@
vec2.prototype.clone = function(){
return new vec2(this.a, this.b)
}
+ vec2.prototype.assign = function(v){
+ this.a = v.a
+ this.b = v.b
+ }
vec2.prototype.abs = function(){
if (this.b < this.a) {
this.invert()
diff --git a/public/assets/javascripts/rectangles/models/wall.js b/public/assets/javascripts/rectangles/models/wall.js
index 027d5f5..91e7c18 100644
--- a/public/assets/javascripts/rectangles/models/wall.js
+++ b/public/assets/javascripts/rectangles/models/wall.js
@@ -146,8 +146,6 @@ window.Wall = (function(){
if (shouldFlip) {
sortedWalls = sortedWalls.reverse()
}
-
-console.log(sortedWalls.map(function(z){return useX ? z.x : z.z}).join(" "))
var len = sortedWalls.length
diff --git a/public/assets/javascripts/rectangles/util/constants.js b/public/assets/javascripts/rectangles/util/constants.js
index 58cb1a5..b9485ca 100644
--- a/public/assets/javascripts/rectangles/util/constants.js
+++ b/public/assets/javascripts/rectangles/util/constants.js
@@ -8,6 +8,12 @@ var TOP = CEILING, BOTTOM = FLOOR,
BOTTOM_RIGHT = BOTTOM | RIGHT,
TOP_BOTTOM = TOP | BOTTOM
+var FRONT_LEFT = FRONT | LEFT,
+ FRONT_RIGHT = FRONT | RIGHT,
+ BACK_LEFT = BACK | LEFT,
+ BACK_RIGHT = BACK | RIGHT
+
+
var height_min = 200,
height_max = 2000,
side_min = 10,
diff --git a/public/assets/javascripts/rectangles/util/keys.js b/public/assets/javascripts/rectangles/util/keys.js
index 5a5c9d2..62d763f 100644
--- a/public/assets/javascripts/rectangles/util/keys.js
+++ b/public/assets/javascripts/rectangles/util/keys.js
@@ -19,7 +19,7 @@ var keys = (function(){
break;
default:
if (keys.debug) console.log(key)
- base.tube(key)
+ base.tube(key, e)
break;
}
})
@@ -158,8 +158,8 @@ var keys = (function(){
'backslash' : '220',
'closebracket' : '221',
'single_quote' : '222'
- }
- var KEY_NAMES = invert_hash(KEYCODES)
+ },
+ KEY_NAMES = invert_hash(KEYCODES)
return base
})() \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/util/measurement.js b/public/assets/javascripts/rectangles/util/measurement.js
new file mode 100644
index 0000000..d6a0b35
--- /dev/null
+++ b/public/assets/javascripts/rectangles/util/measurement.js
@@ -0,0 +1,82 @@
+$.fn.resetUnitVal = function(){
+ this.each(function(){
+ var n = $(this).data("px")
+ $(this).unitVal(n)
+ });
+}
+
+$.fn.unitVal = function(n){
+ var s
+ if (typeof n === "undefined") {
+ s = $(this).val()
+ n = stringToMeasurement( s )
+ if (! n || isNaN(n)) {
+ n = $(this).data("px")
+ }
+ }
+ s = measurementToString( n )
+ $(this).val( s ).data("px", n)
+ return n
+}
+
+function measurementToString( n ) {
+ var s, ft, inch
+ switch (app.units) {
+ case 'm':
+ s = round(n/36 * 0.3048 * 100) / 100 + " m"
+ break
+ case 'ft':
+ ft = floor(n / 36)
+ inch = abs(round((n % 36) / 3))
+ s = ft + "'"
+ if (inch > 0) {
+ s += " " + inch + '"'
+ }
+ break
+ case 'px':
+ default:
+ s = round(n) + " px"
+ break
+ }
+ return s
+}
+function stringToMeasurement( s ) {
+ var ft, inch, ft_in, type
+ if (! s.match(/[0-9]/)) {
+ return NaN
+ }
+ if (s.indexOf("'") !== -1 || s.indexOf('"') !== -1 || s.indexOf('ft') !== -1) {
+ ft_in = s.match(/[0-9.]+/g)
+ if (ft_in.length >= 2) {
+ ft = parseFloat( ft_in[0] )
+ inch = parseFloat( ft_in[1] )
+ }
+ else if (ft_in.length == 1) {
+ if (s.indexOf('"') !== -1) {
+ ft = 0
+ inch = parseFloat( ft_in[0] )
+ }
+ else {
+ ft = parseFloat( ft_in[0] )
+ inch = 0
+ }
+ }
+ else {
+ ft = inch = 0
+ }
+ n = ft * 36 + inch * 3
+ }
+ else if (s.indexOf("m") !== -1) {
+ n = parseFloat(s.match(/[0-9.]+/)) * 36 / 0.3048
+ }
+ else if (s.indexOf("px") !== -1) {
+ n = parseFloat(s.match(/[0-9.]+/))
+ }
+ else {
+ n = abs( stringToMeasurement( s + app.units ) )
+ }
+ if (s.indexOf('-') !== -1) {
+ n *= -1
+ }
+ return n
+}
diff --git a/public/assets/javascripts/rectangles/util/permissions.js b/public/assets/javascripts/rectangles/util/permissions.js
index adb2498..1b5a1b5 100644
--- a/public/assets/javascripts/rectangles/util/permissions.js
+++ b/public/assets/javascripts/rectangles/util/permissions.js
@@ -24,9 +24,26 @@ Permissions.prototype.assign = function (key, state) {
return state
}
+Permissions.prototype.add = function (key) {
+ var base = this
+ base[key] = true
+}
+
+Permissions.prototype.remove = function (key) {
+ var base = this
+ base[key] = true
+}
+
Permissions.prototype.clear = function () {
var base = this
base.keys.forEach(function(op){
base[op] = false
})
}
+
+Permissions.prototype.log = function () {
+ var base = this
+ base.keys.forEach(function(op){
+ console.log(op, base[op])
+ })
+} \ No newline at end of file
diff --git a/public/assets/javascripts/rectangles/util/undo.js b/public/assets/javascripts/rectangles/util/undo.js
new file mode 100644
index 0000000..dfc74dc
--- /dev/null
+++ b/public/assets/javascripts/rectangles/util/undo.js
@@ -0,0 +1,52 @@
+(function(){
+
+ var UndoStack = function(){
+ this.debug = true
+ this.stack = []
+ this.types = {}
+ this.pointer = -1
+ }
+ UndoStack.prototype.push = function(action){
+ this.pointer++
+ this.stack[this.pointer] = action
+ this.purge()
+ }
+ UndoStack.prototype.purge = function(){
+ if (this.stack.length-1 == this.pointer) return
+ this.stack.length = this.pointer+1
+ }
+ UndoStack.prototype.undo = function(){
+ if (this.pointer == -1) return false
+ var action = this.stack[this.pointer]
+ this.debug && console.log("undo", action.type)
+ this.types[ action.type ].undo(action.prev)
+ this.pointer--
+ return this.pointer > -1
+ }
+ UndoStack.prototype.redo = function(){
+ if (this.pointer == this.stack.length-1) return false
+ this.pointer++
+ var action = this.stack[this.pointer]
+ this.debug && console.log("redo", action.type)
+ this.types[ action.type ].redo(action.next)
+ return this.pointer < this.stack.length-1
+ }
+ UndoStack.prototype.register = function(actionType){
+ if (actionType.length) {
+ actionType.forEach(this.registerOne.bind(this))
+ }
+ else {
+ this.registerOne(actionType)
+ }
+ }
+ UndoStack.prototype.registerOne = function(actionType){
+ this.types[ actionType.type ] = actionType
+ }
+ if ('window' in this) {
+ window.UndoStack = new UndoStack
+ }
+ else {
+ module.exports = new UndoStack
+ }
+
+})()
diff --git a/public/assets/javascripts/ui/builder/BuilderInfo.js b/public/assets/javascripts/ui/builder/BuilderInfo.js
index 56f1338..2fffdba 100644
--- a/public/assets/javascripts/ui/builder/BuilderInfo.js
+++ b/public/assets/javascripts/ui/builder/BuilderInfo.js
@@ -96,86 +96,3 @@ var BuilderInfo = View.extend({
},
})
-
-$.fn.resetUnitVal = function(){
- this.each(function(){
- var n = $(this).data("px")
- $(this).unitVal(n)
- });
-}
-
-$.fn.unitVal = function(n){
- var s
- if (typeof n === "undefined") {
- s = $(this).val()
- n = stringToMeasurement( s )
- if (! n || isNaN(n)) {
- n = $(this).data("px")
- }
- }
- s = measurementToString( n )
- $(this).val( s ).data("px", n)
- return n
-}
-
-function measurementToString( n ) {
- var s, ft, inch
- switch (app.units) {
- case 'm':
- s = round(n/36 * 0.3048 * 100) / 100 + " m"
- break
- case 'ft':
- ft = floor(n / 36)
- inch = abs(round((n % 36) / 3))
- s = ft + "'"
- if (inch > 0) {
- s += " " + inch + '"'
- }
- break
- case 'px':
- default:
- s = round(n) + " px"
- break
- }
- return s
-}
-function stringToMeasurement( s ) {
- var ft, inch, ft_in, type
- if (! s.match(/[0-9]/)) {
- return NaN
- }
- if (s.indexOf("'") !== -1 || s.indexOf('"') !== -1 || s.indexOf('ft') !== -1) {
- ft_in = s.match(/[0-9.]+/g)
- if (ft_in.length >= 2) {
- ft = parseFloat( ft_in[0] )
- inch = parseFloat( ft_in[1] )
- }
- else if (ft_in.length == 1) {
- if (s.indexOf('"') !== -1) {
- ft = 0
- inch = parseFloat( ft_in[0] )
- }
- else {
- ft = parseFloat( ft_in[0] )
- inch = 0
- }
- }
- else {
- ft = inch = 0
- }
- n = ft * 36 + inch * 3
- }
- else if (s.indexOf("m") !== -1) {
- n = parseFloat(s.match(/[0-9.]+/)) * 36 / 0.3048
- }
- else if (s.indexOf("px") !== -1) {
- n = parseFloat(s.match(/[0-9.]+/))
- }
- else {
- n = abs( stringToMeasurement( s + app.units ) )
- }
- if (s.indexOf('-') !== -1) {
- n *= -1
- }
- return n
-} \ No newline at end of file
diff --git a/public/assets/javascripts/ui/builder/BuilderToolbar.js b/public/assets/javascripts/ui/builder/BuilderToolbar.js
index df98ab0..2eb7590 100644
--- a/public/assets/javascripts/ui/builder/BuilderToolbar.js
+++ b/public/assets/javascripts/ui/builder/BuilderToolbar.js
@@ -6,15 +6,22 @@ var BuilderToolbar = View.extend({
"click [data-role='toggle-map-view']": 'toggleMap',
"click [data-role='toggle-layout-settings']": 'toggleSettings',
"click [data-role='undo']": 'undo',
- "click [data-role='create-mode']": 'create',
- "click [data-role='resize-mode']": 'resize',
- "click [data-role='move-mode']": 'move',
+// "click [data-role='create-mode']": 'create',
+// "click [data-role='resize-mode']": 'resize',
+// "click [data-role='move-mode']": 'move',
"click [data-role='destroy-mode']": 'destroy',
},
initialize: function(opt){
this.parent = opt.parent
- map.ui.permissions.toggle()
+ this.resetPermissions()
+ },
+
+ resetPermissions: function(){
+ map.ui.permissions.clear()
+ map.ui.permissions.add("create")
+ map.ui.permissions.add("move")
+ map.ui.permissions.add("resize")
},
toggleMap: function(){
@@ -27,7 +34,8 @@ var BuilderToolbar = View.extend({
undo: function(){
},
-
+
+/*
create: function(e){
var state = map.ui.permissions.toggle("create")
$(".inuse").removeClass("inuse")
@@ -45,7 +53,8 @@ var BuilderToolbar = View.extend({
$(".inuse").removeClass("inuse")
$(e.currentTarget).toggleClass("inuse", state)
},
-
+*/
+
destroy: function(e){
var state = map.ui.permissions.toggle("destroy")
$(".inuse").removeClass("inuse")
diff --git a/public/assets/javascripts/ui/editor/MediaEditor.js b/public/assets/javascripts/ui/editor/MediaEditor.js
index cd8fb63..b9eb8fc 100644
--- a/public/assets/javascripts/ui/editor/MediaEditor.js
+++ b/public/assets/javascripts/ui/editor/MediaEditor.js
@@ -11,6 +11,9 @@ var MediaEditor = FormView.extend({
"change [name=autoplay]": "setAutoplay",
"change [name=loop]": "setLoop",
"change [name=mute]": "setMute",
+ "change [name=width]": 'changeWidth',
+ "change [name=height]": 'changeHeight',
+ "change [name=units]": 'changeUnits',
"click [data-role=destroy-media]": "destroy",
},
@@ -22,8 +25,8 @@ var MediaEditor = FormView.extend({
this.$description = this.$("[name=description]")
// image fields
- this.$widthDimension = this.$("[name=width]")
- this.$heightDimension = this.$("[name=height]")
+ this.$width = this.$("[name=width]")
+ this.$height = this.$("[name=height]")
this.$units = this.$("[name=units]")
// video fields
@@ -55,16 +58,14 @@ var MediaEditor = FormView.extend({
this.$name.val(media.title)
this.$description.val(media.description)
+ this.setDimensions()
+ this.$units.val( "ft" )
switch (media.type) {
case "image":
this.$(".image").show()
this.$(".video").hide()
-
- this.$widthDimension.val( Number(media.widthDimension) || "" )
- this.$heightDimension.val( Number(media.heightDimension) || "" )
- this.$units.val( media.units || "cm" )
-
+
break
case "youtube":
@@ -113,6 +114,25 @@ var MediaEditor = FormView.extend({
this.scenery.mute(checked)
},
+ setDimensions: function(){
+ this.$width.unitVal( Number(this.scenery.dimensions.a * this.scenery.scale) || "" )
+ this.$height.unitVal( Number(this.scenery.dimensions.b * this.scenery.scale) || "" )
+ },
+ changeWidth: function(e){
+ e.stopPropagation()
+ this.scenery.set_scale( this.$width.unitVal() / this.scenery.dimensions.a )
+ this.setDimensions()
+ },
+ changeHeight: function(e){
+ e.stopPropagation()
+ this.scenery.set_scale( this.$height.unitVal() / this.scenery.dimensions.b )
+ this.setDimensions()
+ },
+ changeUnits: function(){
+ app.units = this.$units.val()
+ this.$('.units').resetUnitVal()
+ },
+
bind: function(scenery){
this.scenery = scenery
this.scenery.mx.bound = true
diff --git a/public/assets/stylesheets/app.css b/public/assets/stylesheets/app.css
index a5b1733..36cccef 100755
--- a/public/assets/stylesheets/app.css
+++ b/public/assets/stylesheets/app.css
@@ -230,6 +230,12 @@ iframe.embed {
color:white;
}
+#projectList .editBtn {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+}
+
/*
.room1 {
position: relative;
@@ -240,12 +246,6 @@ iframe.embed {
background-image:url(https://s3.amazonaws.com/luckyplop/fd4ebe8a7a4246c8273fc999fb1ef0d6a8260b8c.png);
}
-.room1 .editBtn {
- position: absolute;
- right: 10px;
- top: 10px;
-}
-
.room1 form textarea {
width: 226px;
}
@@ -526,20 +526,25 @@ iframe.embed {
position:fixed;
top:0;
left:0;
- cursor: -webkit-grab;
- cursor: -moz-grab;
z-index: 1;
position: fixed;
top: 50%;
left: 50%;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
- cursor:pointer;
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
}
.mx-scene:active{
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
+.mx-scenery {
+ cursor: pointer;
+}
+.mx-scenery:active {
+ cursor: pointer;
+}
.mx-object3d.image,
.mx-object3d.video,
.mx-object3d iframe,
@@ -573,6 +578,28 @@ iframe.embed {
z-index: 10;
}
+#map {
+ cursor: crosshair;
+}
+#map.move {
+ cursor: move;
+}
+#map.ew-resize {
+ cursor: ew-resize;
+}
+#map.ns-resize {
+ cursor: ns-resize;
+}
+#map.nesw-resize {
+ cursor: nesw-resize;
+}
+#map.nwse-resize {
+ cursor: nwse-resize;
+}
+#map.destroy {
+ cursor: url(/assets/img/destroy-cursor.png) 12 12, auto;
+}
+
.face {
background-color: #fff;
transition: 0.1s background-color ease;
@@ -581,11 +608,10 @@ iframe.embed {
.back { background-color: #fff; }
.left { background-color: #fff; }
.right { background-color: #fff; }
-.floor { background-color: #eee; }
-.ceiling { background-color: rgba(230,230,255,0.3); }
+.floor { background-color: #f8f8f8; }
+.ceiling { background-color: rgba(255,525,255,0.3); }
-.active.floor { background-color: #ffe; }
-.active.ceiling { background-color: rgba(230,230,255,0.3); }
+.active.floor { background-color: #f8f8f8; }
.dragging .mx-object3d.image {
pointer-events: none;
@@ -1269,6 +1295,13 @@ input[type="range"]::-webkit-slider-thumb {
top: 0px;
}
+#mediaEditor .setting.number label {
+ width: 40px;
+}
+#mediaEditor .setting.number [type=text] {
+ width: 140px;
+}
+
.playButton,.muteButton {
color: white;
background: black;
diff --git a/server/lib/middleware.js b/server/lib/middleware.js
index aec54ad..27b9c04 100644
--- a/server/lib/middleware.js
+++ b/server/lib/middleware.js
@@ -36,7 +36,7 @@ var middleware = {
ensureLocals: function (req, res, next) {
res.locals.token = req.csrfToken();
res.locals.logged_in = req.isAuthenticated()
- res.locals.user = req.user || {}
+ res.locals.user = req.user || { id: undefined }
res.locals.config = config
res.locals.profile = null
res.locals.opt = {}
diff --git a/server/lib/views.js b/server/lib/views.js
index 27b7446..d619d71 100644
--- a/server/lib/views.js
+++ b/server/lib/views.js
@@ -36,6 +36,7 @@ views.editor = function (req, res) {
date: moment(req.project.updated_at).format("M/DD/YYYY"),
author: user.displayName,
authorlink: "/profile/" + user.username,
+ noui: !! (req.query.noui === 1),
})
})
}
@@ -60,6 +61,7 @@ views.reader = function (req, res) {
date: moment(req.project.updated_at).format("M/DD/YYYY"),
author: user.displayName,
authorlink: "/profile/" + user.username,
+ noui: !! (req.query.noui === 1),
})
})
}
diff --git a/test/00-setup.js b/test/00-setup.js
index 78ad2c4..20f9d66 100644
--- a/test/00-setup.js
+++ b/test/00-setup.js
@@ -1,2 +1 @@
Error.stackTraceLimit = 5
-
diff --git a/test/09-test-undo.js b/test/09-test-undo.js
new file mode 100644
index 0000000..f774c04
--- /dev/null
+++ b/test/09-test-undo.js
@@ -0,0 +1,164 @@
+var assert = require("assert")
+var UndoStack = require("../public/assets/javascripts/rectangles/util/undo.js")
+UndoStack.debug = false
+
+describe('undo', function(){
+
+ var state = "zero"
+
+ describe('#register()', function(){
+
+ UndoStack.register({
+ type: "demo",
+ undo: function(myState){
+ state = myState
+ },
+ redo: function(myState){
+ state = myState
+ },
+ })
+
+ it('registers undoable actions', function(){
+ assert( UndoStack.types.hasOwnProperty("demo") )
+ })
+ })
+
+ describe('#push()', function(){
+
+ it('starts empty', function(){
+ assert.equal(0, UndoStack.stack.length)
+ assert.equal(-1, UndoStack.pointer)
+ })
+
+ it('pushes some actions', function(){
+
+ UndoStack.push({
+ type: "demo",
+ prev: state,
+ next: "one"
+ })
+ state = "one"
+
+ UndoStack.push({
+ type: "demo",
+ prev: state,
+ next: "two"
+ })
+ state = "two"
+
+ UndoStack.push({
+ type: "demo",
+ prev: state,
+ next: "three"
+ })
+ state = "three"
+
+ assert.equal(3, UndoStack.stack.length)
+ assert.equal(2, UndoStack.pointer)
+ })
+ })
+
+ describe('#undo()', function(){
+
+ it('retrieves old state', function(){
+ assert.equal("three", state)
+ UndoStack.undo()
+ assert.equal("two", state)
+ assert.equal(1, UndoStack.pointer)
+ })
+
+ it('can only undo so far', function(){
+ var canUndo
+
+ canUndo = UndoStack.undo()
+ assert.equal("one", state)
+ assert.equal(0, UndoStack.pointer)
+ assert.equal(true, canUndo)
+
+ canUndo = UndoStack.undo()
+ assert.equal("zero", state)
+ assert.equal(-1, UndoStack.pointer)
+ assert.equal(false, canUndo)
+
+ canUndo = UndoStack.undo()
+ assert.equal("zero", state)
+ assert.equal(-1, UndoStack.pointer)
+ assert.equal(false, canUndo)
+ })
+
+ })
+
+ describe('#redo()', function(){
+
+ it('reassigns new state', function(){
+ UndoStack.redo()
+ assert.equal("one", state)
+ })
+
+ it('can only redo so far', function(){
+ var canRedo
+
+ canRedo = UndoStack.redo()
+ assert.equal("two", state)
+ assert.equal(true, canRedo)
+
+ canRedo = UndoStack.redo()
+ assert.equal("three", state)
+ assert.equal(false, canRedo)
+
+ canRedo = UndoStack.redo()
+ assert.equal("three", state)
+ assert.equal(false, canRedo)
+ })
+
+ it('clobbers old state if we undo then do something else', function(){
+ UndoStack.undo()
+ assert.equal("two", state)
+ assert.equal(1, UndoStack.pointer)
+
+ UndoStack.undo()
+ assert.equal("one", state)
+ assert.equal(0, UndoStack.pointer)
+ assert.equal(3, UndoStack.stack.length)
+
+ UndoStack.push({
+ type: "demo",
+ prev: state,
+ next: "four"
+ })
+ state = "four"
+
+ assert.equal(1, UndoStack.pointer)
+ assert.equal(2, UndoStack.stack.length)
+
+ UndoStack.undo()
+ assert.equal("one", state)
+ assert.equal(0, UndoStack.pointer)
+ assert.equal(2, UndoStack.stack.length)
+
+ UndoStack.redo()
+ assert.equal("four", state)
+ assert.equal(1, UndoStack.pointer)
+ assert.equal(2, UndoStack.stack.length)
+ })
+ })
+
+})
+
+// 1) push action
+// 2) push action
+// 3) push action
+// undo 3
+// undo 2
+// undo 1
+// undo * can't undo anymore
+// redo 1
+// redo 2
+// redo 3
+// redo * can't redo anymore
+// undo 3
+// 4) push action (clobbers action 3)
+// undo 4
+// undo 2
+// redo 2
+// redo 4
diff --git a/views/controls/builder/toolbar.ejs b/views/controls/builder/toolbar.ejs
index 98aee1e..a00249c 100644
--- a/views/controls/builder/toolbar.ejs
+++ b/views/controls/builder/toolbar.ejs
@@ -3,6 +3,7 @@
data-role='toggle-map-view'
data-info="toggle map view"
class="icon-ios7-photos-outline"></span>
+<!--
<span
data-role='create-mode'
data-info="draw"
@@ -15,6 +16,7 @@
data-role='resize-mode'
data-info="resize"
class="icon-arrow-resize"></span>
+-->
<span
data-role='destroy-mode'
data-info="delete"
diff --git a/views/controls/editor/media-editor.ejs b/views/controls/editor/media-editor.ejs
index 5db1fb2..7f8f299 100644
--- a/views/controls/editor/media-editor.ejs
+++ b/views/controls/editor/media-editor.ejs
@@ -34,13 +34,19 @@
<input type="range" min="0" max="1" value="0" step="0.01" name="keyframe" id="video-keyframe">
</div>
- <div class="image setting">
- Dimensions<br>
- <input type="text" name="width" placeholder="width" class="number">
- <input type="text" name="height" placeholder="height" class="number">
- <select name="units">
- <option value="inch">inch</option>
- <option value="cm">cm</option>
+ <div class="setting number">
+ <label for="scenery-width">width</label>
+ <input type="text" class="units" name="width" id="scenery-width">
+ </div>
+ <div class="setting number">
+ <label for="scenery-height">height</label>
+ <input type="text" class="units" name="height" id="scenery-height">
+ </div>
+ <div class="setting number">
+ <select id="builder-units" name="units">
+ <option value="px">pixels</option>
+ <option value="ft">foot</option>
+ <option value="m">meter</option>
</select>
</div>
diff --git a/views/partials/scripts.ejs b/views/partials/scripts.ejs
index a1f51ef..087c0d7 100644
--- a/views/partials/scripts.ejs
+++ b/views/partials/scripts.ejs
@@ -23,11 +23,13 @@
<script type="text/javascript" src="/assets/javascripts/rectangles/util/colors.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/util/debug.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/util/permissions.js"></script>
+<script type="text/javascript" src="/assets/javascripts/rectangles/util/measurement.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/util/sort.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/util/uid.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/util/wheel.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/util/mouse.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/util/keys.js"></script>
+<script type="text/javascript" src="/assets/javascripts/rectangles/util/undo.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/models/vec2.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/models/vec3.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/models/mat4.js"></script>
@@ -44,6 +46,7 @@
<script type="text/javascript" src="/assets/javascripts/rectangles/engine/scenery/_scenery.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/engine/scenery/move.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/engine/scenery/resize.js"></script>
+<script type="text/javascript" src="/assets/javascripts/rectangles/engine/scenery/undo.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/engine/scenery/types/_object.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/engine/scenery/types/image.js"></script>
<script type="text/javascript" src="/assets/javascripts/rectangles/engine/scenery/types/video.js"></script>
diff --git a/views/projects/list-projects.ejs b/views/projects/list-projects.ejs
index d2c0447..c78bf9f 100644
--- a/views/projects/list-projects.ejs
+++ b/views/projects/list-projects.ejs
@@ -14,10 +14,13 @@
<td class="border room1">
[[ } ]]
<iframe src="/project/[[- project.slug ]]/view?noui=1&mute=1" class="embed"></iframe>
- [[ if (profile && profile._id == project.user_id) { ]]
- <div class="editBtn">edit</div>
+ [[ if (String(user._id) == String(project.user_id)) { ]]
+ <a href="/project/[[- project.slug ]]/edit"><div class="editBtn">edit</div></a>
[[ } ]]
- <a href="/project/[[- project.slug ]]" class="roomName">[[- project.name ]]<br>[[- project.date ]]</a>
+ <a href="/project/[[- project.slug ]]" class="roomName">
+ [[- project.name ]]<br>
+ [[- project.date ]]
+ </a>
</td>
[[ }) ]]
diff --git a/views/reader.ejs b/views/reader.ejs
index 44fb2dd..7c31766 100644
--- a/views/reader.ejs
+++ b/views/reader.ejs
@@ -8,6 +8,7 @@
<div id="scene"></div>
+ [[ if (! noui) { ]]
<div class="rapper">
[[ include partials/header ]]
@@ -16,6 +17,7 @@
[[ include controls/reader/media-player ]]
</div>
</div>
+ [[ } ]]
[[ include partials/confirm-modal ]]
[[ include projects/layouts-modal ]]