827 lines
26 KiB
JavaScript
827 lines
26 KiB
JavaScript
"use strict";
|
|
|
|
const keyboard = require("../util/keyboard.js");
|
|
const views = require("../util/views.js");
|
|
const events = require("../events.js");
|
|
const misc = require("../util/misc.js");
|
|
const Note = require("../models/note.js");
|
|
const Point = require("../models/point.js");
|
|
|
|
const svgNS = "http://www.w3.org/2000/svg";
|
|
const snapThreshold = 10;
|
|
const circleSize = 10;
|
|
|
|
const MOUSE_BUTTON_LEFT = 1;
|
|
|
|
const KEY_LEFT = 37;
|
|
const KEY_UP = 38;
|
|
const KEY_RIGHT = 39;
|
|
const KEY_DOWN = 40;
|
|
const KEY_ESCAPE = 27;
|
|
const KEY_RETURN = 13;
|
|
|
|
function _getDistance(point1, point2) {
|
|
return Math.sqrt(
|
|
Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)
|
|
);
|
|
}
|
|
|
|
function _setNodeState(node, stateName) {
|
|
if (node === null) {
|
|
return;
|
|
}
|
|
node.setAttribute("data-state", stateName);
|
|
}
|
|
|
|
function _clearEditedNote(hostNode) {
|
|
const node = hostNode.querySelector("[data-state='editing']");
|
|
_setNodeState(node, null);
|
|
return node !== null;
|
|
}
|
|
|
|
function _getNoteCentroid(note) {
|
|
const vertexCount = note.polygon.length;
|
|
const centroid = new Point(0, 0);
|
|
let signedArea = 0.0;
|
|
for (let i of misc.range(vertexCount)) {
|
|
const x0 = note.polygon.at(i).x;
|
|
const y0 = note.polygon.at(i).y;
|
|
const x1 = note.polygon.at((i + 1) % vertexCount).x;
|
|
const y1 = note.polygon.at((i + 1) % vertexCount).y;
|
|
const a = x0 * y1 - x1 * y0;
|
|
signedArea += a;
|
|
centroid.x += (x0 + x1) * a;
|
|
centroid.y += (y0 + y1) * a;
|
|
}
|
|
signedArea *= 0.5;
|
|
centroid.x /= 6 * signedArea;
|
|
centroid.y /= 6 * signedArea;
|
|
return centroid;
|
|
}
|
|
|
|
function _getNoteSize(note) {
|
|
const min = new Point(Infinity, Infinity);
|
|
const max = new Point(-Infinity, -Infinity);
|
|
for (let point of note.polygon) {
|
|
min.x = Math.min(min.x, point.x);
|
|
min.y = Math.min(min.y, point.y);
|
|
max.x = Math.max(max.x, point.x);
|
|
max.y = Math.max(max.y, point.y);
|
|
}
|
|
return new Point(max.x - min.x, max.y - min.y);
|
|
}
|
|
|
|
class State {
|
|
constructor(control, stateName) {
|
|
this._control = control;
|
|
_setNodeState(control._hostNode, stateName);
|
|
_setNodeState(control._textNode, stateName);
|
|
}
|
|
|
|
get canShowNoteText() {
|
|
return false;
|
|
}
|
|
|
|
evtCanvasKeyDown(e) {}
|
|
|
|
evtNoteMouseDown(e, hoveredNote) {}
|
|
|
|
evtCanvasMouseDown(e) {}
|
|
|
|
evtCanvasMouseMove(e) {}
|
|
|
|
evtCanvasMouseUp(e) {}
|
|
|
|
_getScreenPoint(point) {
|
|
return new Point(
|
|
point.x * this._control.boundingBox.width,
|
|
point.y * this._control.boundingBox.height
|
|
);
|
|
}
|
|
|
|
_snapPoints(targetPoint, referencePoint) {
|
|
const targetScreenPoint = this._getScreenPoint(targetPoint);
|
|
const referenceScreenPoint = this._getScreenPoint(referencePoint);
|
|
if (
|
|
_getDistance(targetScreenPoint, referenceScreenPoint) <
|
|
snapThreshold
|
|
) {
|
|
targetPoint.x = referencePoint.x;
|
|
targetPoint.y = referencePoint.y;
|
|
}
|
|
}
|
|
|
|
_createNote() {
|
|
const note = new Note();
|
|
this._control._createPolygonNode(note);
|
|
return note;
|
|
}
|
|
|
|
_getPointFromEvent(e) {
|
|
return new Point(
|
|
(e.clientX - this._control.boundingBox.left) /
|
|
this._control.boundingBox.width,
|
|
(e.clientY - this._control.boundingBox.top) /
|
|
this._control.boundingBox.height
|
|
);
|
|
}
|
|
}
|
|
|
|
class ReadOnlyState extends State {
|
|
constructor(control) {
|
|
super(control, "read-only");
|
|
if (_clearEditedNote(control._hostNode)) {
|
|
this._control.dispatchEvent(new CustomEvent("blur"));
|
|
}
|
|
keyboard.unpause();
|
|
}
|
|
|
|
get canShowNoteText() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class PassiveState extends State {
|
|
constructor(control) {
|
|
super(control, "passive");
|
|
if (_clearEditedNote(control._hostNode)) {
|
|
this._control.dispatchEvent(new CustomEvent("blur"));
|
|
}
|
|
keyboard.unpause();
|
|
}
|
|
|
|
get canShowNoteText() {
|
|
return true;
|
|
}
|
|
|
|
evtNoteMouseDown(e, hoveredNote) {
|
|
this._control._state = new SelectedState(this._control, hoveredNote);
|
|
}
|
|
}
|
|
|
|
class ActiveState extends State {
|
|
constructor(control, note, stateName) {
|
|
super(control, stateName);
|
|
if (_clearEditedNote(control._hostNode)) {
|
|
this._control.dispatchEvent(new CustomEvent("blur"));
|
|
}
|
|
keyboard.pause();
|
|
if (note !== null) {
|
|
this._note = note;
|
|
this._control.dispatchEvent(
|
|
new CustomEvent("focus", {
|
|
detail: { note: note },
|
|
})
|
|
);
|
|
_setNodeState(this._note.groupNode, "editing");
|
|
}
|
|
}
|
|
}
|
|
|
|
class SelectedState extends ActiveState {
|
|
constructor(control, note) {
|
|
super(control, note, "selected");
|
|
this._clickTimeout = null;
|
|
this._control._hideNoteText();
|
|
}
|
|
|
|
evtCanvasKeyDown(e) {
|
|
const delta = e.ctrlKey ? 10 : 1;
|
|
const offsetMap = {
|
|
[KEY_LEFT]: [-delta, 0],
|
|
[KEY_UP]: [0, -delta],
|
|
[KEY_DOWN]: [0, delta],
|
|
[KEY_RIGHT]: [delta, 0],
|
|
};
|
|
if (Object.prototype.hasOwnProperty.call(offsetMap, e.witch)) {
|
|
e.stopPropagation();
|
|
e.stopImmediatePropagation();
|
|
e.preventDefault();
|
|
const args = offsetMap[e.which];
|
|
if (e.shiftKey) {
|
|
this._scaleEditedNote(...args);
|
|
} else {
|
|
this._moveEditedNote(...args);
|
|
}
|
|
}
|
|
}
|
|
|
|
evtNoteMouseDown(e, hoveredNote) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
const mouseScreenPoint = this._getScreenPoint(mousePoint);
|
|
if (e.shiftKey) {
|
|
this._control._state = new ScalingNoteState(
|
|
this._control,
|
|
this._note,
|
|
mousePoint
|
|
);
|
|
return;
|
|
}
|
|
if (this._note !== hoveredNote) {
|
|
this._control._state = new SelectedState(
|
|
this._control,
|
|
hoveredNote
|
|
);
|
|
return;
|
|
}
|
|
this._clickTimeout = window.setTimeout(() => {
|
|
for (let polygonPoint of this._note.polygon) {
|
|
const distance = _getDistance(
|
|
mouseScreenPoint,
|
|
this._getScreenPoint(polygonPoint)
|
|
);
|
|
if (distance < circleSize) {
|
|
this._control._state = new MovingPointState(
|
|
this._control,
|
|
this._note,
|
|
polygonPoint,
|
|
mousePoint
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
this._control._state = new MovingNoteState(
|
|
this._control,
|
|
this._note,
|
|
mousePoint
|
|
);
|
|
}, 100);
|
|
}
|
|
|
|
evtCanvasMouseMove(e) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
const mouseScreenPoint = this._getScreenPoint(mousePoint);
|
|
for (let polygonPoint of this._note.polygon) {
|
|
const distance = _getDistance(
|
|
mouseScreenPoint,
|
|
this._getScreenPoint(polygonPoint)
|
|
);
|
|
polygonPoint.edgeNode.classList.toggle(
|
|
"nearby",
|
|
distance < circleSize
|
|
);
|
|
}
|
|
}
|
|
|
|
evtCanvasMouseDown(e) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
const mouseScreenPoint = this._getScreenPoint(mousePoint);
|
|
if (e.shiftKey) {
|
|
this._control._state = new ScalingNoteState(
|
|
this._control,
|
|
this._note,
|
|
mousePoint
|
|
);
|
|
return;
|
|
}
|
|
for (let polygonPoint of this._note.polygon) {
|
|
const distance = _getDistance(
|
|
mouseScreenPoint,
|
|
this._getScreenPoint(polygonPoint)
|
|
);
|
|
if (distance < circleSize) {
|
|
this._control._state = new MovingPointState(
|
|
this._control,
|
|
this._note,
|
|
polygonPoint,
|
|
mousePoint
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
this._control._state = new PassiveState(this._control);
|
|
}
|
|
|
|
evtCanvasMouseUp(e) {
|
|
window.clearTimeout(this._clickTimeout);
|
|
}
|
|
|
|
_moveEditedNote(x, y) {
|
|
for (let point of this._note.polygon) {
|
|
point.x += x / this._control.boundingBox.width;
|
|
point.y += y / this._control.boundingBox.height;
|
|
}
|
|
}
|
|
|
|
_scaleEditedNote(x, y) {
|
|
const origin = _getNoteCentroid(this._note);
|
|
const originalSize = _getNoteSize(this._note);
|
|
const targetSize = new Point(
|
|
originalSize.x + x / this._control.boundingBox.width,
|
|
originalSize.y + y / this._control.boundingBox.height
|
|
);
|
|
const scale = new Point(
|
|
targetSize.x / originalSize.x,
|
|
targetSize.y / originalSize.y
|
|
);
|
|
for (let point of this._note.polygon) {
|
|
point.x = origin.x + (point.x - origin.x) * scale.x;
|
|
point.y = origin.y + (point.y - origin.y) * scale.y;
|
|
}
|
|
}
|
|
}
|
|
|
|
class MovingPointState extends ActiveState {
|
|
constructor(control, note, notePoint, mousePoint) {
|
|
super(control, note, "moving-point");
|
|
this._notePoint = notePoint;
|
|
this._originalNotePoint = { x: notePoint.x, y: notePoint.y };
|
|
this._originalPosition = mousePoint;
|
|
_setNodeState(this._note.groupNode, "editing");
|
|
}
|
|
|
|
evtCanvasKeyDown(e) {
|
|
if (e.which === KEY_ESCAPE) {
|
|
this._notePoint.x = this._originalNotePoint.x;
|
|
this._notePoint.y = this._originalNotePoint.y;
|
|
this._control._state = new SelectedState(
|
|
this._control,
|
|
this._note
|
|
);
|
|
}
|
|
}
|
|
|
|
evtCanvasMouseMove(e) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
this._notePoint.x += mousePoint.x - this._originalPosition.x;
|
|
this._notePoint.y += mousePoint.y - this._originalPosition.y;
|
|
this._originalPosition = mousePoint;
|
|
}
|
|
|
|
evtCanvasMouseUp(e) {
|
|
this._control._state = new SelectedState(this._control, this._note);
|
|
}
|
|
}
|
|
|
|
class MovingNoteState extends ActiveState {
|
|
constructor(control, note, mousePoint) {
|
|
super(control, note, "moving-note");
|
|
this._originalPolygon = [...note.polygon].map((point) => ({
|
|
x: point.x,
|
|
y: point.y,
|
|
}));
|
|
this._originalPosition = mousePoint;
|
|
}
|
|
|
|
evtCanvasKeyDown(e) {
|
|
if (e.which === KEY_ESCAPE) {
|
|
for (let i of misc.range(this._note.polygon.length)) {
|
|
this._note.polygon.at(i).x = this._originalPolygon[i].x;
|
|
this._note.polygon.at(i).y = this._originalPolygon[i].y;
|
|
}
|
|
this._control._state = new SelectedState(
|
|
this._control,
|
|
this._note
|
|
);
|
|
}
|
|
}
|
|
|
|
evtCanvasMouseMove(e) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
for (let point of this._note.polygon) {
|
|
point.x += mousePoint.x - this._originalPosition.x;
|
|
point.y += mousePoint.y - this._originalPosition.y;
|
|
}
|
|
this._originalPosition = mousePoint;
|
|
}
|
|
|
|
evtCanvasMouseUp(e) {
|
|
this._control._state = new SelectedState(this._control, this._note);
|
|
}
|
|
}
|
|
|
|
class ScalingNoteState extends ActiveState {
|
|
constructor(control, note, mousePoint) {
|
|
super(control, note, "scaling-note");
|
|
this._originalPolygon = [...note.polygon].map((point) => ({
|
|
x: point.x,
|
|
y: point.y,
|
|
}));
|
|
this._originalMousePoint = mousePoint;
|
|
this._originalSize = _getNoteSize(note);
|
|
}
|
|
|
|
evtCanvasKeyDown(e) {
|
|
if (e.which === KEY_ESCAPE) {
|
|
for (let i of misc.range(this._note.polygon.length)) {
|
|
this._note.polygon.at(i).x = this._originalPolygon[i].x;
|
|
this._note.polygon.at(i).y = this._originalPolygon[i].y;
|
|
}
|
|
this._control._state = new SelectedState(
|
|
this._control,
|
|
this._note
|
|
);
|
|
}
|
|
}
|
|
|
|
evtCanvasMouseMove(e) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
const originalMousePoint = this._originalMousePoint;
|
|
const originalSize = this._originalSize;
|
|
for (let i of misc.range(this._note.polygon.length)) {
|
|
const polygonPoint = this._note.polygon.at(i);
|
|
const originalPolygonPoint = this._originalPolygon[i];
|
|
polygonPoint.x =
|
|
originalMousePoint.x +
|
|
(originalPolygonPoint.x - originalMousePoint.x) *
|
|
(1 +
|
|
(mousePoint.x - originalMousePoint.x) /
|
|
originalSize.x);
|
|
polygonPoint.y =
|
|
originalMousePoint.y +
|
|
(originalPolygonPoint.y - originalMousePoint.y) *
|
|
(1 +
|
|
(mousePoint.y - originalMousePoint.y) /
|
|
originalSize.y);
|
|
}
|
|
}
|
|
|
|
evtCanvasMouseUp(e) {
|
|
this._control._state = new SelectedState(this._control, this._note);
|
|
}
|
|
}
|
|
|
|
class ReadyToDrawState extends ActiveState {
|
|
constructor(control) {
|
|
super(control, null, "ready-to-draw");
|
|
}
|
|
|
|
evtNoteMouseDown(e, hoveredNote) {
|
|
this._control._state = new SelectedState(this._control, hoveredNote);
|
|
}
|
|
|
|
evtCanvasMouseDown(e) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
if (e.shiftKey) {
|
|
this._control._state = new DrawingRectangleState(
|
|
this._control,
|
|
mousePoint
|
|
);
|
|
} else {
|
|
this._control._state = new DrawingPolygonState(
|
|
this._control,
|
|
mousePoint
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
class DrawingRectangleState extends ActiveState {
|
|
constructor(control, mousePoint) {
|
|
super(control, null, "drawing-rectangle");
|
|
this._note = this._createNote();
|
|
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
|
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
|
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
|
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
|
_setNodeState(this._note.groupNode, "drawing");
|
|
}
|
|
|
|
evtCanvasMouseUp(e) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
const x1 = this._note.polygon.at(0).x;
|
|
const y1 = this._note.polygon.at(0).y;
|
|
const x2 = this._note.polygon.at(2).x;
|
|
const y2 = this._note.polygon.at(2).y;
|
|
const width = (x2 - x1) * this._control.boundingBox.width;
|
|
const height = (y2 - y1) * this._control.boundingBox.height;
|
|
this._control._deleteDomNode(this._note);
|
|
if (width < 20 && height < 20) {
|
|
this._control._state = new ReadyToDrawState(this._control);
|
|
} else {
|
|
this._control._post.notes.add(this._note);
|
|
this._control._state = new SelectedState(
|
|
this._control,
|
|
this._note
|
|
);
|
|
}
|
|
}
|
|
|
|
evtCanvasMouseMove(e) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
this._note.polygon.at(1).x = mousePoint.x;
|
|
this._note.polygon.at(3).y = mousePoint.y;
|
|
this._note.polygon.at(2).x = mousePoint.x;
|
|
this._note.polygon.at(2).y = mousePoint.y;
|
|
}
|
|
}
|
|
|
|
class DrawingPolygonState extends ActiveState {
|
|
constructor(control, mousePoint) {
|
|
super(control, null, "drawing-polygon");
|
|
this._note = this._createNote();
|
|
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
|
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
|
_setNodeState(this._note.groupNode, "drawing");
|
|
}
|
|
|
|
evtCanvasKeyDown(e) {
|
|
if (e.which === KEY_ESCAPE) {
|
|
this._note.polygon.remove(this._note.polygon.secondLastPoint);
|
|
if (this._note.polygon.length === 1) {
|
|
this._cancel();
|
|
}
|
|
} else if (e.which === KEY_RETURN) {
|
|
this._finish();
|
|
}
|
|
}
|
|
|
|
evtNoteMouseDown(e, hoveredNote) {
|
|
this.evtCanvasMouseDown(e);
|
|
}
|
|
|
|
evtCanvasMouseDown(e) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
const firstPoint = this._note.polygon.firstPoint;
|
|
const mouseScreenPoint = this._getScreenPoint(mousePoint);
|
|
const firstScreenPoint = this._getScreenPoint(firstPoint);
|
|
if (_getDistance(mouseScreenPoint, firstScreenPoint) < snapThreshold) {
|
|
this._finish();
|
|
} else {
|
|
this._note.polygon.add(new Point(mousePoint.x, mousePoint.y));
|
|
}
|
|
}
|
|
|
|
evtCanvasMouseMove(e) {
|
|
const mousePoint = this._getPointFromEvent(e);
|
|
const lastPoint = this._note.polygon.lastPoint;
|
|
const secondLastPoint = this._note.polygon.secondLastPoint;
|
|
const firstPoint = this._note.polygon.firstPoint;
|
|
if (!lastPoint) {
|
|
return;
|
|
}
|
|
|
|
if (e.shiftKey && secondLastPoint) {
|
|
const direction =
|
|
(Math.round(
|
|
Math.atan2(
|
|
secondLastPoint.y - mousePoint.y,
|
|
secondLastPoint.x - mousePoint.x
|
|
) /
|
|
((2 * Math.PI) / 4)
|
|
) +
|
|
4) %
|
|
4;
|
|
if (direction === 0 || direction === 2) {
|
|
lastPoint.x = mousePoint.x;
|
|
lastPoint.y = secondLastPoint.y;
|
|
} else if (direction === 1 || direction === 3) {
|
|
lastPoint.x = secondLastPoint.x;
|
|
lastPoint.y = mousePoint.y;
|
|
}
|
|
} else {
|
|
lastPoint.x = mousePoint.x;
|
|
lastPoint.y = mousePoint.y;
|
|
}
|
|
this._snapPoints(lastPoint, firstPoint);
|
|
}
|
|
|
|
_cancel() {
|
|
this._control._deleteDomNode(this._note);
|
|
this._control._state = new ReadyToDrawState(this._control);
|
|
}
|
|
|
|
_finish() {
|
|
this._note.polygon.remove(this._note.polygon.lastPoint);
|
|
if (this._note.polygon.length <= 2) {
|
|
this._cancel();
|
|
} else {
|
|
this._control._deleteDomNode(this._note);
|
|
this._control._post.notes.add(this._note);
|
|
this._control._state = new SelectedState(
|
|
this._control,
|
|
this._note
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
class PostNotesOverlayControl extends events.EventTarget {
|
|
constructor(hostNode, post) {
|
|
super();
|
|
this._post = post;
|
|
this._hostNode = hostNode;
|
|
|
|
this._svgNode = document.createElementNS(svgNS, "svg");
|
|
this._svgNode.classList.add("resize-listener");
|
|
this._svgNode.classList.add("notes-overlay");
|
|
this._svgNode.setAttribute("preserveAspectRatio", "none");
|
|
this._svgNode.setAttribute("viewBox", "0 0 1 1");
|
|
for (let note of this._post.notes) {
|
|
this._createPolygonNode(note);
|
|
}
|
|
this._hostNode.appendChild(this._svgNode);
|
|
this._post.addEventListener("change", (e) => this._evtPostChange(e));
|
|
this._post.notes.addEventListener("remove", (e) => {
|
|
this._deleteDomNode(e.detail.note);
|
|
});
|
|
this._post.notes.addEventListener("add", (e) => {
|
|
this._createPolygonNode(e.detail.note);
|
|
});
|
|
|
|
const keyHandler = (e) => this._evtCanvasKeyDown(e);
|
|
document.addEventListener("keydown", keyHandler);
|
|
this._svgNode.addEventListener("mousedown", (e) =>
|
|
this._evtCanvasMouseDown(e)
|
|
);
|
|
this._svgNode.addEventListener("mouseup", (e) =>
|
|
this._evtCanvasMouseUp(e)
|
|
);
|
|
this._svgNode.addEventListener("mousemove", (e) =>
|
|
this._evtCanvasMouseMove(e)
|
|
);
|
|
|
|
const wrapperNode = document.createElement("div");
|
|
wrapperNode.classList.add("wrapper");
|
|
this._textNode = document.createElement("div");
|
|
this._textNode.classList.add("note-text");
|
|
this._textNode.appendChild(wrapperNode);
|
|
this._textNode.addEventListener("mouseleave", (e) =>
|
|
this._evtNoteMouseLeave(e)
|
|
);
|
|
document.body.appendChild(this._textNode);
|
|
|
|
views.monitorNodeRemoval(this._hostNode, () => {
|
|
this._hostNode.removeChild(this._svgNode);
|
|
document.removeEventListener("keydown", keyHandler);
|
|
document.body.removeChild(this._textNode);
|
|
this._state = new ReadOnlyState(this);
|
|
});
|
|
|
|
this._state = new ReadOnlyState(this);
|
|
}
|
|
|
|
switchToPassiveEdit() {
|
|
this._state = new PassiveState(this);
|
|
}
|
|
|
|
switchToDrawing() {
|
|
this._state = new ReadyToDrawState(this);
|
|
}
|
|
|
|
get boundingBox() {
|
|
return this._hostNode.getBoundingClientRect();
|
|
}
|
|
|
|
_evtPostChange(e) {
|
|
while (this._svgNode.childNodes.length) {
|
|
this._svgNode.removeChild(this._svgNode.firstChild);
|
|
}
|
|
this._post = e.detail.post;
|
|
for (let note of this._post.notes) {
|
|
this._createPolygonNode(note);
|
|
}
|
|
}
|
|
|
|
_evtCanvasKeyDown(e) {
|
|
const illegalNodeNames = ["textarea", "input", "select"];
|
|
if (illegalNodeNames.includes(e.target.nodeName.toLowerCase())) {
|
|
return;
|
|
}
|
|
this._state.evtCanvasKeyDown(e);
|
|
}
|
|
|
|
_evtCanvasMouseDown(e) {
|
|
e.preventDefault();
|
|
if (e.which !== MOUSE_BUTTON_LEFT) {
|
|
return;
|
|
}
|
|
const hoveredNode = document.elementFromPoint(e.clientX, e.clientY);
|
|
let hoveredNote = null;
|
|
for (let note of this._post.notes) {
|
|
if (note.polygonNode === hoveredNode) {
|
|
hoveredNote = note;
|
|
}
|
|
}
|
|
if (hoveredNote) {
|
|
this._state.evtNoteMouseDown(e, hoveredNote);
|
|
} else {
|
|
this._state.evtCanvasMouseDown(e);
|
|
}
|
|
}
|
|
|
|
_evtCanvasMouseUp(e) {
|
|
this._state.evtCanvasMouseUp(e);
|
|
}
|
|
|
|
_evtCanvasMouseMove(e) {
|
|
this._state.evtCanvasMouseMove(e);
|
|
}
|
|
|
|
_evtNoteMouseEnter(e, note) {
|
|
if (this._state.canShowNoteText) {
|
|
this._showNoteText(note);
|
|
}
|
|
}
|
|
|
|
_evtNoteMouseLeave(e) {
|
|
const newElement = e.relatedTarget;
|
|
if (
|
|
newElement === this._svgNode ||
|
|
(!this._svgNode.contains(newElement) &&
|
|
!this._textNode.contains(newElement) &&
|
|
newElement !== this._textNode)
|
|
) {
|
|
this._hideNoteText();
|
|
}
|
|
}
|
|
|
|
_showNoteText(note) {
|
|
this._textNode.querySelector(".wrapper").innerHTML =
|
|
misc.formatMarkdown(note.text);
|
|
this._textNode.style.display = "block";
|
|
const bodyRect = document.body.getBoundingClientRect();
|
|
const noteRect = this._textNode.getBoundingClientRect();
|
|
const svgRect = this.boundingBox;
|
|
const centroid = _getNoteCentroid(note);
|
|
const x =
|
|
-bodyRect.left +
|
|
svgRect.left +
|
|
svgRect.width * centroid.x -
|
|
noteRect.width / 2;
|
|
const y =
|
|
-bodyRect.top +
|
|
svgRect.top +
|
|
svgRect.height * centroid.y -
|
|
noteRect.height / 2;
|
|
this._textNode.style.left = x + "px";
|
|
this._textNode.style.top = y + "px";
|
|
}
|
|
|
|
_hideNoteText() {
|
|
this._textNode.style.display = "none";
|
|
}
|
|
|
|
_updatePolygonNotePoints(note) {
|
|
note.polygonNode.setAttribute(
|
|
"points",
|
|
[...note.polygon]
|
|
.map((point) => [point.x, point.y].join(","))
|
|
.join(" ")
|
|
);
|
|
}
|
|
|
|
_createEdgeNode(point, groupNode) {
|
|
const node = document.createElementNS(svgNS, "ellipse");
|
|
node.setAttribute("cx", point.x);
|
|
node.setAttribute("cy", point.y);
|
|
node.setAttribute("rx", circleSize / 2 / this.boundingBox.width);
|
|
node.setAttribute("ry", circleSize / 2 / this.boundingBox.height);
|
|
point.edgeNode = node;
|
|
groupNode.appendChild(node);
|
|
}
|
|
|
|
_deleteEdgeNode(point, note) {
|
|
this._updatePolygonNotePoints(note);
|
|
point.edgeNode.parentNode.removeChild(point.edgeNode);
|
|
}
|
|
|
|
_updateEdgeNode(point, note) {
|
|
this._updatePolygonNotePoints(note);
|
|
point.edgeNode.setAttribute("cx", point.x);
|
|
point.edgeNode.setAttribute("cy", point.y);
|
|
}
|
|
|
|
_deleteDomNode(note) {
|
|
note.groupNode.parentNode.removeChild(note.groupNode);
|
|
}
|
|
|
|
_createPolygonNode(note) {
|
|
const groupNode = document.createElementNS(svgNS, "g");
|
|
note.groupNode = groupNode;
|
|
{
|
|
const node = document.createElementNS(svgNS, "polygon");
|
|
note.polygonNode = node;
|
|
node.setAttribute("vector-effect", "non-scaling-stroke");
|
|
node.setAttribute("stroke-alignment", "inside");
|
|
node.addEventListener("mouseenter", (e) =>
|
|
this._evtNoteMouseEnter(e, note)
|
|
);
|
|
node.addEventListener("mouseleave", (e) =>
|
|
this._evtNoteMouseLeave(e)
|
|
);
|
|
this._updatePolygonNotePoints(note);
|
|
groupNode.appendChild(node);
|
|
}
|
|
for (let point of note.polygon) {
|
|
this._createEdgeNode(point, groupNode);
|
|
}
|
|
|
|
note.polygon.addEventListener("change", (e) => {
|
|
this._updateEdgeNode(e.detail.point, note);
|
|
this.dispatchEvent(new CustomEvent("change"));
|
|
});
|
|
note.polygon.addEventListener("remove", (e) => {
|
|
this._deleteEdgeNode(e.detail.point, note);
|
|
this.dispatchEvent(new CustomEvent("change"));
|
|
});
|
|
note.polygon.addEventListener("add", (e) => {
|
|
this._createEdgeNode(e.detail.point, groupNode);
|
|
this.dispatchEvent(new CustomEvent("change"));
|
|
});
|
|
|
|
this._svgNode.appendChild(groupNode);
|
|
}
|
|
}
|
|
|
|
module.exports = PostNotesOverlayControl;
|