zijlijn concepten beter geplaatst
This commit is contained in:
@@ -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];
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user