www.spaceplanner.app

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

commit 078dba2472b6dee8a17f7cefcf76451b770503fc
parent 5ead523b74cbff96461c176a10046384631c80bc
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Wed, 16 Oct 2024 19:49:29 -0700

Add multi-selection

Multi-selection is going to be very important for moving multiple
points, etc. At the moment it's still fairly limited and only useful
for saving a bit of time when deleting a few different things or
changing pointmap types etc., but I'll rework various handlers to
support moving multiple objects as a group in the future (see
thoughts below).

In this selection mode I want to have press, drag, and release
create a box from the press to the release which selects everything
inside of it.

I'm thinking that while it sounds a little tedious it might be a
good idea to have changes to the SVG elements themselves push diffs
to the backend. I think this would end up making all this front-end
code—and I can't overstate this enough—a lot easier.  For instance
moving or rotating multiple objects would just mean putting them
in a group, transforming them, then have svg.js take them out of a
group and apply the transform to each individually. Of course there
are some issues I see with this already but I still think it might
be a good idea.

Diffstat:
Mfiles/floorplans/floorplan/main.js | 86++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
1 file changed, 63 insertions(+), 23 deletions(-)

diff --git a/files/floorplans/floorplan/main.js b/files/floorplans/floorplan/main.js @@ -8,6 +8,7 @@ import * as geometry from "./geometry.js" import * as backend from "./backend.js" import * as api from "/lib/api.js" +const defaultMode = "Precise" const messageTimeout = 4000 const buttons = { @@ -41,6 +42,16 @@ const modes = { select: selectHandler, reselect: selectHandler } + }, + Select: { + points: true, + handlers: { + contextmenu: preventDefaultHandler, + pointerdown: selectionHandler, + keydown: keyHandler, + select: selectHandler, + reselect: selectHandler + } } } @@ -49,6 +60,7 @@ let State = { pointOp: 'Create', snapAngle: true, snapPoints: true, + selectMode: false, lastClick: null } @@ -134,7 +146,7 @@ function run(editor) { for (let mode in modes) { editor.addMode(mode, modes[mode]) } - editor.useMode("Precise") + editor.useMode(defaultMode) let toolbar = document.querySelector("header") .appendChild(document.createElement("ul")) @@ -195,6 +207,12 @@ function run(editor) { on: function() { State.snapPoints = true }, value: State.snapPoints }))) + toolbar.append(item(checkToggle("Select mode", { + title: "Enter selection mode", + off: function() { editor.useMode(defaultMode); State.selectMode = false }, + on: function() { editor.useMode("Select"); State.selectMode = true }, + value: State.selectMode + }))) if (debug) { toolbar.append(item( @@ -282,7 +300,8 @@ function checkToggle(name, params) { input.setAttribute("title", params.title) } - if (params.value) { + if (params.value != undefined) { + input.checked = params.value run(params.value) } return c @@ -304,6 +323,10 @@ function selectHandler(event, editor, state) { selectHandler(event, editor, state) } + c.append(ui.button("Unselect", "Deselect selection", null, { + handlers: { click: function() { editor.draw.select() } } + })) + c.append(ui.input("Delete", "Delete selected objects", { attributes: { type: "button", value: "Delete" }, handlers: { click: function() { @@ -519,6 +542,8 @@ function selector(things, select, options) { // pointerdown function selectionHandler(event, editor) { + let sel + if (event.pointerType === "mouse" && event.button === buttons.right) { return } @@ -526,32 +551,47 @@ function selectionHandler(event, editor) { let p = editor.draw.point(event.clientX, event.clientY) State.lastClick = structuredClone(p) let order = [ "#" + editor.layoutG(), "#points", "#pointmaps" ] - for (let i = 0; i < order.length; ++i) { - let x = editor.thingAt(p, order[i]) - if (x) { - x.select() - return + for (let i = 0; !sel && i < order.length; ++i) { + sel = editor.thingAt(p, order[i]) + } + + if (!sel) { + let close = editor.thingsAt(p, order.join(","), { method: "touching", minsize: 3500 }) + let dist + let closest + for (let i = 0; i < close.length; ++i) { + let tmp = close[i].distanceTo(p.x, p.y) + if (dist == null || tmp < dist) { + dist = tmp + closest = close[i] + } } + sel = closest } - let close = editor.thingsAt(p, order.join(","), { method: "touching", minsize: 3500 }) - let dist - let closest - for (let i = 0; i < close.length; ++i) { - let tmp = close[i].distanceTo(p.x, p.y) - if (dist == null || tmp < dist) { - dist = tmp - closest = close[i] + if (sel != null) { + if (!State.selectMode) { + sel.select() + } else { + let selection = editor.draw.find(".selected") + let i = selection.indexOf(sel) + if (i >= 0) { + selection.splice(i, 1) + } else { + selection.push(sel) + } + if (selection.length === 0) { + editor.draw.select() + } else { + selection.selectList() + } } + } else { + if (!State.selectMode) { + editor.draw.select() + } + escape() } - - if (closest != null) { - closest.select() - return - } - - editor.draw.select() - escape() } function keyHandler(ev, editor) {