www.spaceplanner.app

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

commit bfc470af516500c70d6dca7cf42ecc2423b35751
parent 312097e6f1ae7261375f61e99aa40838cdc57515
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Wed,  7 Aug 2024 16:47:24 -0700

Add floorplan metadata editing

Diffstat:
Mfiles/floorplans/main.css | 12++++++++++++
Mfiles/floorplans/main.js | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Afiles/icons/save-outline.svg | 2++
3 files changed, 167 insertions(+), 16 deletions(-)

diff --git a/files/floorplans/main.css b/files/floorplans/main.css @@ -44,3 +44,15 @@ #floorplans.grid > li > .floorplan:hover > footer { opacity: 100%; } + +input.fp_name { + font-size: larger; +} + +input.fp_address { + font-style: italic; +} + +input { + display: block; +} diff --git a/files/floorplans/main.js b/files/floorplans/main.js @@ -2,6 +2,9 @@ import * as api from "/lib/api.js" import * as etc from "/lib/etc.js" import * as ui from "/lib/ui.js" +// These are in the order they should appear +const editables = [ "name", "synopsis", "address" ] + function init() { etc.authorize() etc.bar() @@ -30,12 +33,120 @@ function gridview() { document.getElementById("floorplans").setAttribute("class", "grid") } -function edit_floorplan_func(item, floorplan) { +function commit_editable_floorplan_func(element, data) { + let update_display = function() { + let parent = element.querySelector("header") + for (let i in editables) { + let c = floorplan_info_class(editables[i]) + let field = parent.querySelector("." + c) + if (!field) { + throw new Error("Expected ." + c + ", got nothing") + } + if (!field.value) { + field.remove() + } else { + let creator = create_field[editables[i]] + if (!creator) { + throw new Error("Expected " + editables[i] + "in create_field") + } + field.replaceWith(creator(field.value)) + } + } + } + + return function () { + let patches = [] + let fields = Array.from(element.querySelectorAll("header > input")) + for (let i in fields) { + let name = floorplan_info_name(fields[i].getAttribute("class")) + let value = fields[i].value + console.debug(fields[i], name, value) + if (value === data[name]) { + continue; + } else if (value) { + patches.push({ op: "add", path: name, value: value }) + } else if (!value) { + patches.push({ op: "remove", path: name }) + } + } + + if (patches.length == 0) { + console.debug("No changes, skipping PATCH") + update_display() + return + } + + return api.fetch("PATCH", "floorplans/" + localStorage.getItem("username") + "/" + data.name, patches) + .then(function(data) { + for (let i in data) { + data[i] = data[i] + } + update_display() + }) + .catch(function(err) { + etc.error(err, element) + throw err + }) + } +} + +function editable_floorplan_func(element, data) { return function() { - etc.error("Edit not implemented", item) + let prev + let parent = element.querySelector("header") + for (let i in editables) { + let input + let c = floorplan_info_class(editables[i]) + let e = parent.querySelector("." + c) // .getElementsByClassName() + if (e) { + input = make_input(editables[i], { value: e.textContent }) + input.setAttribute("class", c) + e.replaceWith(input) + } else { + input = make_input(editables[i]) + input.setAttribute("class", c) + if (prev) { + prev.after(input) + } else { + parent.append(input) + } + } + prev = input + } } } +function floorplan_info_class(name) { + return "fp_" + name; +} + +function floorplan_info_name(classname) { + if (!classname.match("^fp_")) { + throw new Error("Expected floorplan info class") + } + return classname.substring(3) +} + +function make_input(name, options) { + if (!name) { + throw new Error("No name provided") + } + if (!options) { + options = {} + } + + let input = document.createElement("input") + input.name = name + input.placeholder = name + if (options["type"]) { + input.type = options["type"] + } + if (options["value"]) { + input.value = options["value"] + } + return input +} + function delete_floorplan_func(item, floorplan) { return function() { api.fetch("DELETE", "floorplans/" + floorplan.user + "/" + floorplan.name) @@ -49,34 +160,34 @@ function delete_floorplan_func(item, floorplan) { } function create_floorplan(floorplan) { + if (!floorplan.name) { + throw new Error("Expected floorplan name") + } + let root = document.createElement("div") root.setAttribute("class", "floorplan") let aside = document.createElement("aside") - aside.append(ui.button("Edit", "Edit floorplan", "create", edit_floorplan_func(root, floorplan))) + aside.append( + ui.toggle( + ui.button("Edit", "Edit floorplan", "create"), editable_floorplan_func(root, floorplan), + ui.button("Save", "Save floorplan", "save"), commit_editable_floorplan_func(root, floorplan), + ) + ) + aside.append(ui.button("Delete", "Delete floorplan", "trash", delete_floorplan_func(root, floorplan))) root.append(aside) let header = document.createElement("header") - let heading = document.createElement("h2") - header.append(heading) - let link = document.createElement("a") - heading.append(link) + header.append(create_field.name(floorplan.name)) if (floorplan.synopsis) { - let synopsis = document.createElement("span") - synopsis.setAttribute("class", "synopsis") - synopsis.appendChild(document.createTextNode(floorplan.synopsis)) - header.append(synopsis) + header.append(create_field.synopsis(floorplan.synopsis)) } if (floorplan.address) { - let address = document.createElement("address") - address.appendChild(document.createTextNode(floorplan.address)) - header.append(address) + header.append(create_field.address(floorplan.address)) } - link.href = "floorplans/" + localStorage.getItem("username") + "/" + floorplan.name - link.appendChild(document.createTextNode(floorplan.name)) root.append(header) if (floorplan.user != localStorage.getItem("username")) { @@ -89,6 +200,32 @@ function create_floorplan(floorplan) { return root } +var create_field = { + name: function(text) { + let heading = document.createElement("h2") + heading.setAttribute("class", floorplan_info_class("name")) + let link = document.createElement("a") + link.href = "floorplans/" + localStorage.getItem("username") + "/" + text + link.appendChild(document.createTextNode(text)) + heading.append(link) + return heading + }, + + synopsis: function(text) { + let synopsis = document.createElement("span") + synopsis.setAttribute("class", floorplan_info_class("synopsis")) + synopsis.appendChild(document.createTextNode(text)) + return synopsis + }, + + address: function(text) { + let address = document.createElement("address") + address.setAttribute("class", floorplan_info_class("address")) + address.appendChild(document.createTextNode(text)) + return address + } +} + function show_floorplans(floorplans) { let list = document.getElementById("floorplans") if (!list) { diff --git a/files/icons/save-outline.svg b/files/icons/save-outline.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><path d="M380.93,57.37A32,32,0,0,0,358.3,48H94.22A46.21,46.21,0,0,0,48,94.22V417.78A46.21,46.21,0,0,0,94.22,464H417.78A46.36,46.36,0,0,0,464,417.78V153.7a32,32,0,0,0-9.37-22.63ZM256,416a64,64,0,1,1,64-64A63.92,63.92,0,0,1,256,416Zm48-224H112a16,16,0,0,1-16-16V112a16,16,0,0,1,16-16H304a16,16,0,0,1,16,16v64A16,16,0,0,1,304,192Z" style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/></svg> +\ No newline at end of file