www.spaceplanner.app

Web client to the spaceplanner API
git clone git://jacobedwards.org/www.spaceplanner.app
Log | Files | Refs

commit c47ec0c9322dcf7b565b3fb772f1249e68ed1487
parent 9657828f4d9615d3571384b4f9a52b454eb3ff72
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Wed,  9 Oct 2024 10:48:04 -0700

Implement furniture styles

This is a new feature in the backend to allow for the same type to
have multiple "styles", like round tables.

Diffstat:
Mfiles/floorplans/floorplan/backend.js | 10+++++++++-
Mfiles/floorplans/floorplan/editor.js | 24++++++++++++++++--------
Mfiles/floorplans/floorplan/main.js | 36+++++++++++++++++++++++++++++++-----
Rfiles/furniture/bed.svg -> files/furniture/bed/default.svg | 0
Rfiles/furniture/dresser.svg -> files/furniture/dresser/default.svg | 0
Rfiles/furniture/nightstand.svg -> files/furniture/nightstand/default.svg | 0
Rfiles/furniture/sofa.svg -> files/furniture/sofa/default.svg | 0
Dfiles/furniture/table.svg | 89-------------------------------------------------------------------------------
Afiles/furniture/table/default.svg | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afiles/furniture/table/round.svg | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afiles/furniture/table/semi-circle.svg | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 318 insertions(+), 103 deletions(-)

