commit 8dc6ee418eaafd9bb057433478ebfc2a70a0067d
parent 262c9089ffd3bb2168fb1fd18822b2a599907bb2
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date: Tue, 3 Sep 2024 17:01:18 -0700
Add very basic furniture support
All you can do is place furniture by clicking on an empty space in
the floorplan, and delete it by pressing delete in the selection
menu, but I think it's got all the boiler-plate done and is ready
to have the nuts and bolts added.
This also fixes a bug in history.updateIds where the id wouldn't
be updated for pointmaps, I believe.
Diffstat:
4 files changed, 206 insertions(+), 9 deletions(-)
diff --git a/files/floorplans/floorplan/backend.js b/files/floorplans/floorplan/backend.js
@@ -291,13 +291,18 @@ class BackendHistory {
let diff = this.diffs[i]
let r = parsePath(diff.path)
console.debug(r, type, oldId)
- if (r.type === "pointmaps" && type === "points") {
+ if (r.type === "furniture_maps" && type === "furniture") {
+ if (r.furniture_id === oldId) {
+ r.furniture_id = newId
+ }
+ } else if (r.type === "pointmaps" && type === "points") {
if (diff.value.a === oldId) {
diff.value.a = newId
} else if (diff.value.b === oldId) {
diff.value.b = newId
}
- } else if (r.type === type && r.id == oldId) {
+ }
+ if (r.type === type && r.id == oldId) {
// NOTE: Above r.id is string, oldId is number
console.debug("Backend.History.updateId", type, oldId, newId)
diff.path = diffPath(r.type, newId)
@@ -339,9 +344,23 @@ export class FloorplanBackend {
* [*] The only map types I think are needed are wall and door
* at the moment.
*/
- pointmaps: {}
+ pointmaps: {},
// There will be here more later, such as furnature
+
+ /*
+ * Furniture definitions:
+ * { id: { type: furnitureType, name: name, width: width, depth: depth } }
+ */
+ furniture: {},
+
+ /*
+ * Furniture map definitions:
+ * { id: { furniture_id*: id, layout: layout, x: x, y: y, angle: angle } }
+ *
+ * [*] references if from furniture/defs
+ */
+ furniture_maps: {}
}
// Reverse lookup table for pointmaps
@@ -509,6 +528,79 @@ export class FloorplanBackend {
}
}
+ addFurniture(type, options) {
+ options = options ?? {}
+ if (options.width == null || options.depth == null) {
+ throw new Error("These 'options' are not yet optional")
+ }
+
+ if (typeof type !== "string") {
+ throw new Error(type + ": Expected string type")
+ }
+
+ let f = {
+ type: type,
+ }
+ if (options.width != undefined) {
+ f.width = Math.round(options.width)
+ if (f.width <= 0) {
+ throw new Error(options.width + ": rounded width must be greater than zero")
+ }
+ }
+ if (options.depth != undefined) {
+ f.depth = Math.round(options.depth)
+ if (f.depth <= 0) {
+ throw new Error(options.depth + ": rounded depth must be greater than zero")
+ }
+ f.depth = options.depth
+ }
+ return this.addData("furniture", f)
+ }
+
+ removeFurniture(id, options) {
+ for (let map in this.cache.furniture_maps) {
+ this.unmapFurniture(map)
+ }
+ this.removeData(id, options)
+ }
+
+ mapFurniture(def, x, y, options) {
+ options = options ?? {}
+
+ let fm = {
+ furniture_id: def,
+ x: Math.round(x),
+ y: Math.round(y)
+ }
+ if (options.angle != undefined) {
+ if (typeof options.angle !== "number" || options.angle < 0 ||
+ options.angle >= 360) {
+ throw new Error(options.angle + ": Invalid angle")
+ }
+ fm.angle = options.angle
+ }
+ if (options.layout != undefined) {
+ if (typeof options.layout !== "string") {
+ throw new Error(options.layout + ": Invalid layout")
+ }
+ fm.layout = options.layout
+ } else {
+ // for now, this should be handled by the server later
+ fm.layout = "1"
+ }
+ return this.addData("furniture_maps", fm)
+ }
+
+ unmapFurniture(id, options) {
+ this.removeData("furniture_maps", id, options)
+ }
+
+ addMappedFurniture(type, x, y, options) {
+ let ref = this.addFurniture(type, options)
+ this.mapFurniture(ref.id, x, y, options)
+ return ref
+ }
+
reqId(type, id) {
let obj = this.byId(type, id)
if (!obj) {
@@ -773,6 +865,13 @@ function updateIds(backend, newdata) {
// cycle of the program. I'll probably forget and mess up but
// hopefully this provides some assistance.
backend.updateMappedPoints(x.a, x.b, id)
+ } else if (type === "furniture") {
+ let maps = backend.cache["furniture_maps"]
+ for (let mapid in maps) {
+ if (maps[mapid].furniture_id === x.old_id) {
+ maps[mapid].furniture_id = id
+ }
+ }
}
}
}
diff --git a/files/floorplans/floorplan/editor.js b/files/floorplans/floorplan/editor.js
@@ -287,6 +287,8 @@ export class FloorplanEditor {
let data = this.draw.group().attr({ id: "floorplan" })
data.group().attr({ id: "pointmaps" }) // lines
data.group().attr({ id: "points" }) // circles
+ this.layouts = data.group().attr({ id: "furniture_layouts" }) // g of furniture
+ this.layout = "1"
this.ui.top = this.draw.group().attr({ id: "top" })
@@ -477,6 +479,8 @@ export class FloorplanEditor {
let ref = getRef(elements[i])
if (ref.type === "pointmaps") {
this.backend.unmapPoints(ref.id)
+ } else if (ref.type === "furniture_maps") {
+ this.backend.unmapFurniture(ref.id)
} else {
later.push(ref)
}
@@ -485,8 +489,10 @@ export class FloorplanEditor {
for (let i in later) {
if (later[i].type === "points") {
this.backend.removePoint(later[i].id, { unmap: true })
+ } else if (later[i].type === "furniture_maps") {
+ this.backend.removeFurniture(later[i].id)
} else {
- throw new Error("Unsupported type")
+ throw new Error(later[i].type + ": Unsupported type")
}
}
@@ -540,6 +546,20 @@ export class FloorplanEditor {
return ref
}
+ addFurniture(type, options) {
+ this.backend.addFurniture(type, options)
+ }
+
+ mapFurniture(def, x, y, options) {
+ this.backend.mapFurniture(def, x, y, options)
+ this.updateDisplay()
+ }
+
+ addMappedFurniture(type, x, y, options) {
+ this.backend.addMappedFurniture(type, x, y, options)
+ this.updateDisplay()
+ }
+
selectedPoints() {
return {
a: this.selectedPoint(),
@@ -625,6 +645,21 @@ export class FloorplanEditor {
.addClass(value.type)
.data("type", value.type)
}
+ },
+ furniture: function() {},
+ furniture_maps: function(name, value) {
+ let fm = editor.draw.findOneMax(byId(name))
+ if (!fm) {
+ fm = editor.layoutG().rect(1600, 1600)
+ .cx(value.x).cy(value.y)
+ .fill("black")
+ .attr({ id: name })
+ }
+ let f = editor.backend.cache.furniture[value.furniture_id]
+ fm.element("title").words(f.name ?? f.type)
+ fm.transform({
+ rotate: value.angle
+ })
}
},
remove: {
@@ -634,6 +669,10 @@ export class FloorplanEditor {
},
pointmaps: function(name) {
editor.draw.findExactlyOne(byId(name)).remove()
+ },
+ furniture: function(name) {},
+ furniture_maps: function(name) {
+ editor.draw.findExactlyOne(byId(name)).remove()
}
}
}
@@ -653,7 +692,31 @@ export class FloorplanEditor {
ops[diff.op][ref.type](refId(ref), diff.value, ref)
}
+ switchLayout(name) {
+ if (this.layout != null) {
+ this.layouts.findExactlyOne(byId(layoutID(this.layout))).hide()
+ }
+ this.layouts.findExactlyOne(byId(layoutID(name))).show()
+ this.layout = name
+ }
+
+ layoutG(name) {
+ if (name == null) {
+ name = this.layout
+ }
+ let id = layoutID(name)
+ let layout = this.layouts.findOneMax(byId(id))
+ if (layout) {
+ return layout
+ }
+ layout = this.layouts.group().attr({id: id})
+ return layout
+ }
+
updateId(ids) {
+ if (ids.type == "furniture") {
+ return
+ }
let e = this.findRef(backend.newRef(ids.type, ids.old))
e.attr({ id: refId(backend.newRef(ids.type, ids.new)) })
console.log("Editor.updateId", `${ids.old} -> ${ids.new}`)
@@ -664,6 +727,10 @@ export class FloorplanEditor {
}
}
+function layoutID(name) {
+ return "layout_" + name
+}
+
function remove_mode_handlers(target, mode_handlers) {
for (let event in mode_handlers) {
for (let handler in mode_handlers[event]) {
@@ -749,10 +816,11 @@ export function getId(thing, type) {
export function idRef(id) {
let a = id.split("_")
- if (a.length != 2) {
+ if (a.length < 2) {
throw new Error(`${id}: Invalid id`)
}
- return backend.newRef(a[0], a[1])
+ id = a.pop()
+ return backend.newRef(a.join("_"), id)
}
function byId(id) {
diff --git a/files/floorplans/floorplan/main.js b/files/floorplans/floorplan/main.js
@@ -6,6 +6,7 @@ import * as lib from "./editor.js" // Confusing, but I don't want to fix variabl
import { Vector2 } from "/lib/github.com/ros2jsguy/threejs-math/math/Vector2.js"
import "./geometry.js"
import * as backend from "./backend.js"
+import * as api from "/lib/api.js"
const messageTimeout = 4000
@@ -53,6 +54,15 @@ function init() {
})
editor.useUnits("imperial")
editor.draw.viewbox(0, 0, editor.units.get("foot", 40), editor.units.get("foot", 40))
+ editor.draw.hide()
+ api.fetch("GET", "furniture")
+ .then(function(furniture) {
+ editor.furniture_types = furniture
+ editor.draw.show()
+ })
+ .catch(function(err) {
+ etc.error("That's unexpected. Unable to get furniture definitions")
+ })
let push = ui.button("Push", "Push updates", "arrow-up",
{ handlers: { click: function() { editor.backend.push(); notify("Pushed floorplan", "pushpull") } } })
@@ -228,7 +238,7 @@ let modes = {
points: true,
handlers: {
contextmenu: preventDefaultHandler,
- mousedown: [selectionHandler, precisePointHandler],
+ mousedown: [selectionHandler, precisePointHandler, furnitureHandler],
mousemove: [precisePointHandler, precisePointMapHandler],
mouseup: precisePointHandler,
keydown: [zoomKeysHandler, undoRedoHandler],
@@ -245,7 +255,13 @@ function selectionHandler(event, editor) {
let p = editor.draw.point(event.clientX, event.clientY)
- let x = editor.thingAt(p, "#points")
+ let x = editor.thingAt(p, "#" + editor.layoutG())
+ if (x) {
+ x.select()
+ return
+ }
+
+ x = editor.thingAt(p, "#points")
if (x) {
x.select()
return
@@ -591,6 +607,20 @@ function precisePointMapHandler(event, editor) {
}
}
+function furnitureHandler(ev, editor) {
+ if (ev.type != "mousedown" || ev.button !== buttons.left) {
+ return
+ }
+ if (editor.draw.findOne(".selected") != null) {
+ return
+ }
+
+ let p = editor.draw.point(ev.clientX, ev.clientY)
+ console.log("Add furniture", editor.addMappedFurniture("table", p.x, p.y, { width: 500, depth: 500 }))
+ editor.finishAction()
+ ev.preventDefault()
+}
+
function parseUserLength(editor, length) {
let a = length.replaceAll(" ", "").split(/([0-9]+)/)
let amount
diff --git a/files/floorplans/floorplan/svg.css b/files/floorplans/floorplan/svg.css
@@ -1,6 +1,6 @@
/* SVG element CSS */
-circle.selected {
+rect.selected, circle.selected {
fill: blue;
}