zijlijn concepten beter geplaatst

This commit is contained in:
2026-09-03 08:50:20 +02:00
parent 2c141b6d3f
commit 1abc84489f
5 changed files with 49 additions and 26 deletions
@@ -9,6 +9,7 @@ export class CmapInteractionController {
this.activeMarqueeCleanup = null; this.activeMarqueeCleanup = null;
this.canvasPanPointerDownHandler = null; this.canvasPanPointerDownHandler = null;
this.activeCanvasPanCleanup = null; this.activeCanvasPanCleanup = null;
this.boundaryRefreshScheduled = false;
} }
get items() { return this.editor.items; } get items() { return this.editor.items; }
@@ -24,9 +25,19 @@ export class CmapInteractionController {
this.moveSubmapGroup(record, x, y, moveMembership); this.moveSubmapGroup(record, x, y, moveMembership);
queueMicrotask(() => this.redrawAllConnectors()); queueMicrotask(() => this.redrawAllConnectors());
} }
this.scheduleBoundaryRefresh();
return { x, y }; return { x, y };
} }
scheduleBoundaryRefresh() {
if (this.boundaryRefreshScheduled) return;
this.boundaryRefreshScheduled = true;
window.requestAnimationFrame(() => {
this.boundaryRefreshScheduled = false;
if (!this.editor.destroyed) this.editor.refreshBoundaryReferences();
});
}
beginItemMove(record, includeDescendants = false) { beginItemMove(record, includeDescendants = false) {
if (record.moveMembership) return record.moveMembership; if (record.moveMembership) return record.moveMembership;
let groupItems = [record]; let groupItems = [record];
+5 -5
View File
@@ -2,6 +2,8 @@
import { debug, elementDescription, selectionStyle, escapeHtml } from "../cmap-utils.js"; import { debug, elementDescription, selectionStyle, escapeHtml } from "../cmap-utils.js";
const BOUNDARY_LINE_COLOR = "#77838e";
export class CmapItemDecorator { export class CmapItemDecorator {
constructor(editor) { constructor(editor) {
this.editor = editor; this.editor = editor;
@@ -457,7 +459,7 @@ export class CmapItemDecorator {
marker.setAttribute("orient", "auto-start-reverse"); marker.setAttribute("orient", "auto-start-reverse");
const arrow = document.createElementNS("http://www.w3.org/2000/svg", "path"); const arrow = document.createElementNS("http://www.w3.org/2000/svg", "path");
arrow.setAttribute("d", "M 0 0 L 10 5 L 0 10 z"); arrow.setAttribute("d", "M 0 0 L 10 5 L 0 10 z");
arrow.setAttribute("fill", "#4a5560"); arrow.setAttribute("fill", BOUNDARY_LINE_COLOR);
marker.append(arrow); marker.append(arrow);
definitions.append(marker); definitions.append(marker);
svg.append(definitions); svg.append(definitions);
@@ -484,9 +486,7 @@ export class CmapItemDecorator {
(Number(crossing.insideConcept.node.attr("width")) / 2); (Number(crossing.insideConcept.node.attr("width")) / 2);
const insideY = Number(crossing.insideConcept.node.attr("y")) + const insideY = Number(crossing.insideConcept.node.attr("y")) +
(Number(crossing.insideConcept.node.attr("height")) / 2); (Number(crossing.insideConcept.node.attr("height")) / 2);
const outsideX = Number(crossing.outsideConcept.node.attr("x")) + const side = insideX <= viewLeft + (viewWidth / 2) ? "left" : "right";
(Number(crossing.outsideConcept.node.attr("width")) / 2);
const side = outsideX < insideX ? "left" : "right";
const x = side === "left" ? viewLeft + 12 : viewLeft + viewWidth - buttonWidth - 12; const x = side === "left" ? viewLeft + 12 : viewLeft + viewWidth - buttonWidth - 12;
const y = reserveY(side, insideY - 15); const y = reserveY(side, insideY - 15);
const button = document.createElement("button"); const button = document.createElement("button");
@@ -512,7 +512,7 @@ export class CmapItemDecorator {
const endY = crossing.sourceInside ? boundaryY : insideY; const endY = crossing.sourceInside ? boundaryY : insideY;
path.setAttribute("d", `M ${startX} ${startY} L ${endX} ${endY}`); path.setAttribute("d", `M ${startX} ${startY} L ${endX} ${endY}`);
path.setAttribute("fill", "none"); path.setAttribute("fill", "none");
path.setAttribute("stroke", crossing.connector.lineColor || "#4a5560"); path.setAttribute("stroke", BOUNDARY_LINE_COLOR);
path.setAttribute("stroke-width", String(crossing.connector.lineWidth || 2)); path.setAttribute("stroke-width", String(crossing.connector.lineWidth || 2));
if (crossing.connector.hasArrow) path.setAttribute("marker-end", "url(#rw-cmap-boundary-arrow)"); if (crossing.connector.hasArrow) path.setAttribute("marker-end", "url(#rw-cmap-boundary-arrow)");
svg.append(path); svg.append(path);
+1 -1
View File
@@ -147,7 +147,7 @@
<section id="cmap-view" class="hidden"> <section id="cmap-view" class="hidden">
<header class="cmap-page-header"> <header class="cmap-page-header">
<div class="cmap-storage-controls"> <div class="cmap-storage-controls">
<span id="cmap-map-navigation" class="cmap-map-navigation hidden"><button id="cmap-map-back" type="button" aria-label="Back to parent map"></button><strong id="cmap-active-map-title"></strong></span> <span id="cmap-map-navigation" class="cmap-map-navigation hidden"><strong id="cmap-active-map-title"></strong></span>
<div id="cmap-map-combobox" class="wiki-combobox"> <div id="cmap-map-combobox" class="wiki-combobox">
<input id="cmap-map-select" type="search" role="combobox" <input id="cmap-map-select" type="search" role="combobox"
aria-label="Concept map" data-tr-placeholder="select-concept-map" aria-label="Concept map" data-tr-placeholder="select-concept-map"
+1 -20
View File
@@ -776,12 +776,7 @@ export class CmapWorkspace {
navigateToHash(cmapRoute(record.cmapSlug)).catch((error) => console.error(error)); navigateToHash(cmapRoute(record.cmapSlug)).catch((error) => console.error(error));
} }
}, },
onOpenBoundaryReference: () => { onOpenBoundaryReference: () => openParentCmap(),
const source = state.currentConceptMapSource;
if (source) {
navigateToHash(cmapRoute(source.slug)).catch((error) => console.error(error));
}
},
onOpenSubMap: (record) => { onOpenSubMap: (record) => {
console.info("[racket-wiki:cmap-host 0.2.122] submap state changed", { console.info("[racket-wiki:cmap-host 0.2.122] submap state changed", {
id: record.id, id: record.id,
@@ -1713,20 +1708,6 @@ export class CmapWorkspace {
markCurrentCmapSaved(); markCurrentCmapSaved();
} }
}); });
const navigateToParentCmap = () => {
const prototype = cmapPrototypeState();
if (prototype.editor && prototype.editor.canStepBackWithinMap()) {
prototype.editor.openParentMap();
return;
}
if (state.currentConceptMapSource) {
navigateToHash(cmapRoute(state.currentConceptMapSource.slug))
.catch((error) => console.error(error));
return;
}
if (prototype.editor) prototype.editor.openParentMap();
};
$("cmap-map-navigation").addEventListener("click", navigateToParentCmap);
$("cmap-canvas").addEventListener("contextmenu", (event) => { $("cmap-canvas").addEventListener("contextmenu", (event) => {
event.preventDefault(); event.preventDefault();
const prototype = cmapPrototypeState(); const prototype = cmapPrototypeState();
+31
View File
@@ -218,3 +218,34 @@ test("moving a concept also moves connected concepts that link to another CMap",
linked: [500, 140] linked: [500, 140]
}); });
}); });
test("moving a concept schedules one boundary-reference geometry refresh", async () => {
const factory = await loadEditorFactory();
const frames = [];
global.window.requestAnimationFrame = (callback) => frames.push(callback);
const map = {
onSelection() {},
onActivation() {},
node: (attributes) => fakeNode(attributes)
};
const canvas = {
addEventListener() {},
querySelector: () => null,
clientWidth: 1200,
clientHeight: 800,
scrollLeft: 0,
scrollTop: 0,
isConnected: true
};
const editor = factory.createEditor(canvas, { createDiagramEngine: () => map });
const concept = editor.addItem({ id: 1, label: "Inside concept", x: 100, y: 100 });
let refreshCount = 0;
editor.refreshBoundaryReferences = () => { refreshCount += 1; };
editor.handleItemMove(concept, 150, 130);
editor.handleItemMove(concept, 170, 140);
assert.equal(frames.length, 1, "pointer moves in one frame are coalesced");
frames[0]();
assert.equal(refreshCount, 1);
});