diff --git a/files/floorplans/floorplan/backend.js b/files/floorplans/floorplan/backend.js @@ -532,7 +532,8 @@ export class FloorplanBackend { addFurniture(params, id) { params = params ?? {} - let f = id ? this.reqObj(id) : {} + //let f = id ? this.reqObj(id) : {} + let f = {} if (params.width != undefined) { f.width = Math.round(params.width) @@ -559,6 +560,13 @@ export class FloorplanBackend { f.type = params.type } + if (params.style != undefined) { + if (typeof params.style !== "string") { + throw new Error(params.style + ": Invalid style") + } + f.style = params.style + } + if (f.width == null || f.depth == null || f.type == null) { throw new Error("Missing required parameters") } diff --git a/files/floorplans/floorplan/editor.js b/files/floorplans/floorplan/editor.js @@ -706,25 +706,29 @@ export class FloorplanEditor { 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) - m.findOne("title").words(furniture_name(value)) + if (m == null) { + ops.add.furniture_maps(id, editor.backend.cache[id]) + m = editor.draw.findOneMax(byId(mid)) } - m.load(`/furniture/${value.type}.svg`) + m.size(value.width, value.depth) + let t = m.findOne("title") + if (t == null) { + t = m.element("title") + } + t.words(furniture_name(value)) + m.load(furnitureImage(value)) } } }, furniture_maps: function(id, value) { + let f = editor.backend.reqObj(value.furniture_id) let fm = editor.draw.findOneMax(byId(id)) if (!fm) { - let f = editor.backend.reqObj(value.furniture_id) - fm = editor.layoutG().image(`/furniture/${f.type}.svg`) + fm = editor.layoutG().image(furnitureImage(f)) .size(f.width, f.depth) .attr({ id, preserveAspectRatio: "none" }) - fm.element("title").words(furniture_name(f)) fm.on("error", function() { if (this.attr("href") === "/furniture/any.svg") { - etc.error("Unable to load furniture assets") throw new Error("Unable to load furniture assets") } this.load("/furniture/any.svg") @@ -912,3 +916,7 @@ function furniture_name(f) { function swingID(id) { return id + "_swing" } + +function furnitureImage(f) { + return `/furniture/${f.type}/${f.style ?? "default"}.svg` +} diff --git a/files/floorplans/floorplan/main.js b/files/floorplans/floorplan/main.js @@ -881,6 +881,13 @@ function furnitureMenuX(editor, pointOrID) { return i } } + const styles = function(type) { + let styles = ['default'] + if (editor.furniture_types[type].styles == null) { + return styles + } + return styles.concat(editor.furniture_types[type].styles) + } editor.finishAction() let p @@ -923,6 +930,7 @@ function furnitureMenuX(editor, pointOrID) { let items = [ menuItem("name", "Name", { attributes: { value: params.name ?? "" } }), menuItem("type", "Type", { break: false, enum: editor.furniture_types, attributes: { value: params.type, required: true } }), + menuItem("style", "Style"), menuItem("variety", "Variety", { enum: editor.furniture_types[params.type].varieties, attributes: { value: editor.varietyFrom(params) } }), menuItem("width", "Width", { attributes: { value: userLength(editor, params.width), required: true } }), menuItem("depth", "Depth", { attributes: { value: userLength(editor, params.depth), required: true } }), @@ -964,26 +972,44 @@ function furnitureMenuX(editor, pointOrID) { fromVariety(items[keys.type].input.value, ev.target.value) }) } + const newStyle = function() { + let typeStyles = styles(params.type) + if (typeStyles.length === 1) { + items[keys.style].container.classList.add("hidden") + } else { + let s = menuItem("style", "Style", { enum: typeStyles }) + items[keys.style].container.replaceWith(makeItem(s)) + items[keys.style] = s + if (params.style != null) { + items[keys.style].input.value = params.style + } + } + } let menu = makeMenu(items) items[keys.type].input.value = params.type newVariety(true) + newStyle(params.type) items[keys.type].input.addEventListener("change", function(ev) { newVariety() }) menu.addEventListener("change", function(ev) { handled(ev) try { - console.debug("furnitureMenu.change(event)", event.target.name, event.target.value) - if (event.target.name === "width" || event.target.name === "depth") { - let u = unitInput(editor, event.target) + console.debug("furnitureMenu.change(ev)", ev.target.name, ev.target.value) + if (ev.target.name === "width" || ev.target.name === "depth") { + let u = unitInput(editor, ev.target) if (u == undefined) { return } - params[event.target.name] = u + params[ev.target.name] = u items[keys.variety].input.value = editor.varietyFrom(params) } else { - params[event.target.name] = event.target.value + if (ev.target.name === "style" && ev.target.value === "default") { + params[ev.target.name] = null + } else { + params[ev.target.name] = ev.target.value.length === 0 ? null : ev.target.value + } } editor.addMappedFurniture(params, id) } diff --git a/files/furniture/bed.svg b/files/furniture/bed/default.svg diff --git a/files/furniture/dresser.svg b/files/furniture/dresser/default.svg diff --git a/files/furniture/nightstand.svg b/files/furniture/nightstand/default.svg diff --git a/files/furniture/sofa.svg b/files/furniture/sofa/default.svg diff --git a/files/furniture/table.svg b/files/furniture/table.svg @@ -1,89 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<svg - width="100" - height="100" - version="1.1" - id="svg1" - sodipodi:docname="table.svg" - inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns="http://www.w3.org/2000/svg" - xmlns:svg="http://www.w3.org/2000/svg"> - <defs - id="defs1"> - <inkscape:path-effect - effect="fillet_chamfer" - id="path-effect1" - is_visible="true" - lpeversion="1" - nodesatellites_param="F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1" - radius="10" - unit="px" - method="auto" - mode="F" - chamfer_steps="1" - flexible="false" - use_knot_distance="true" - apply_no_radius="true" - apply_with_radius="true" - only_selected="false" - hide_knots="false" /> - <inkscape:path-effect - effect="fillet_chamfer" - id="path-effect1-6" - is_visible="true" - lpeversion="1" - nodesatellites_param="F,0,0,1,0,4,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,4,0,1 @ F,0,0,1,0,4,0,1" - radius="4" - unit="px" - method="auto" - mode="F" - chamfer_steps="1" - flexible="false" - use_knot_distance="true" - apply_no_radius="true" - apply_with_radius="true" - only_selected="false" - hide_knots="false" /> - </defs> - <sodipodi:namedview - id="namedview1" - pagecolor="#ffffff" - bordercolor="#000000" - borderopacity="0.25" - inkscape:showpageshadow="2" - inkscape:pageopacity="0.0" - inkscape:pagecheckerboard="0" - inkscape:deskcolor="#d1d1d1" - inkscape:zoom="5.77" - inkscape:cx="49.913345" - inkscape:cy="50" - inkscape:window-width="1366" - inkscape:window-height="749" - inkscape:window-x="0" - inkscape:window-y="19" - inkscape:window-maximized="1" - inkscape:current-layer="svg1" /> - <path - x="2" - y="2" - width="96" - height="96" - style="fill:none;stroke:#000000;stroke-width:4;stroke-linejoin:round;fill-opacity:0" - id="rect1" - inkscape:path-effect="#path-effect1" - sodipodi:type="rect" - d="M 12,2 H 88 A 10,10 45 0 1 98,12 V 88 A 10,10 135 0 1 88,98 H 12 A 10,10 45 0 1 2,88 V 12 A 10,10 135 0 1 12,2 Z" /> - <path - x="2" - y="2" - width="96" - height="96" - style="fill:none;stroke:#000000;stroke-width:4;stroke-linejoin:round" - id="rect1-0" - inkscape:path-effect="#path-effect1-6" - sodipodi:type="rect" - d="m 6,2 h 88 a 4,4 45 0 1 4,4 v 88 a 4,4 135 0 1 -4,4 H 6 A 4,4 45 0 1 2,94 V 6 A 4,4 135 0 1 6,2 Z" - transform="matrix(0.86224037,0,0,0.86224037,6.8879813,6.8879813)" /> -</svg> diff --git a/files/furniture/table/default.svg b/files/furniture/table/default.svg @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="100" + height="100" + version="1.1" + id="svg1" + sodipodi:docname="default.svg" + inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1"> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1" + is_visible="true" + lpeversion="1" + nodesatellites_param="F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1" + radius="10" + unit="px" + method="auto" + mode="F" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1-6" + is_visible="true" + lpeversion="1" + nodesatellites_param="F,0,0,1,0,8,0,1 @ F,0,0,1,0,8,0,1 @ F,0,0,1,0,8,0,1 @ F,0,0,1,0,8,0,1" + radius="8" + unit="px" + method="auto" + mode="F" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + </defs> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:zoom="4.1" + inkscape:cx="50" + inkscape:cy="50.121951" + inkscape:window-width="1366" + inkscape:window-height="749" + inkscape:window-x="0" + inkscape:window-y="19" + inkscape:window-maximized="1" + inkscape:current-layer="svg1" /> + <path + x="2" + y="2" + width="96" + height="96" + style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:3;stroke-linejoin:round;stroke-dasharray:none" + id="rect1" + inkscape:path-effect="#path-effect1" + sodipodi:type="rect" + d="M 12,2 H 88 A 10,10 45 0 1 98,12 V 88 A 10,10 135 0 1 88,98 H 12 A 10,10 45 0 1 2,88 V 12 A 10,10 135 0 1 12,2 Z" /> + <path + x="2" + y="2" + width="96" + height="96" + style="fill:none;stroke:#000000;stroke-width:2.31954;stroke-linejoin:round;stroke-dasharray:none" + id="rect1-0" + inkscape:path-effect="#path-effect1-6" + sodipodi:type="rect" + d="m 10,2 h 80 a 8,8 45 0 1 8,8 v 80 a 8,8 135 0 1 -8,8 H 10 A 8,8 45 0 1 2,90 V 10 a 8,8 135 0 1 8,-8 z" + transform="matrix(0.91539094,0,0,0.91537467,4.2308611,4.2308748)" /> +</svg> diff --git a/files/furniture/table/round.svg b/files/furniture/table/round.svg @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="100" + height="100" + version="1.1" + id="svg1" + sodipodi:docname="round.svg" + inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1"> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1" + is_visible="true" + lpeversion="1" + nodesatellites_param="F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1" + radius="10" + unit="px" + method="auto" + mode="F" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1-6" + is_visible="true" + lpeversion="1" + nodesatellites_param="F,0,0,1,0,4800,0,1 @ F,0,0,1,0,4800,0,1 @ F,0,0,1,0,4800,0,1 @ F,0,0,1,0,4800,0,1" + radius="50" + unit="in" + method="auto" + mode="F" + chamfer_steps="1" + flexible="true" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + </defs> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:zoom="4.1" + inkscape:cx="50" + inkscape:cy="49.878049" + inkscape:window-width="1366" + inkscape:window-height="749" + inkscape:window-x="0" + inkscape:window-y="19" + inkscape:window-maximized="1" + inkscape:current-layer="svg1" /> + <ellipse + style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:4.0532;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none" + id="path1" + cy="50" + cx="50" + rx="47.973396" + ry="47.9734" /> + <circle + style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.853;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none" + id="path1-1" + cy="50" + cx="49.999996" + r="43.537704" /> +</svg> diff --git a/files/furniture/table/semi-circle.svg b/files/furniture/table/semi-circle.svg @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="100" + height="52" + version="1.1" + id="svg1" + sodipodi:docname="semi-circle.svg" + inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs1"> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1" + is_visible="true" + lpeversion="1" + nodesatellites_param="F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1 @ F,0,0,1,0,10,0,1" + radius="10" + unit="px" + method="auto" + mode="F" + chamfer_steps="1" + flexible="false" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + <inkscape:path-effect + effect="fillet_chamfer" + id="path-effect1-6" + is_visible="true" + lpeversion="1" + nodesatellites_param="F,0,0,1,0,4800,0,1 @ F,0,0,1,0,4800,0,1 @ F,0,0,1,0,4800,0,1 @ F,0,0,1,0,4800,0,1" + radius="50" + unit="in" + method="auto" + mode="F" + chamfer_steps="1" + flexible="true" + use_knot_distance="true" + apply_no_radius="true" + apply_with_radius="true" + only_selected="false" + hide_knots="false" /> + </defs> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:zoom="4.1" + inkscape:cx="49.756098" + inkscape:cy="49.878049" + inkscape:window-width="1366" + inkscape:window-height="749" + inkscape:window-x="0" + inkscape:window-y="19" + inkscape:window-maximized="1" + inkscape:current-layer="svg1" /> + <path + style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" + id="path1" + sodipodi:type="arc" + sodipodi:cx="50" + sodipodi:cy="2.0265999" + sodipodi:rx="47.973396" + sodipodi:ry="47.9734" + sodipodi:start="0" + sodipodi:end="3.1415927" + sodipodi:arc-type="slice" + d="M 97.973396,2.0265999 A 47.973396,47.9734 0 0 1 73.986698,43.572783 47.973396,47.9734 0 0 1 26.013301,43.572782 47.973396,47.9734 0 0 1 2.0266037,2.0265999 H 50 Z" /> + <path + style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:1.853;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none" + id="path1-1" + sodipodi:type="arc" + sodipodi:cx="49.999996" + sodipodi:cy="2.0265999" + sodipodi:rx="43.537704" + sodipodi:ry="43.537704" + sodipodi:start="0" + sodipodi:end="3.1415927" + sodipodi:arc-type="slice" + d="M 93.537701,2.0265999 A 43.537704,43.537704 0 0 1 71.768848,39.731358 43.537704,43.537704 0 0 1 28.231143,39.731357 43.537704,43.537704 0 0 1 6.4622917,2.0265999 H 49.999996 Z" /> +</svg>