big cmap refactoring
This commit is contained in:
@@ -0,0 +1,568 @@
|
||||
"use strict";
|
||||
|
||||
import { debug, elementDescription } from "../cmap-utils.js";
|
||||
|
||||
export class CmapInteractionController {
|
||||
constructor(editor) {
|
||||
this.editor = editor;
|
||||
this.marqueeMouseDownHandler = null;
|
||||
this.activeMarqueeCleanup = null;
|
||||
this.canvasPanPointerDownHandler = null;
|
||||
this.activeCanvasPanCleanup = null;
|
||||
}
|
||||
|
||||
get items() { return this.editor.items; }
|
||||
get connectors() { return this.editor.connectors; }
|
||||
get canvas() { return this.editor.canvas; }
|
||||
get zoomFactor() { return this.editor.zoomFactor; }
|
||||
|
||||
handleItemMove(record, x, y) {
|
||||
const movesCompleteSubmap = false;
|
||||
const movesSelection = this.editor.selectedItems.has(record) && this.editor.selectedItems.size > 1;
|
||||
const moveMembership = this.beginItemMove(record, movesCompleteSubmap || movesSelection);
|
||||
if (moveMembership.groupPositions.length > 1) {
|
||||
this.moveSubmapGroup(record, x, y, moveMembership);
|
||||
queueMicrotask(() => this.redrawAllConnectors());
|
||||
}
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
beginItemMove(record, includeDescendants = false) {
|
||||
if (record.moveMembership) return record.moveMembership;
|
||||
let groupItems = [record];
|
||||
if (includeDescendants) {
|
||||
const selected = this.editor.selectedItems.has(record) && this.editor.selectedItems.size > 1 ?
|
||||
this.editor.selectedAll() : [record];
|
||||
const expanded = [];
|
||||
for (const item of selected) {
|
||||
expanded.push(item);
|
||||
if (item.kind === "submap" && item !== this.editor.activeMapRoot) {
|
||||
expanded.push(...this.items.filter((candidate) => this.editor.isDescendantOf(candidate, item)));
|
||||
}
|
||||
}
|
||||
groupItems = Array.from(new Set(expanded));
|
||||
}
|
||||
groupItems = this.includeLinkedMapItems(groupItems);
|
||||
record.moveMembership = {
|
||||
parent: record.parentSubmap,
|
||||
parentBounds: record.parentSubmap ? this.editor.submapBounds(record.parentSubmap) : null,
|
||||
startX: Number(record.node.attr("x")),
|
||||
startY: Number(record.node.attr("y")),
|
||||
groupPositions: groupItems.map((item) => ({
|
||||
item,
|
||||
x: Number(item.node.attr("x")),
|
||||
y: Number(item.node.attr("y"))
|
||||
}))
|
||||
};
|
||||
return record.moveMembership;
|
||||
}
|
||||
|
||||
includeLinkedMapItems(groupItems) {
|
||||
const group = new Set(groupItems);
|
||||
const pending = [...groupItems];
|
||||
while (pending.length) {
|
||||
const current = pending.pop();
|
||||
for (const connector of this.connectors) {
|
||||
if (connector.source !== current && connector.target !== current) continue;
|
||||
const other = connector.source === current ? connector.target : connector.source;
|
||||
const include = other.kind === "phrase" || Boolean(other.cmapSlug);
|
||||
if (!include || group.has(other)) continue;
|
||||
group.add(other);
|
||||
pending.push(other);
|
||||
}
|
||||
}
|
||||
return Array.from(group);
|
||||
}
|
||||
|
||||
moveSubmapGroup(record, x, y, moveMembership = this.beginItemMove(record, true)) {
|
||||
const deltaX = x - moveMembership.startX;
|
||||
const deltaY = y - moveMembership.startY;
|
||||
for (const position of moveMembership.groupPositions) {
|
||||
position.item.node.attr({
|
||||
x: position.x + deltaX,
|
||||
y: position.y + deltaY
|
||||
});
|
||||
position.item.node.redraw();
|
||||
}
|
||||
for (const connector of this.connectors) {
|
||||
connector.link.straighten();
|
||||
connector.link.redraw();
|
||||
}
|
||||
for (const submap of this.items
|
||||
.filter((item) => item.kind === "submap")
|
||||
.sort((a, b) => b.submapDepth - a.submapDepth)) {
|
||||
this.editor.updateSubmapFrame(submap);
|
||||
}
|
||||
}
|
||||
|
||||
redrawAllConnectors() {
|
||||
for (const connector of this.connectors) {
|
||||
connector.link.straighten();
|
||||
connector.link.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
handleItemMoveEnd(record) {
|
||||
const moveMembership = record.moveMembership;
|
||||
record.moveMembership = null;
|
||||
if (!moveMembership) return;
|
||||
this.redrawAllConnectors();
|
||||
|
||||
const movedItems = this.editor.selectedAll().filter((item) =>
|
||||
moveMembership.groupPositions.some((position) => position.item === item));
|
||||
const movedParents = new Set(movedItems.map((item) => item.parentSubmap));
|
||||
if (movedItems.length > 1 && movedParents.size === 1) {
|
||||
const previousParent = movedItems[0].parentSubmap;
|
||||
const centers = movedItems.map((item) => this.editor.itemCenter(item));
|
||||
const center = {
|
||||
x: centers.reduce((sum, point) => sum + point.x, 0) / centers.length,
|
||||
y: centers.reduce((sum, point) => sum + point.y, 0) / centers.length
|
||||
};
|
||||
let parent = null;
|
||||
if (previousParent && this.editor.pointInBounds(center, moveMembership.parentBounds)) {
|
||||
parent = previousParent;
|
||||
} else {
|
||||
parent = this.editor.submapAtPoint(center, null, movedItems);
|
||||
}
|
||||
if (!parent && this.editor.activeMapRoot && !movedItems.includes(this.editor.activeMapRoot)) {
|
||||
parent = this.editor.activeMapRoot;
|
||||
}
|
||||
if (previousParent && parent !== previousParent && this.editor.onConfirmDetachFromSubmap &&
|
||||
!this.editor.onConfirmDetachFromSubmap(record, previousParent, parent)) {
|
||||
parent = previousParent;
|
||||
}
|
||||
if (parent !== previousParent) {
|
||||
for (const item of movedItems) {
|
||||
if (item === this.editor.activeMapRoot) continue;
|
||||
item.parentSubmap = parent;
|
||||
this.editor.updateSubmapDepth(item, parent ? parent.submapDepth + 1 : 0);
|
||||
}
|
||||
this.editor.reconcilePhraseMembership();
|
||||
this.editor.refreshConceptMapReferences();
|
||||
debug("selection submap membership changed", {
|
||||
itemIds: movedItems.map((item) => item.id),
|
||||
previousParentId: previousParent ? previousParent.id : null,
|
||||
parentId: parent ? parent.id : null
|
||||
});
|
||||
}
|
||||
this.editor.refreshSubmapVisibility();
|
||||
this.editor.scheduleHistoryCommit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.kind === "phrase") {
|
||||
this.editor.scheduleHistoryCommit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (record === this.editor.activeMapRoot) {
|
||||
this.editor.refreshSubmapVisibility();
|
||||
debug("active map head moved without changing parent membership", {
|
||||
id: record.id,
|
||||
parentId: record.parentSubmap ? record.parentSubmap.id : null
|
||||
});
|
||||
this.editor.scheduleHistoryCommit();
|
||||
return;
|
||||
}
|
||||
|
||||
const center = this.editor.itemCenter(record);
|
||||
let parent = null;
|
||||
if (moveMembership.parent && this.editor.pointInBounds(center, moveMembership.parentBounds)) {
|
||||
parent = moveMembership.parent;
|
||||
} else {
|
||||
parent = this.editor.submapAtPoint(center, record);
|
||||
}
|
||||
if (!parent && this.editor.activeMapRoot && record !== this.editor.activeMapRoot) parent = this.editor.activeMapRoot;
|
||||
|
||||
if (moveMembership.parent && parent !== moveMembership.parent &&
|
||||
this.editor.onConfirmDetachFromSubmap &&
|
||||
!this.editor.onConfirmDetachFromSubmap(record, moveMembership.parent, parent)) {
|
||||
parent = moveMembership.parent;
|
||||
}
|
||||
|
||||
if (parent !== record.parentSubmap) {
|
||||
const previousParent = record.parentSubmap;
|
||||
record.parentSubmap = parent;
|
||||
this.editor.updateSubmapDepth(record, parent ? parent.submapDepth + 1 : 0);
|
||||
debug("item submap membership changed", {
|
||||
id: record.id,
|
||||
previousParentId: previousParent ? previousParent.id : null,
|
||||
parentId: parent ? parent.id : null
|
||||
});
|
||||
this.editor.reconcilePhraseMembership();
|
||||
this.editor.refreshConceptMapReferences();
|
||||
}
|
||||
this.editor.refreshSubmapVisibility();
|
||||
this.editor.scheduleHistoryCommit();
|
||||
}
|
||||
|
||||
startSubmapFrameDrag(event, record) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const additive = event.ctrlKey || event.metaKey || event.shiftKey;
|
||||
if (!additive && this.editor.selectedItems.size > 1 && this.editor.selectedItems.has(record)) {
|
||||
this.editor.selectedItem = record;
|
||||
this.editor.refreshSelectionDecoration();
|
||||
this.editor.notifySelection();
|
||||
} else {
|
||||
this.editor.selectItem(record, { additive });
|
||||
}
|
||||
const pointerId = event.pointerId;
|
||||
const startClientX = event.clientX;
|
||||
const startClientY = event.clientY;
|
||||
const bounds = this.editor.submapBounds(record);
|
||||
if (!bounds) return;
|
||||
const moveMembership = {
|
||||
parent: record.parentSubmap,
|
||||
parentBounds: null,
|
||||
startX: bounds.left,
|
||||
startY: bounds.top,
|
||||
groupPositions: this.items
|
||||
.filter((item) => this.editor.isDescendantOf(item, record))
|
||||
.map((item) => ({
|
||||
item,
|
||||
x: Number(item.node.attr("x")),
|
||||
y: Number(item.node.attr("y"))
|
||||
}))
|
||||
};
|
||||
record.moveMembership = moveMembership;
|
||||
|
||||
const move = (moveEvent) => {
|
||||
if (moveEvent.pointerId !== pointerId) return;
|
||||
moveEvent.preventDefault();
|
||||
const x = moveMembership.startX +
|
||||
((moveEvent.clientX - startClientX) / this.zoomFactor);
|
||||
const y = moveMembership.startY +
|
||||
((moveEvent.clientY - startClientY) / this.zoomFactor);
|
||||
this.moveSubmapGroup(record, x, y, moveMembership);
|
||||
};
|
||||
|
||||
const up = (upEvent) => {
|
||||
if (upEvent.pointerId !== pointerId) return;
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
record.moveMembership = null;
|
||||
this.editor.saveCurrentContextLayout();
|
||||
this.editor.refreshSubmapVisibility();
|
||||
this.editor.scheduleHistoryCommit();
|
||||
};
|
||||
|
||||
window.addEventListener("pointermove", move);
|
||||
window.addEventListener("pointerup", up);
|
||||
}
|
||||
|
||||
installMarqueeSelection() {
|
||||
this.marqueeMouseDownHandler = (event) => {
|
||||
if (event.button !== 0) return;
|
||||
if (!(event.target instanceof Element)) return;
|
||||
if (event.target.closest(
|
||||
"[data-rw-cmap-item-id], [data-rw-cmap-connector-id], .rw-cmap-submap-frame, .rw-cmap-handle")) return;
|
||||
|
||||
const start = this.editor.canvasPoint(event);
|
||||
if (this.editor.pointNearConnector(start)) return;
|
||||
if (this.activeMarqueeCleanup) this.activeMarqueeCleanup();
|
||||
const additive = event.ctrlKey || event.metaKey || event.shiftKey;
|
||||
const surface = this.editor.surfaceElement() || this.canvas;
|
||||
const marquee = document.createElement("div");
|
||||
marquee.className = "rw-cmap-marquee";
|
||||
Object.assign(marquee.style, { left: `${start.x}px`, top: `${start.y}px`, width: "0", height: "0" });
|
||||
surface.append(marquee);
|
||||
|
||||
const cleanup = () => {
|
||||
window.removeEventListener("mousemove", move);
|
||||
window.removeEventListener("mouseup", up);
|
||||
marquee.remove();
|
||||
if (this.activeMarqueeCleanup === cleanup) this.activeMarqueeCleanup = null;
|
||||
};
|
||||
|
||||
const move = (moveEvent) => {
|
||||
const point = this.editor.canvasPoint(moveEvent);
|
||||
const left = Math.min(start.x, point.x);
|
||||
const top = Math.min(start.y, point.y);
|
||||
Object.assign(marquee.style, {
|
||||
left: `${left}px`,
|
||||
top: `${top}px`,
|
||||
width: `${Math.abs(point.x - start.x)}px`,
|
||||
height: `${Math.abs(point.y - start.y)}px`
|
||||
});
|
||||
};
|
||||
|
||||
const up = (upEvent) => {
|
||||
const point = this.editor.canvasPoint(upEvent);
|
||||
cleanup();
|
||||
const bounds = {
|
||||
left: Math.min(start.x, point.x),
|
||||
top: Math.min(start.y, point.y),
|
||||
right: Math.max(start.x, point.x),
|
||||
bottom: Math.max(start.y, point.y)
|
||||
};
|
||||
if (bounds.right - bounds.left < 4 && bounds.bottom - bounds.top < 4) {
|
||||
if (!additive) this.editor.clearSelection();
|
||||
return;
|
||||
}
|
||||
if (!additive) this.editor.clearSelection(false);
|
||||
const matches = this.items.filter((item) => {
|
||||
if (!this.editor.isEffectiveItemVisible(item)) return false;
|
||||
const left = Number(item.node.attr("x"));
|
||||
const top = Number(item.node.attr("y"));
|
||||
const right = left + Number(item.node.attr("width"));
|
||||
const bottom = top + Number(item.node.attr("height"));
|
||||
return right >= bounds.left && left <= bounds.right &&
|
||||
bottom >= bounds.top && top <= bounds.bottom;
|
||||
});
|
||||
const expanded = new Set(matches);
|
||||
for (const item of matches) {
|
||||
if (!item.groupId) continue;
|
||||
for (const member of this.items.filter((candidate) =>
|
||||
candidate.groupId === item.groupId && this.editor.isItemVisible(candidate))) expanded.add(member);
|
||||
}
|
||||
for (const item of expanded) this.editor.selectedItems.add(item);
|
||||
this.editor.selectedItem = matches.at(-1) || this.editor.selectedItem;
|
||||
this.editor.selectedConnector = null;
|
||||
this.editor.refreshSelectionDecoration();
|
||||
this.editor.notifySelection();
|
||||
debug("marquee selection applied", {
|
||||
selectedIds: this.editor.selectedAll().map((item) => item.id)
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("mousemove", move);
|
||||
window.addEventListener("mouseup", up);
|
||||
this.activeMarqueeCleanup = cleanup;
|
||||
};
|
||||
this.canvas.addEventListener("mousedown", this.marqueeMouseDownHandler);
|
||||
}
|
||||
|
||||
installCanvasPanning() {
|
||||
this.canvasPanPointerDownHandler = (event) => {
|
||||
const target = event.target instanceof Element ? event.target : null;
|
||||
if (target && target.closest(
|
||||
"[data-rw-cmap-item-id], [data-rw-cmap-connector-id], .rw-cmap-submap-frame, .rw-cmap-handle")) return;
|
||||
if (event.button !== 1 && !(event.button === 0 && event.altKey)) return;
|
||||
event.preventDefault();
|
||||
if (this.activeCanvasPanCleanup) this.activeCanvasPanCleanup();
|
||||
const startX = event.clientX;
|
||||
const startY = event.clientY;
|
||||
const startScrollLeft = this.canvas.scrollLeft;
|
||||
const startScrollTop = this.canvas.scrollTop;
|
||||
const pointerId = event.pointerId;
|
||||
this.canvas.classList.add("rw-cmap-canvas-panning");
|
||||
|
||||
const move = (moveEvent) => {
|
||||
if (moveEvent.pointerId !== pointerId) return;
|
||||
moveEvent.preventDefault();
|
||||
this.canvas.scrollLeft = startScrollLeft - (moveEvent.clientX - startX);
|
||||
this.canvas.scrollTop = startScrollTop - (moveEvent.clientY - startY);
|
||||
};
|
||||
const up = (upEvent) => {
|
||||
if (upEvent.pointerId !== pointerId) return;
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
this.canvas.classList.remove("rw-cmap-canvas-panning");
|
||||
if (this.activeCanvasPanCleanup === cleanup) this.activeCanvasPanCleanup = null;
|
||||
};
|
||||
const cleanup = () => {
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
this.canvas.classList.remove("rw-cmap-canvas-panning");
|
||||
if (this.activeCanvasPanCleanup === cleanup) this.activeCanvasPanCleanup = null;
|
||||
};
|
||||
|
||||
window.addEventListener("pointermove", move);
|
||||
window.addEventListener("pointerup", up);
|
||||
this.activeCanvasPanCleanup = cleanup;
|
||||
};
|
||||
this.canvas.addEventListener("pointerdown", this.canvasPanPointerDownHandler);
|
||||
}
|
||||
|
||||
handleMapSelection(component, event) {
|
||||
if (event.target instanceof Element &&
|
||||
event.target.closest(".rw-cmap-handle, .rw-cmap-phrase-input")) {
|
||||
debug("cmap selection belongs to an editor control", elementDescription(event.target));
|
||||
return;
|
||||
}
|
||||
|
||||
const item = this.items.find((candidate) => candidate.node === component) || null;
|
||||
const connector = this.connectors.find((candidate) => candidate.link === component) || null;
|
||||
debug("selection callback received from cmap hit test", {
|
||||
componentFound: Boolean(component),
|
||||
itemId: item ? item.id : null,
|
||||
connectorId: connector ? connector.id : null,
|
||||
target: elementDescription(event.target)
|
||||
});
|
||||
|
||||
if (item) {
|
||||
const additive = Boolean(event && (event.ctrlKey || event.metaKey || event.shiftKey));
|
||||
if (!additive && this.editor.selectedItems.size > 1 && this.editor.selectedItems.has(item)) {
|
||||
this.editor.selectedItem = item;
|
||||
this.editor.refreshSelectionDecoration();
|
||||
this.editor.notifySelection();
|
||||
return;
|
||||
}
|
||||
this.editor.selectItem(item, { additive, toggle: additive });
|
||||
return;
|
||||
}
|
||||
if (connector) {
|
||||
this.editor.selectConnector(connector);
|
||||
return;
|
||||
}
|
||||
if (!(event && (event.ctrlKey || event.metaKey || event.shiftKey))) this.editor.clearSelection();
|
||||
}
|
||||
|
||||
handleMapActivation(component, event) {
|
||||
const item = this.items.find((candidate) => candidate.node === component) || null;
|
||||
debug("activation callback received from cmap", {
|
||||
itemId: item ? item.id : null,
|
||||
kind: item ? item.kind : null,
|
||||
pageSlug: item ? item.pageSlug : null
|
||||
});
|
||||
if (!item) return;
|
||||
if (event && event.preventDefault) event.preventDefault();
|
||||
if (item.kind === "phrase") {
|
||||
this.editor.editPhraseInline(item);
|
||||
return;
|
||||
}
|
||||
if (this.editor.onEditItem) {
|
||||
this.editor.selectItem(item);
|
||||
this.editor.onEditItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
startResize(event, record) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
record.autoWidth = false;
|
||||
record.autoHeight = false;
|
||||
record.fitContentPending = false;
|
||||
const startX = event.clientX;
|
||||
const startY = event.clientY;
|
||||
const startWidth = Number(record.node.attr("width"));
|
||||
const startHeight = Number(record.node.attr("height"));
|
||||
const pointerId = event.pointerId;
|
||||
event.currentTarget.setPointerCapture(pointerId);
|
||||
|
||||
const move = (moveEvent) => {
|
||||
if (moveEvent.pointerId !== pointerId) return;
|
||||
record.width = Math.max(100, startWidth + ((moveEvent.clientX - startX) / this.zoomFactor));
|
||||
record.height = Math.max(42, startHeight + ((moveEvent.clientY - startY) / this.zoomFactor));
|
||||
record.node.attr({ width: record.width, height: record.height });
|
||||
record.node.redraw();
|
||||
this.editor.decorateItem(record);
|
||||
this.editor.redrawConnectorsFor(record);
|
||||
this.editor.ensureCanvasExtent(Number(record.node.attr("x")) + record.width,
|
||||
Number(record.node.attr("y")) + record.height);
|
||||
};
|
||||
|
||||
const up = (upEvent) => {
|
||||
if (upEvent.pointerId !== pointerId) return;
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
this.editor.decorateItem(record);
|
||||
this.editor.scheduleHistoryCommit();
|
||||
};
|
||||
|
||||
window.addEventListener("pointermove", move);
|
||||
window.addEventListener("pointerup", up);
|
||||
}
|
||||
|
||||
startRelationDrag(event, source) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const pointerId = event.pointerId;
|
||||
const start = this.editor.itemCenter(source);
|
||||
const draft = this.createDraftLine(start);
|
||||
this.editor.dragRelation = { source, draft };
|
||||
event.currentTarget.setPointerCapture(pointerId);
|
||||
|
||||
const move = (moveEvent) => {
|
||||
if (moveEvent.pointerId !== pointerId) return;
|
||||
const point = this.editor.canvasPoint(moveEvent);
|
||||
this.editor.ensureCanvasExtent(point.x, point.y);
|
||||
draft.line.setAttribute("x2", String(point.x));
|
||||
draft.line.setAttribute("y2", String(point.y));
|
||||
draft.svg.setAttribute("width", String(Math.max(this.editor.logicalCanvasWidth(), point.x + 180)));
|
||||
draft.svg.setAttribute("height", String(Math.max(this.editor.logicalCanvasHeight(), point.y + 180)));
|
||||
};
|
||||
|
||||
const up = (upEvent) => {
|
||||
if (upEvent.pointerId !== pointerId) return;
|
||||
window.removeEventListener("pointermove", move);
|
||||
window.removeEventListener("pointerup", up);
|
||||
const target = this.editor.itemAt(upEvent.clientX, upEvent.clientY);
|
||||
const point = this.editor.canvasPoint(upEvent);
|
||||
draft.svg.remove();
|
||||
this.editor.dragRelation = null;
|
||||
if (!target) {
|
||||
this.editor.ensureCanvasExtent(point.x, point.y);
|
||||
const parentSubmap = this.editor.submapAtPoint(point);
|
||||
debug("relation dropped on empty canvas", {
|
||||
sourceId: source.id,
|
||||
point,
|
||||
parentSubmapId: parentSubmap ? parentSubmap.id : null
|
||||
});
|
||||
if (this.editor.onCreateConnectedItem) this.editor.onCreateConnectedItem({ source, point, parentSubmap });
|
||||
return;
|
||||
}
|
||||
if (target === source) return;
|
||||
this.finishRelation(source, target, event.altKey || upEvent.altKey);
|
||||
};
|
||||
|
||||
window.addEventListener("pointermove", move);
|
||||
window.addEventListener("pointerup", up);
|
||||
}
|
||||
|
||||
finishRelation(source, target, direct = false) {
|
||||
if (source.kind === "phrase" && target.kind !== "phrase") {
|
||||
this.editor.addConnector(source, target, true);
|
||||
this.editor.reconcilePhraseMembership(source);
|
||||
this.editor.refreshSubmapVisibility();
|
||||
this.editor.selectItem(source);
|
||||
return;
|
||||
}
|
||||
if (source.kind !== "phrase" && target.kind === "phrase") {
|
||||
this.editor.addConnector(source, target, false);
|
||||
this.editor.reconcilePhraseMembership(target);
|
||||
this.editor.refreshSubmapVisibility();
|
||||
this.editor.selectItem(target);
|
||||
return;
|
||||
}
|
||||
if (source.kind === "phrase" && target.kind === "phrase") return;
|
||||
if (direct) {
|
||||
const connector = this.editor.addConnector(source, target, true);
|
||||
this.editor.refreshSubmapVisibility();
|
||||
this.editor.selectConnector(connector);
|
||||
return;
|
||||
}
|
||||
this.editor.connectWithPhrase(source, target, "?????", true);
|
||||
}
|
||||
|
||||
createDraftLine(start) {
|
||||
const ns = "http://www.w3.org/2000/svg";
|
||||
const svg = document.createElementNS(ns, "svg");
|
||||
svg.classList.add("rw-cmap-draft-layer");
|
||||
svg.setAttribute("width", String(this.editor.logicalCanvasWidth()));
|
||||
svg.setAttribute("height", String(this.editor.logicalCanvasHeight()));
|
||||
const line = document.createElementNS(ns, "line");
|
||||
line.setAttribute("x1", String(start.x));
|
||||
line.setAttribute("y1", String(start.y));
|
||||
line.setAttribute("x2", String(start.x));
|
||||
line.setAttribute("y2", String(start.y));
|
||||
line.setAttribute("class", "rw-cmap-draft-line");
|
||||
svg.append(line);
|
||||
(this.editor.surfaceElement() || this.canvas).append(svg);
|
||||
return { svg, line };
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.marqueeMouseDownHandler) {
|
||||
this.canvas.removeEventListener("mousedown", this.marqueeMouseDownHandler);
|
||||
this.marqueeMouseDownHandler = null;
|
||||
}
|
||||
if (this.activeMarqueeCleanup) this.activeMarqueeCleanup();
|
||||
if (this.canvasPanPointerDownHandler) {
|
||||
this.canvas.removeEventListener("pointerdown", this.canvasPanPointerDownHandler);
|
||||
this.canvasPanPointerDownHandler = null;
|
||||
}
|
||||
if (this.activeCanvasPanCleanup) this.activeCanvasPanCleanup();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user