fix: pin click should not bubble to canvas (was placing waypoints under notes)
Clicking a Note (or any pin) in Route mode used to: 1. fire the canvas click handler → drop a waypoint at the note's spot 2. only THEN run the pin's own click/dblclick handlers So a single click on a note added a stray waypoint, and a double-click added two and may not have reliably reached the dblclick handler. Edit-via-dblclick therefore felt broken. Fix: every interactive pin (note, text label, route waypoint, pull marker) now stops 'click' propagation explicitly. Click and dblclick on a pin no longer affect the active tool. Drag detection bumped to a 3px threshold and now only persists history when the pin actually moved.
This commit is contained in:
+70
-30
@@ -254,14 +254,20 @@ function makeNotePin(note, idx) {
|
||||
g.dataset.tooltip = note.text || "(empty)";
|
||||
|
||||
// drag-to-move + right-click delete + double-click to edit text
|
||||
// Click on a pin must NOT bubble to the SVG (which would add a waypoint
|
||||
// / pull / etc. at the same spot, depending on the active tool).
|
||||
g.addEventListener("click", (e) => e.stopPropagation());
|
||||
g.addEventListener("pointerdown", (e) => {
|
||||
e.stopPropagation();
|
||||
state.drag = { kind: "note", idx };
|
||||
state.drag = { kind: "note", idx, moved: false, downX: e.clientX, downY: e.clientY };
|
||||
g.classList.add("dragging");
|
||||
g.setPointerCapture(e.pointerId);
|
||||
});
|
||||
g.addEventListener("pointermove", (e) => {
|
||||
if (!state.drag || state.drag.kind !== "note" || state.drag.idx !== idx) return;
|
||||
if (Math.hypot(e.clientX - state.drag.downX, e.clientY - state.drag.downY) > 3) {
|
||||
state.drag.moved = true;
|
||||
}
|
||||
const pt = svgPointFromEvent(e);
|
||||
const arr = state.notes[currentKey()];
|
||||
arr[idx].x = pt.x; arr[idx].y = pt.y;
|
||||
@@ -269,29 +275,40 @@ function makeNotePin(note, idx) {
|
||||
});
|
||||
g.addEventListener("pointerup", () => {
|
||||
if (!state.drag) return;
|
||||
const moved = state.drag.moved;
|
||||
state.drag = null;
|
||||
g.classList.remove("dragging");
|
||||
pushHistory();
|
||||
updateHash();
|
||||
});
|
||||
g.addEventListener("contextmenu", (e) => {
|
||||
e.preventDefault();
|
||||
removeNote(idx);
|
||||
});
|
||||
g.addEventListener("dblclick", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const txt = prompt("Edit note text", note.text || "");
|
||||
if (txt !== null) {
|
||||
state.notes[currentKey()][idx].text = txt;
|
||||
if (moved) {
|
||||
pushHistory();
|
||||
renderOverlay();
|
||||
updateHash();
|
||||
}
|
||||
});
|
||||
g.addEventListener("contextmenu", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removeNote(idx);
|
||||
});
|
||||
// Double-click → edit. Works regardless of active tool because the
|
||||
// pin captures click + dblclick before the canvas click handler runs.
|
||||
g.addEventListener("dblclick", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
editNote(idx);
|
||||
});
|
||||
return g;
|
||||
}
|
||||
|
||||
function editNote(idx) {
|
||||
const cur = state.notes[currentKey()]?.[idx];
|
||||
if (!cur) return;
|
||||
const txt = prompt("Edit note text:", cur.text || "");
|
||||
if (txt === null) return;
|
||||
cur.text = txt;
|
||||
pushHistory();
|
||||
renderOverlay();
|
||||
updateHash();
|
||||
}
|
||||
|
||||
function removeNote(idx) {
|
||||
const key = currentKey();
|
||||
const arr = state.notes[key];
|
||||
@@ -338,15 +355,21 @@ function makeTextLabel(item, idx) {
|
||||
hit.setAttribute("fill", "transparent");
|
||||
g.appendChild(hit);
|
||||
|
||||
// Drag / right-click delete / dbl-click edit
|
||||
// Stop click propagating so the canvas's tool handler doesn't fire when
|
||||
// the user is interacting with this label (otherwise editing in Route
|
||||
// mode would also drop a waypoint underneath).
|
||||
g.addEventListener("click", (e) => e.stopPropagation());
|
||||
g.addEventListener("pointerdown", (e) => {
|
||||
e.stopPropagation();
|
||||
state.drag = { kind: "text", idx };
|
||||
state.drag = { kind: "text", idx, moved: false, downX: e.clientX, downY: e.clientY };
|
||||
g.classList.add("dragging");
|
||||
g.setPointerCapture(e.pointerId);
|
||||
});
|
||||
g.addEventListener("pointermove", (e) => {
|
||||
if (!state.drag || state.drag.kind !== "text" || state.drag.idx !== idx) return;
|
||||
if (Math.hypot(e.clientX - state.drag.downX, e.clientY - state.drag.downY) > 3) {
|
||||
state.drag.moved = true;
|
||||
}
|
||||
const pt = svgPointFromEvent(e);
|
||||
const arr = state.texts[currentKey()];
|
||||
arr[idx].x = pt.x; arr[idx].y = pt.y;
|
||||
@@ -354,25 +377,23 @@ function makeTextLabel(item, idx) {
|
||||
});
|
||||
g.addEventListener("pointerup", () => {
|
||||
if (!state.drag) return;
|
||||
const moved = state.drag.moved;
|
||||
state.drag = null;
|
||||
g.classList.remove("dragging");
|
||||
pushHistory();
|
||||
updateHash();
|
||||
if (moved) {
|
||||
pushHistory();
|
||||
updateHash();
|
||||
}
|
||||
});
|
||||
g.addEventListener("contextmenu", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removeText(idx);
|
||||
});
|
||||
g.addEventListener("dblclick", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const txt = prompt("Edit label", item.text || "");
|
||||
if (txt !== null) {
|
||||
state.texts[currentKey()][idx].text = txt;
|
||||
pushHistory();
|
||||
renderOverlay();
|
||||
updateHash();
|
||||
}
|
||||
editText(idx);
|
||||
});
|
||||
return g;
|
||||
}
|
||||
@@ -387,6 +408,17 @@ function removeText(idx) {
|
||||
updateHash();
|
||||
}
|
||||
|
||||
function editText(idx) {
|
||||
const cur = state.texts[currentKey()]?.[idx];
|
||||
if (!cur) return;
|
||||
const txt = prompt("Edit label:", cur.text || "");
|
||||
if (txt === null) return;
|
||||
cur.text = txt;
|
||||
pushHistory();
|
||||
renderOverlay();
|
||||
updateHash();
|
||||
}
|
||||
|
||||
// kg classification IDs:
|
||||
// 1 = minor (NPCs, ambient mobs)
|
||||
// 2 = standard trash
|
||||
@@ -555,14 +587,18 @@ function makeUserPin(x, y, label, cssClass, kind, idx) {
|
||||
t.textContent = label;
|
||||
g.appendChild(t);
|
||||
|
||||
g.addEventListener("click", (e) => e.stopPropagation());
|
||||
g.addEventListener("pointerdown", (e) => {
|
||||
e.stopPropagation();
|
||||
state.drag = { kind, idx };
|
||||
state.drag = { kind, idx, moved: false, downX: e.clientX, downY: e.clientY };
|
||||
g.classList.add("dragging");
|
||||
g.setPointerCapture(e.pointerId);
|
||||
});
|
||||
g.addEventListener("pointermove", (e) => {
|
||||
if (!state.drag || state.drag.idx !== idx || state.drag.kind !== kind) return;
|
||||
if (Math.hypot(e.clientX - state.drag.downX, e.clientY - state.drag.downY) > 3) {
|
||||
state.drag.moved = true;
|
||||
}
|
||||
const pt = svgPointFromEvent(e);
|
||||
const arr = kind === "route" ? state.routes[currentKey()] : state.pulls[currentKey()];
|
||||
arr[idx] = { x: pt.x, y: pt.y };
|
||||
@@ -579,14 +615,18 @@ function makeUserPin(x, y, label, cssClass, kind, idx) {
|
||||
});
|
||||
g.addEventListener("pointerup", () => {
|
||||
if (!state.drag) return;
|
||||
const moved = state.drag.moved;
|
||||
state.drag = null;
|
||||
g.classList.remove("dragging");
|
||||
pushHistory();
|
||||
renderInfoPane();
|
||||
updateHash();
|
||||
if (moved) {
|
||||
pushHistory();
|
||||
renderInfoPane();
|
||||
updateHash();
|
||||
}
|
||||
});
|
||||
g.addEventListener("contextmenu", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
removePoint(kind, idx);
|
||||
});
|
||||
return g;
|
||||
|
||||
Reference in New Issue
Block a user