commit 6e19218dfcff0330348a22e5182e951ab74266a8
parent 7c594a6798cf848220a830268a21d7d345a7fd43
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date: Mon, 9 Sep 2024 14:10:10 -0700
Add some more furniture manipulation functions
You can now move furniture and easily edit the parameters of existing
furniture.
Diffstat:
4 files changed, 206 insertions(+), 99 deletions(-)
diff --git a/files/floorplans/floorplan/backend.js b/files/floorplans/floorplan/backend.js
@@ -86,6 +86,8 @@ class BackendHistory {
return group
}
+ this.truncate()
+
if (this.groups.length === 0) {
return pushGroup(this)
}
@@ -559,45 +561,65 @@ export class FloorplanBackend {
}
mapFurniture(params, id) {
- let fm = id ? this.reqObj(id) : {}
-
- if (params.furniture_id != undefined) {
- if (this.obj(params.furniture_id) == undefined) {
- throw new Error("invalid furniture id for furniture map")
- }
- fm.furniture_id = params.furniture_id
- } else if (fm.furniture_id == undefined) {
- throw new Error("Missing furniture id")
- }
- if (params.x != undefined) {
- if (typeof params.x !== "number") {
- throw new Error("Invalid x coordinate")
+ let backend = this
+ const validInt = function(input, cur) {
+ let x = Math.round(input ?? cur)
+ if (isNaN(x)) {
+ throw new Error(input + " is NaN")
}
- fm.x = params.x
+ return x
}
- if (params.y != undefined) {
- if (typeof params.x !== "number") {
- throw new Error("Invalid y coordinate")
+ const validSize = function(input, cur) {
+ let x = validInt(input, cur)
+ if (x <= 0) {
+ throw new Error(input + " must be a positive number")
}
- fm.y = params.y
- }
- if (params.angle != undefined) {
- if (typeof params.angle !== "number" || params.angle < 0 ||
- params.angle >= 360) {
- throw new Error(params.angle + ": Invalid angle")
+ return x
+ }
+ let parsers = {
+ x: validInt,
+ y: validInt,
+ width: validSize,
+ depth: validSize,
+ angle: function(input, cur) {
+ if (input == undefined) {
+ return cur ?? 0
+ }
+ let x = validInt(input)
+ if (x < 0 || x >= 360) {
+ throw new Error(input + ": Angle must be between 0 and 359 degrees")
+ }
+ return x
+ },
+ layout: function(input, cur) {
+ if (input == undefined) {
+ return cur ?? "1"
+ }
+ if (typeof input !== "string") {
+ throw new Error(input + ": Layout should be a string")
+ }
+ return input
+ },
+ furniture_id: function(id, cur) {
+ if (id == undefined) {
+ if (cur == null) {
+ throw new Error("Missing furniture id")
+ }
+ return cur
+ }
+ if (backend.obj(id) == undefined) {
+ throw new Error("invalid furniture id for furniture map")
+ }
+ return id
}
- fm.angle = params.angle
}
- if (params.layout != undefined) {
- if (typeof params.layout !== "string") {
- throw new Error(params.layout + ": Invalid layout")
- }
- fm.layout = params.layout
- } else {
- // for now, this should be handled by the server later
- fm.layout = "1"
+
+ let fm = id ? this.reqObj(id) : {}
+ for (let param in parsers) {
+ fm[param] = parsers[param](params[param], fm ? fm[param] : undefined)
}
- return this.addData("furniture_maps", fm)
+
+ return this.addData(id ?? "furniture_maps", fm)
}
unmapFurniture(id, options) {
@@ -606,7 +628,7 @@ export class FloorplanBackend {
addMappedFurniture(params, id) {
params.furniture_id = this.addFurniture(params, id ? this.reqObj(id).furniture_id : null)
- return this.mapFurniture(params)
+ return this.mapFurniture(params, id)
}
reqObj(id) {
diff --git a/files/floorplans/floorplan/editor.js b/files/floorplans/floorplan/editor.js
@@ -539,17 +539,19 @@ export class FloorplanEditor {
}
addFurniture(params, id) {
- this.backend.addFurniture(params, id)
+ return this.backend.addFurniture(params, id)
}
mapFurniture(params, id) {
- this.backend.mapFurniture(params, id)
+ id = this.backend.mapFurniture(params, id)
this.updateDisplay()
+ return id
}
addMappedFurniture(params, id) {
- this.backend.addMappedFurniture(params, id)
+ id = this.backend.addMappedFurniture(params, id)
this.updateDisplay()
+ return id
}
selectedPoints() {
@@ -640,27 +642,28 @@ export class FloorplanEditor {
}
},
furniture: function(id, value) {
- let maps = editor.backend.cache
- for (let id in maps) {
- if (maps[id].furniture_id == id) {
- let m = editor.draw.findOneMax(byId(id))
+ let maps = editor.backend.cache.furniture_maps
+ for (let mid in maps) {
+ if (maps[mid].furniture_id == id) {
+ let m = editor.draw.findOneMax(byId(mid))
if (m != null) {
m.size(value.width, value.depth)
+ console.log("New name", furniture_name(value))
+ m.findOne("title").words(furniture_name(value))
}
}
}
},
furniture_maps: function(id, value) {
let fm = editor.draw.findOneMax(byId(id))
- let f = editor.backend.cache.furniture[value.furniture_id]
if (!fm) {
- console.log(f, editor.layoutG())
+ let f = editor.backend.reqObj(value.furniture_id)
fm = editor.layoutG().rect(f.width, f.depth)
- .cx(value.x).cy(value.y)
.fill("black")
.attr({ id })
+ fm.element("title").words(furniture_name(f))
}
- fm.element("title").words(f.name ?? f.type)
+ fm.cx(value.x).cy(value.y)
fm.transform({
rotate: value.angle
})
@@ -803,3 +806,7 @@ export function getID(thing, type) {
function byId(id) {
return "#" + id
}
+
+function furniture_name(f) {
+ return f.name ? `${f.name} (${f.type})` : f.type
+}
diff --git a/files/floorplans/floorplan/main.js b/files/floorplans/floorplan/main.js
@@ -117,8 +117,6 @@ function init() {
)
))
- editor.draw.on("select", function(event) { selectHandler(event, editor) })
-
editor.backend.pull()
.then(function() {
if (editor.draw.findExactlyOne("#points").children().length === 0) {
@@ -127,7 +125,7 @@ function init() {
})
}
-function selectHandler(event, editor) {
+function selectHandler(event, editor, state) {
let old = document.getElementById("selOps")
if (!event.detail.selected) {
if (old) {
@@ -172,6 +170,21 @@ function selectHandler(event, editor) {
)
}
+ let fmaps = ids.filter(function(item) { return backend.idType(item) === "furmap" })
+ if (fmaps.length === 1) {
+ c.appendChild(
+ ui.input("edit", "Edit selected furniture", {
+ attributes: {
+ type: "button",
+ value: "Edit furniture"
+ },
+ handlers: { click: function() {
+ document.body.appendChild(furnitureMenu(editor, fmaps[0]))
+ }}
+ })
+ )
+ }
+
if (old) {
old.replaceWith(c)
} else {
@@ -239,10 +252,11 @@ let modes = {
handlers: {
contextmenu: preventDefaultHandler,
mousedown: [selectionHandler, precisePointHandler, furnitureHandler],
- mousemove: [precisePointHandler, precisePointMapHandler],
- mouseup: precisePointHandler,
+ mousemove: [precisePointHandler, precisePointMapHandler, furnitureHandler],
+ mouseup: [precisePointHandler, furnitureHandler],
keydown: [zoomKeysHandler, undoRedoHandler],
- dblclick: precisePointMapHandler
+ dblclick: [precisePointMapHandler, furnitureHandler],
+ select: selectHandler
}
}
}
@@ -606,51 +620,116 @@ function precisePointMapHandler(event, editor) {
}
}
-function furnitureHandler(ev, editor) {
- if (ev.type != "mousedown" || ev.button !== buttons.left) {
- return
- }
- if (editor.draw.findOne(".selected") != null) {
- return
+// mousedown, mouseup, mousemove, dblclick
+function furnitureHandler(ev, editor, state) {
+ const doMove = function() {
+ // racy
+ if (state.move) {
+ let id = state.moving.attr("id")
+ let p = editor.draw.point(state.move.x, state.move.y)
+ editor.mapFurniture({ x: p.x, y: p.y }, id)
+ delete state.move
+ }
}
- ev.preventDefault()
- let menu = document.getElementById("furniture_menu")
- if (menu != null) {
- menu.remove()
+
+ let sel = editor.draw.find("#furniture_layouts > * > .selected").array()
+ if (sel.length === 0 && ev.type === "dblclick" && ev.button === buttons.left) {
+ ev.preventDefault()
+ if (state.menu) {
+ state.menu.remove()
+ delete state.menu
+ }
+ state.menu = document.body.appendChild(
+ furnitureMenu(editor, editor.draw.point(ev.clientX, ev.clientY)))
+ } else if (sel.length === 1) {
+ if (ev.type === "dblclick") {
+ ev.preventDefault()
+ document.body.appendChild(furnitureMenu(editor, lib.getID(sel[0])))
+ } else if (ev.type === "mousedown") {
+ ev.preventDefault()
+ state.moving = sel[0]
+ return
+ } else {
+ if (!state.moving) {
+ return
+ }
+ if (ev.type === "mouseup") {
+ ev.preventDefault()
+ doMove()
+ delete state.moving
+ return
+ } else if (ev.type === "mousemove") {
+ ev.preventDefault()
+ if (state.move) {
+ state.move = { x: ev.clientX, y: ev.clientY }
+ } else {
+ state.move = { x: ev.clientX, y: ev.clientY }
+ setTimeout(doMove, 60)
+ }
+ }
+ }
}
- menu = furnitureMenu(editor, editor.draw.point(ev.clientX, ev.clientY))
- menu.id = "furniture_menu"
- document.body.append(menu)
}
function enumSelection(input, values) {
let a = typeof(values.keys) === "function"
for (let i in values) {
- console.log("EnumSel", i, values[i])
let opt = input.appendChild(document.createElement("option"))
opt.appendChild(document.createTextNode(a ? values[i] : i))
}
}
-function furnitureMenu(editor, p) {
- if (p == null) {
- p = { x: 0, y: 0 }
+function furnitureMenu(editor, pointOrID) {
+ console.warn("Furniture menu")
+ const def = function(obj) {
+ return obj[defKey(obj)]
}
-
- let defaultType
- for (let t in editor.furniture_types) {
- defaultType = t
- break
+ const defKey = function(obj) {
+ for (let i in obj) {
+ return i
+ }
}
+ editor.finishAction()
+ let p
+ let id
+ let params
+ if (typeof pointOrID === "string") {
+ id = pointOrID
+ params = editor.backend.reqObj(id)
+ let fp = editor.backend.reqObj(params.furniture_id)
+ for (let k in fp) {
+ params[k] = fp[k]
+ }
+ } else {
+ if (pointOrID == null) {
+ p = { x: 0, y: 0 }
+ } else if (typeof pointOrID === "object") {
+ p = pointOrID
+ }
+ let type = defKey(editor.furniture_types)
+ console.log(type, editor.furniture_types)
+ let v = def(editor.furniture_types[type].varieties)
+ params = {
+ x: p.x,
+ y: p.y,
+ type,
+ width: v.width ?? 9600,
+ depth: v.height ?? 9600,
+ name: null
+
+ }
+ id = editor.addMappedFurniture(params)
+ }
let items = [
menuItem("name", "Name"),
- menuItem("type", "Type", { break: false, enum: editor.furniture_types, attributes: { required: true } }),
- menuItem("variety", "Variety", { enum: editor.furniture_types[defaultType].varieties }),
- menuItem("width", "Width", { attributes: { required: true } }),
- menuItem("depth", "Depth", { attributes: { required: true } }),
- menuItem("angle", "Angle", { attributes: { min: 0, max: 359, type: "number", value: 0, required: true } }),
- menuItem("add", null, { attributes: { value: "Add", type: "Submit" } })
+ menuItem("type", "Type", { break: false, enum: editor.furniture_types, attributes: { value: params.type, required: true } }),
+ menuItem("variety", "Variety", { enum: editor.furniture_types[params.type].varieties }),
+ menuItem("width", "Width", { attributes: { value: params.width, required: true } }),
+ menuItem("depth", "Depth", { attributes: { value: params.height, required: true } }),
+ menuItem("angle", "Angle", { attributes: { value: params.angle, min: 0, max: 359, type: "number", value: 0, required: true } }),
+ menuItem("cancel", null, { attributes: { value: "Cancel", type: "button" }}),
+ menuItem("done", null, { attributes: { value: "Done", type: "submit" }})
]
let keys = {}
for (let i in items) {
@@ -658,14 +737,16 @@ function furnitureMenu(editor, p) {
}
const fromVariety = function(type, variety) {
+ console.log("Loading width & depth from variety")
let v
if (variety == null) {
v = { width: null, depth: null }
} else {
v = editor.furniture_types[type].varieties[variety]
}
- items[keys.width].input.value = v.width
- items[keys.depth].input.value = v.depth
+ params.width = items[keys.width].input.value = v.width
+ params.depth = items[keys.depth].input.value = v.depth
+ editor.addMappedFurniture(params, id)
}
const newVariety = function() {
let vars = editor.furniture_types[items[keys.type].input.value].varieties
@@ -689,29 +770,26 @@ function furnitureMenu(editor, p) {
items[keys.type].input.addEventListener("change", function(ev) {
newVariety()
})
- menu.addEventListener("submit", function(ev) {
+ items[keys.cancel].input.addEventListener("click", function(ev) {
+ ev.preventDefault()
+ editor.undo()
+ menu.remove()
+ })
+ menu.addEventListener("change", function(ev) {
ev.preventDefault()
try {
- let name = items[keys.name].input.value
- if (name.length === 0) {
- name = null
- }
- let params = {
- type: items[keys.type].input.value,
- x: p.x,
- y: p.y,
- width: Number(items[keys.width].input.value),
- depth: Number(items[keys.depth].input.value),
- angle: Number(items[keys.angle].input.value),
- name
- }
- editor.addMappedFurniture(params)
- editor.finishAction()
+ params[event.target.name] = event.target.value
+ console.log("Change", event.target.name, event.target.value)
+ editor.addMappedFurniture(params, id)
}
catch(err) {
etc.error(err, menu)
throw err
}
+ })
+ menu.addEventListener("submit", function(ev) {
+ ev.preventDefault()
+ editor.finishAction()
menu.remove()
})
@@ -782,7 +860,7 @@ function menuItem(name, label, options) {
if (options.value != undefined) {
attributes.value = options.value
}
- if (attributes.title == undefined) {
+ if (attributes.title == undefined && label != undefined) {
attributes.title = label
}
diff --git a/files/lib/etc.js b/files/lib/etc.js
@@ -84,7 +84,7 @@ export function error(message, on) {
err_elem.append(msg)
err_elem.append(ui.button("Dismiss", "Dismiss error", "close", { handlers: { click: { function() { err_elem.remove() } } } }))
- on.before(err_elem)
+ on.prepend(err_elem)
}
}