main.js (9433B)
1 import * as api from "/lib/api.js" 2 import * as etc from "/lib/etc.js" 3 import * as ui from "/lib/ui.js" 4 5 // These are in the order they should appear 6 const editables = [ "name", "address", "synopsis" ] 7 8 etc.handle_wrap(init) 9 10 function init() { 11 etc.authorize() 12 etc.bar() 13 14 let f = document.getElementById("filter") 15 f.removeAttribute("disabled") 16 f.addEventListener("input", function(ev) { 17 document.querySelectorAll("#floorplans > li").forEach(function(item) { 18 if (item.querySelector("#adder")) { 19 return 20 } 21 22 let data = {} 23 let h = item.querySelector(".floorplan > header") 24 const tc = function(sel) { 25 const e = h.querySelector(sel) 26 return e ? e.textContent : null 27 } 28 data.name = tc(".name_div > h2 > a") 29 data.address = tc(".address") 30 data.synopsis = tc(".synopsis") 31 item.hidden = !matchFloorplan(data, ev.target.value) 32 }) 33 }) 34 35 api.fetch("GET", "floorplans/:user") 36 .then(show_floorplans) 37 .catch(etc.error) 38 } 39 40 function commit_editable_floorplan_func(element, data) { 41 return function () { 42 let patches = [] 43 let fields = Array.from(element.querySelectorAll(".fp_metadata")) 44 let updated = false 45 let newdata = {} 46 for (let i in fields) { 47 let name = fields[i].name 48 let value = fields[i].value 49 if (value.length === 0) { 50 value = null 51 } 52 53 console.debug(fields[i], name, value) 54 newdata[name] = value; 55 if (newdata[name] !== data[name]) { 56 updated = true 57 } 58 } 59 60 if (!updated) { 61 console.debug("No changes, skipping") 62 element.replaceWith(create_floorplan(data)) 63 return 64 } 65 66 return api.fetch("PUT", `floorplans/:user/${data.id}`, newdata) 67 .then(function(rdata) { 68 for (let i in rdata) { 69 data[i] = rdata[i] 70 } 71 element.replaceWith(create_floorplan(data)) 72 }) 73 .catch(function(err) { 74 etc.error(err, element) 75 throw err 76 }) 77 } 78 } 79 80 function editable_floorplan_create_func(element) { 81 return function () { 82 let data = {} 83 let fields = Array.from(element.querySelectorAll("header > input")) 84 for (let i in fields) { 85 let name = fields[i].name 86 let value = fields[i].value 87 console.debug(fields[i], name, value) 88 if (value) { 89 data[name] = value 90 } 91 } 92 93 return api.fetch("POST", "floorplans/:user", data) 94 .then(function(rdata) { 95 for (let i in rdata) { 96 data[i] = rdata[i] 97 } 98 for (let i in fields) { 99 fields[i].value = "" 100 } 101 /* NOTE: I was going to try and not 102 * have these floorplans know anything 103 * about where they are, but I'm living 104 * with this. 105 */ 106 element.parentElement.after(create_floorplan_item(data)) 107 }) 108 .catch(function(err) { 109 etc.error(err, element) 110 throw err 111 }) 112 } 113 } 114 115 function editable_floorplan_func(element, data) { 116 return function() { 117 let prev 118 let parent = element.querySelector("header") 119 for (let i in editables) { 120 let input 121 let memo = "Edit floorplan " + editables[i] 122 let e = parent.querySelector("." + editables[i]) 123 124 input = ui.input(editables[i], memo, { 125 attributes: { value: e ? e.textContent : "" } 126 }) 127 input.classList.add("fp_metadata") 128 input.classList.add(editables[i]) 129 input.name = editables[i] 130 131 if (e) { 132 e.replaceWith(input) 133 } else { 134 if (prev) { 135 if (prev.name === "name") { 136 parent.append(input) 137 } else { 138 prev.after(input) 139 } 140 } else { 141 parent.append(input) 142 } 143 } 144 prev = input 145 } 146 } 147 } 148 149 function delete_floorplan_func(item, floorplan) { 150 return function() { 151 api.fetch("DELETE", `floorplans/:user/${floorplan.id}`) 152 .then(function() { 153 item.parentElement.remove() 154 }) 155 .catch(function(err) { 156 etc.error("Unable to delete floorplan: " + err, item) 157 }) 158 } 159 } 160 161 function ask_delete_floorplan_func(item, floorplan) { 162 return function() { 163 document.querySelectorAll(".delete_dialog").forEach(function(e) { e.remove() }) 164 let c = document.body.appendChild(document.createElement("div")) 165 c.classList.add("delete_dialog") 166 let mkbutton = function(value) { 167 let b = document.createElement("input") 168 b.type = "button" 169 b.value = value 170 return b 171 } 172 173 let t = c.appendChild(document.createElement("p")) 174 t.appendChild(document.createTextNode("Delete ")) 175 let q = t.appendChild(document.createElement("q")) 176 q.appendChild(document.createTextNode(floorplan.name)) 177 t.append(document.createTextNode("?")) 178 179 let yes = c.appendChild(mkbutton("Yes")) 180 let no = c.appendChild(mkbutton("No")) 181 182 let p = new Promise(function(res, rej) {}) 183 let hand = function(ev) { 184 if (ev.target.value == "Yes") { 185 delete_floorplan_func(item, floorplan)() 186 } 187 c.remove() 188 } 189 yes.addEventListener("click", hand) 190 no.addEventListener("click", hand) 191 192 return p 193 } 194 } 195 196 function create_floorplan_item(floorplan) { 197 let item = document.createElement("li") 198 item.append(create_floorplan(floorplan)) 199 return item 200 } 201 202 function create_floorplan(floorplan) { 203 let root = document.createElement("div") 204 root.classList.add("class", "floorplan") 205 206 let aside = document.createElement("div") 207 aside.classList.add("fp_ops") 208 if (floorplan) { 209 let a = aside.appendChild(document.createElement("a")) 210 a.href = `./floorplan/?id=${floorplan.id}` 211 a.append(document.createTextNode("Editor")) 212 213 let ops = aside.appendChild(document.createElement("div")) 214 ops.classList.add("fp_buttons") 215 216 ops.append(ui.button("Copy", "Copy floorplan", null, { handlers: { click: function() { copy_floorplan(floorplan) } } })) 217 ops.append(ui.button("Delete", "Delete floorplan", null, { handlers: { click: ask_delete_floorplan_func(root, floorplan) } })) 218 } else { 219 root.id = "adder" 220 root.addEventListener("keydown", function(ev) { 221 if (ev.key === "Enter") { 222 ev.preventDefault() 223 editable_floorplan_create_func(root)() 224 } 225 }) 226 aside.append(ui.button("Create", "Create floorplan", null, { handlers: { click: editable_floorplan_create_func(root) } })) 227 } 228 229 let header = document.createElement("header") 230 header.append(aside) 231 root.append(header) 232 233 if (!floorplan) { 234 editable_floorplan_func(root, {})() 235 } else { 236 if (!floorplan.name) { 237 throw new Error("Expected floorplan name") 238 } 239 let nameDiv = header.appendChild(document.createElement("div")) 240 nameDiv.classList.add("name_div") 241 nameDiv.append(create_field.name(floorplan.name, floorplan.id)) 242 nameDiv.append(ui.toggle( 243 { button: ui.button("Edit", "Edit floorplan metadata", "create"), func: editable_floorplan_func(root, floorplan) }, 244 { button: ui.button("Save", "Save floorplan metadata", "save"), func: commit_editable_floorplan_func(root, floorplan) }, 245 )) 246 247 if (floorplan.address) { 248 header.append(create_field.address(floorplan.address)) 249 } 250 if (floorplan.synopsis) { 251 header.append(create_field.synopsis(floorplan.synopsis)) 252 } 253 254 if (floorplan.user != localStorage.getItem("username")) { 255 let footer = document.createElement("footer") 256 // TODO: Link to user page, when it exists 257 footer.append(document.createTextNode("By " + floorplan.user)) 258 root.append(footer) 259 } 260 } 261 262 return root 263 } 264 265 var create_field = { 266 name: function(text, id) { 267 let heading = document.createElement("h2") 268 heading.classList.add("fp_metadata") 269 heading.classList.add("name") 270 let link = document.createElement("a") 271 link.href = `./floorplan/?id=${id}` 272 link.appendChild(document.createTextNode(text)) 273 heading.append(link) 274 return heading 275 }, 276 277 synopsis: function(text) { 278 let synopsis = document.createElement("span") 279 synopsis.classList.add("fp_metadata") 280 synopsis.classList.add("synopsis") 281 synopsis.appendChild(document.createTextNode(text)) 282 return synopsis 283 }, 284 285 address: function(text) { 286 let address = document.createElement("address") 287 address.classList.add("fp_metadata") 288 address.classList.add("address") 289 address.appendChild(document.createTextNode(text)) 290 return address 291 } 292 } 293 294 function show_floorplans(floorplans) { 295 let list = document.getElementById("floorplans") 296 if (!list) { 297 throw new Error("expected #floorplans") 298 } 299 300 list.append(create_floorplan_item()) 301 for (let i in floorplans) { 302 list.append(create_floorplan_item(floorplans[i])) 303 } 304 } 305 306 function insertFloorplan(floorplan) { 307 let e = create_floorplan_item(floorplan) 308 309 let adder = document.getElementById("adder") 310 if (adder) { 311 adder.parentElement.after(e) 312 } else { 313 let list = document.getElementById("floorplans") 314 list.prepend(create_floorplan(floorplan)) 315 } 316 } 317 318 function copy_floorplan(floorplan, name, depth) { 319 if (!name) { 320 name = floorplan.name + " (Copy)" 321 } 322 api.fetch("GET", `floorplans/${floorplan.user}/${floorplan.id}/data`) 323 .then(function(data) { 324 let f = structuredClone(floorplan) 325 f.name = name 326 return api.fetch("POST", "floorplans/:user", f) 327 .then(function(floorplan) { 328 insertFloorplan(floorplan) 329 return api.fetch("PUT", `floorplans/${floorplan.user}/${floorplan.id}/data`, data) 330 .catch(function(err) { 331 api.fetch("DELETE", `floorplans/:user/${floorplan.id}`) 332 throw err 333 }) 334 }) 335 .catch(function(err) { 336 depth = depth ?? 0 337 if (depth < 10 && err.message.indexOf('violates unique constraint "id"')) { 338 return copy_floorplan(floorplan, name + " (Copy)", depth + 1) 339 } else { 340 etc.error(err) 341 throw err 342 } 343 }) 344 }) 345 } 346 347 function matchFloorplan(floorplan, exp) { 348 const ms = function(s, e) { 349 return s ? s.toLowerCase().includes(e) : false 350 } 351 352 exp = exp.toLowerCase() 353 return ms(floorplan.name, exp) || ms(floorplan.address, exp) || ms(floorplan.synopsis, exp) 354 }