@aurigma/design-atoms
Version:
Design Atoms is a part of Customer's Canvas SDK which allows for manipulating individual design elements through your code.
465 lines • 25.1 kB
JavaScript
import { RectangleF, PointF, EqualsOfFloatNumbers, RotatedRectangleF, getTriangleAngle, ConvertDegreeToRadian, getSquareDistanceToPoint } from "@aurigma/design-atoms-model/Math";
import { assign } from "@aurigma/design-atoms-model/Utils/Utils";
import { SelectionHandler } from "../Services/Selection/SelectionHandler";
import { BaseRectangleItemHandler } from "../ItemHandlers";
import { ItemUtils } from "../Utils/ItemUtils";
import { GridItemHandler } from "../ItemHandlers/GridItemHandler";
import { MockupContainer } from "@aurigma/design-atoms-model/Product";
import { SafetyLinesHandler } from "./SafetyLinesHandler";
import { SnapElementType } from "./Interfaces";
import { getRotatedRectangleWithBorder } from "../Utils/Math";
export class SnapLinesHandler {
constructor(conf) {
this.setConfiguration(conf);
this._snapData = {
xAnchors: [],
yAnchors: [],
defaultBounds: null,
activeXAnchorIndex: null,
activeYAnchorIndex: null,
//points of startRectangle (in points) through which snapline can pass
xPoints: [],
yPoints: [],
activeXPointIndex: null,
activeYPointIndex: null
};
}
get currentItemRectangle() {
return this._currentItemRectangle;
}
setConfiguration(conf) {
this._conf = assign(this._getDefaultConf, [conf || {}]);
}
get configuration() {
return this._conf;
}
resetActiveLines() {
this._snapData.activeXAnchorIndex = null;
this._snapData.activeYAnchorIndex = null;
}
constrainMoveDataToSnapLines(moveData, canvas, allowMoveHorizontal, allowMoveVertical) {
this._currentItemRectangle = null;
if (!this._enabled)
return moveData.delta;
const resultDiff = moveData.delta.clone();
this._snapData.activeXAnchorIndex = null;
if (allowMoveHorizontal) {
const snapResultX = this._findSnap(canvas, moveData.delta.x, moveData.momentDelta.x, this._snapData.xAnchors, this._snapData.xPoints);
resultDiff.x = snapResultX.diffValue;
this._snapData.activeXAnchorIndex = snapResultX.activeAnchorIndex;
this._snapData.activeXPointIndex = snapResultX.activePointIndex;
}
this._snapData.activeYAnchorIndex = null;
if (allowMoveVertical) {
const snapResultY = this._findSnap(canvas, moveData.delta.y, moveData.momentDelta.y, this._snapData.yAnchors, this._snapData.yPoints);
resultDiff.y = snapResultY.diffValue;
this._snapData.activeYAnchorIndex = snapResultY.activeAnchorIndex;
this._snapData.activeYPointIndex = snapResultY.activePointIndex;
}
if (moveData.startRectangle) {
const curRotRect = moveData.startRectangle.clone();
curRotRect.translate(resultDiff.x, resultDiff.y);
this._currentItemRectangle = getRotatedRectangleWithBorder(curRotRect, moveData.border);
}
return resultDiff;
}
_findSnap(canvas, diff, direction, anchors, points) {
let result = new SnapLinesHandler.SnapResult();
result.diffValue = diff;
var anchorStart = direction > 0 ? 0 : anchors.length - 1;
var anchorEnd = direction > 0 ? anchors.length - 1 : 0;
var anchorStep = direction > 0 ? 1 : -1;
var pointStart = direction > 0 ? points.length - 1 : 0;
var pointEnd = direction > 0 ? 0 : points.length - 1;
var pointStep = direction > 0 ? -1 : 1;
for (var anchorIndex = anchorStart; anchorIndex !== (anchorEnd + anchorStep); anchorIndex += anchorStep) {
var anchor = anchors[anchorIndex];
for (var pointIndex = pointStart; pointIndex !== (pointEnd + pointStep); pointIndex += pointStep) {
var yPoint = points[pointIndex];
const tolerance = this._getSnapLineTolerance(canvas, anchor.type);
if (Math.abs(yPoint + diff - anchor.position) >= tolerance)
continue;
const oldTypeConf = result.activeAnchorIndex != null ? this._conf.snapElements[anchors[result.activeAnchorIndex].type] : null;
const currentTypeConf = this._conf.snapElements[anchor.type];
if (result.activeAnchorIndex == null || currentTypeConf.priority > oldTypeConf.priority) {
result.diffValue = anchor.position - yPoint;
result.activeAnchorIndex = anchorIndex;
result.activePointIndex = pointIndex;
}
}
}
return result;
}
getVerticalLineData() {
if (this._snapData.activeXAnchorIndex == null)
return null;
const xAnchor = this._snapData.xAnchors[this._snapData.activeXAnchorIndex];
const config = this._conf.snapElements[xAnchor.type];
return { anchor: xAnchor, config: config };
}
getHorizontalLineData() {
if (this._snapData.activeYAnchorIndex == null)
return null;
const yAnchor = this._snapData.yAnchors[this._snapData.activeYAnchorIndex];
const config = this._conf.snapElements[yAnchor.type];
return { anchor: yAnchor, config: config };
}
constrainRectangleToSnapLines(rect, oldRectWithBorder, arbitraryResize, resizeIndex, rectWithFramesAndBorder, rectWithBorder, canvas, border) {
this._currentItemRectangle = null;
if (!this._enabled)
return rect;
var r = rect.clone();
this._snapData.xPoints = [];
this._snapData.yPoints = [];
var activePoint; //the point that the user pulls
var activeFramePoint; //the point corresponding to activePoint, taking into account its frame
var activePointIndex = resizeIndex - 1;
if (resizeIndex > 4) {
activeFramePoint = new PointF(rectWithFramesAndBorder.centerX +
SelectionHandler.cw[resizeIndex] * (rectWithFramesAndBorder.width / 2), rectWithFramesAndBorder.centerY +
SelectionHandler.ch[resizeIndex] * (rectWithFramesAndBorder.height / 2));
activeFramePoint.rotateAt(r.angle, rectWithFramesAndBorder.center);
activePoint = new PointF(rectWithBorder.centerX +
SelectionHandler.cw[resizeIndex] * (rectWithBorder.width / 2), rectWithBorder.centerY +
SelectionHandler.ch[resizeIndex] * (rectWithBorder.height / 2));
activePoint.rotateAt(r.angle, rectWithBorder.center);
}
else {
activeFramePoint = rectWithFramesAndBorder.getCornerByIndex(activePointIndex);
activePoint = rectWithBorder.getCornerByIndex(activePointIndex);
}
var self = this;
var fillSnapData = (fillX = null, fillY = null) => {
fillX = fillX != null ? fillX : true;
fillY = fillY != null ? fillY : true;
if (!fillX && !fillY)
return;
const snapDataTolerance = this._getSnapLineTolerance(canvas, SnapElementType.Items);
if (fillX) {
self._snapData.xPoints = [activeFramePoint.x];
if (Math.abs(activeFramePoint.x - activePoint.x) > snapDataTolerance) {
self._snapData.xPoints.push(activePoint.x);
}
}
if (fillY) {
self._snapData.yPoints = [activeFramePoint.y];
if (Math.abs(activeFramePoint.y - activePoint.y) > snapDataTolerance) {
self._snapData.yPoints.push(activePoint.y);
}
}
};
var targetActivePoint; //The resulting point (it belongs to either a frame or the main rectangle)
var isFrameActivePoint;
if (arbitraryResize) {
//When resizing the rectangle by using one of the sides, a snapline appears only if the rotation angle is a multiple of 90.
if (rectWithFramesAndBorder.angle % 90 !== 0 && resizeIndex > 4) {
return rect;
}
var center = r.center;
//When resizing the rectangle by using sides and rotating at 90 or 270 degrees, we will swap the Width and Height for convenience.
var isReverse = resizeIndex > 4 && (r.angle - 90) % 180 === 0;
fillSnapData(SelectionHandler.cw[resizeIndex] !== (0 ^ (isReverse ? 1 : 0)), SelectionHandler.ch[resizeIndex] !== (0 ^ (isReverse ? 1 : 0)));
const moveData = {
delta: new PointF(),
momentDelta: new PointF(),
startPoint: activeFramePoint,
border: border
};
var snapLinesDiff = this.constrainMoveDataToSnapLines(moveData, canvas, true, true);
if (snapLinesDiff == null ||
(EqualsOfFloatNumbers(snapLinesDiff.x, 0, 0.1) &&
EqualsOfFloatNumbers(snapLinesDiff.y, 0, 0.01))) {
return rect;
}
isFrameActivePoint = snapLinesDiff.x !== 0 && this._snapData.activeXPointIndex === 0 ||
snapLinesDiff.y !== 0 && this._snapData.activeYPointIndex === 0;
targetActivePoint = isFrameActivePoint ? activeFramePoint : activePoint;
var snapPoint = new PointF(targetActivePoint.x + snapLinesDiff.x, targetActivePoint.y + snapLinesDiff.y);
var angle = r.angle;
snapPoint.rotateAt(-angle, center);
targetActivePoint.rotateAt(-r.angle, center);
r.angle = 0;
var arbitraryResizeDiff = new PointF((snapPoint.x - targetActivePoint.x) *
(isFrameActivePoint ? (r.width / rectWithFramesAndBorder.width) : 1), (snapPoint.y - targetActivePoint.y) *
(isFrameActivePoint ? (r.height / rectWithFramesAndBorder.height) : 1));
r.centerX += (arbitraryResizeDiff.x / 2);
r.width += SelectionHandler.cw[resizeIndex] * arbitraryResizeDiff.x;
r.centerY += (arbitraryResizeDiff.y / 2);
r.height += SelectionHandler.ch[resizeIndex] * arbitraryResizeDiff.y;
r.rotateAt(angle, center);
}
else if (!arbitraryResize) {
if (oldRectWithBorder.getCornerByIndex(activePointIndex).equals(activePoint, 0.01)) {
return rect;
}
fillSnapData();
const moveData = {
delta: new PointF(),
momentDelta: new PointF(),
startPoint: activePoint,
border: border
};
var proportionalLinesDiff = this.constrainMoveDataToSnapLines(moveData, canvas, true, true);
var ifXDiffIsZero = EqualsOfFloatNumbers(proportionalLinesDiff.x, 0, 0.01);
var ifYDiffIsZero = EqualsOfFloatNumbers(proportionalLinesDiff.y, 0, 0.01);
if (ifXDiffIsZero && ifYDiffIsZero) {
return rect;
}
isFrameActivePoint =
!ifXDiffIsZero && this._snapData.activeXPointIndex === 0 ||
!ifYDiffIsZero && this._snapData.activeYPointIndex === 0;
targetActivePoint = isFrameActivePoint ? activeFramePoint : activePoint;
//at the proportional resize we find a static point opposite to the active point.
var staticPointIndex = (activePointIndex + 2) % 4; //the opposite index
var staticPoint = rectWithBorder.getCornerByIndex(staticPointIndex);
//Projection of the static point on the snap line. It's needed to find the angle of the resize direction.
var projectionPoint;
if (!ifXDiffIsZero) {
projectionPoint = new PointF(targetActivePoint.x + proportionalLinesDiff.x, staticPoint.y);
}
else {
projectionPoint = new PointF(staticPoint.x, targetActivePoint.y + proportionalLinesDiff.y);
}
//Find an angle near the static point for the resize direction.
var triangleAngleInDegrees = getTriangleAngle(projectionPoint, targetActivePoint, staticPoint);
var triangleAngle = ConvertDegreeToRadian(triangleAngleInDegrees);
//Find the difference between the hypotenuse of the triangle and resize the rectangle.
var newHypotenuseWidth = Math.sqrt(getSquareDistanceToPoint(staticPoint, projectionPoint)) /
Math.cos(triangleAngle);
var oldHypotenuseWidth = Math.sqrt(getSquareDistanceToPoint(staticPoint, targetActivePoint));
var multiplier = newHypotenuseWidth / oldHypotenuseWidth;
if (multiplier == null) {
return rect;
}
var oldStaticPoint = r.getCornerByIndex(staticPointIndex);
r.width *= multiplier;
r.height *= multiplier;
var newStaticPoint = r.getCornerByIndex(staticPointIndex);
r.centerX += (oldStaticPoint.x - newStaticPoint.x);
r.centerY += (oldStaticPoint.y - newStaticPoint.y);
}
this._currentItemRectangle = getRotatedRectangleWithBorder(r.clone(), border);
return r;
}
fillSnapData(startRectangle, startRectangleWithFrames, startRectangleWithBorder, region, interactiveZonesBounds, canvas /*TODO: replace to ProductHandler after refactoring ending*/, isDrag) {
var _a;
this.clearSnapData();
const printArea = canvas.viewer.surface.printAreas.get(0);
if (printArea == null)
return;
this._snapData.defaultBounds = canvas.viewer.surface.printAreas.get(0).bounds;
if (!this._enabled || startRectangle == null)
return;
var xAnchors = [], yAnchors = [];
var getRectLimitPoints = (rect) => {
return {
vertical: [rect.top, rect.bottom],
horizontal: [rect.left, rect.right]
};
};
var addRectangleToAnchors = (rectangle, snapElementType, addCenter) => {
const cornerPoints = [rectangle.getUpperLeftCorner(), rectangle.getUpperRightCorner(), rectangle.getBottomLeftCorner(), rectangle.getBottomRightCorner()];
const points = addCenter ? [...cornerPoints, rectangle.center] : cornerPoints;
const bounds = rectangle.bounds;
const center = rectangle.center;
const byx = new Map();
const byy = new Map();
points.forEach(pt => {
if (region != null && canvas.suppressOutOfRegionManipulation && !region.contains(pt, true))
return;
if (EqualsOfFloatNumbers(pt.x, center.x)) {
const anchor = { position: pt.x, limitPoints: [bounds.bottom, bounds.top], type: snapElementType };
byx.set(pt.x, anchor);
}
else if (EqualsOfFloatNumbers(pt.x, bounds.left) || EqualsOfFloatNumbers(pt.x, bounds.right)) {
if (!byx.has(pt.x)) {
const anchor = { position: pt.x, limitPoints: [pt.y], type: snapElementType };
byx.set(pt.x, anchor);
}
else {
const anchor = byx.get(pt.x);
anchor.limitPoints = [...anchor.limitPoints, pt.y];
}
}
if (EqualsOfFloatNumbers(pt.y, center.y)) {
const anchor = { position: pt.y, limitPoints: [bounds.left, bounds.right], type: snapElementType };
byy.set(pt.y, anchor);
}
else if (EqualsOfFloatNumbers(pt.y, bounds.top) || EqualsOfFloatNumbers(pt.y, bounds.bottom)) {
if (!byy.has(pt.y)) {
const anchor = { position: pt.y, limitPoints: [pt.x], type: snapElementType };
byy.set(pt.y, anchor);
}
else {
const anchor = byy.get(pt.y);
anchor.limitPoints = [...anchor.limitPoints, pt.x];
}
}
});
xAnchors.push(...byx.values());
yAnchors.push(...byy.values());
};
var addBoundsToAnchors = (bounds, snapElementType) => {
var rectangle = region != null && canvas.suppressOutOfRegionManipulation ? RectangleF.intersect(bounds, region) : bounds;
if (!rectangle.isEmpty()) {
const limitPoints = getRectLimitPoints(rectangle);
xAnchors.push({ position: rectangle.left, limitPoints: limitPoints.vertical, rect: rectangle, type: snapElementType }, { position: rectangle.left + rectangle.width / 2, limitPoints: limitPoints.vertical, rect: rectangle, type: snapElementType }, { position: rectangle.right, limitPoints: limitPoints.vertical, rect: rectangle, type: snapElementType });
yAnchors.push({ position: rectangle.top, limitPoints: limitPoints.horizontal, type: snapElementType }, { position: rectangle.top + rectangle.height / 2, limitPoints: limitPoints.horizontal, type: snapElementType }, { position: rectangle.bottom, limitPoints: limitPoints.horizontal, type: snapElementType });
}
};
const snapConf = this._conf.snapElements;
if (region != null && snapConf.region.enabled)
addBoundsToAnchors(region, SnapElementType.Region);
if (this._snapData.defaultBounds != null && snapConf.printArea.enabled)
addBoundsToAnchors(this._snapData.defaultBounds, SnapElementType.PrintArea);
if (interactiveZonesBounds != null && snapConf.interactiveZones.enabled) {
interactiveZonesBounds.forEach(x => {
addBoundsToAnchors(x, SnapElementType.InteractiveZones);
});
}
const blackListFilter = (handler) => {
return handler instanceof GridItemHandler || //skip grid
handler.item.parentContainer instanceof MockupContainer || // skip mockup
ItemUtils.isBgContainerItem(handler.item) || ItemUtils.isFgContainerItem(handler.item); //skip bg and fg containers
};
var snapableItemHandlers = canvas.getAllItemHandlers({
onlyVisible: true,
onlyUnlocked: !snapConf.items.includeLocked,
whiteListFilter: SafetyLinesHandler.isSafetyLineItemHandler,
blackListFilter: blackListFilter,
flatGroupItems: true
});
if (snapConf.items.enabled || snapConf.safetyLines.enabled) {
snapableItemHandlers.forEach((itemHandler) => {
if (!itemHandler.layer.visible)
return;
if (SafetyLinesHandler.isSafetyLineItemHandler(itemHandler)) {
if (!snapConf.safetyLines.enabled)
return;
if (itemHandler.item.name === `${SafetyLinesHandler.handlerPrefix}_region_shape`)
return;
var point0 = itemHandler.controlPoints[0];
var point1 = itemHandler.controlPoints[1];
addRectangleToAnchors(RotatedRectangleF.fromRectangleF(RectangleF.fromPoints(point0, point1)), SnapElementType.SafetyLines, false);
}
else if (!canvas.isItemHandlerSelected(itemHandler) && snapConf.items.enabled) {
if (itemHandler instanceof BaseRectangleItemHandler) {
const margin = itemHandler.getBorderMargin();
const border = { left: margin, top: margin, right: margin, bottom: margin };
const rectWithBorder = getRotatedRectangleWithBorder(itemHandler.getSelectionRectangle(), border);
addRectangleToAnchors(rectWithBorder, SnapElementType.Items, true);
}
}
});
}
if (((_a = snapConf.grid) === null || _a === void 0 ? void 0 : _a.enabled) && canvas.viewer.gridHandler.visible) {
const grid = canvas.viewer.gridHandler.getGridParameters();
if (grid != null) {
for (let i = 0; i <= grid.cols; i++) {
xAnchors.push({
position: grid.location.x + i * grid.stepX,
type: SnapElementType.Grid
});
}
for (let i = 0; i <= grid.rows; i++) {
yAnchors.push({
position: grid.location.y + i * grid.stepY,
type: SnapElementType.Grid
});
}
}
}
var sortAndRemoveDuplicatesFromAnchorArray = (array, tolerance = null) => {
var sortedArray = array.sort((a, b) => a.position - b.position);
var result = [];
sortedArray.forEach((value, index) => {
const lastElement = result.slice(-1)[0];
if (index === 0 || !EqualsOfFloatNumbers(lastElement.position, value.position, tolerance != null ? tolerance : 0.01) || lastElement.type !== value.type)
result.push(value);
});
return result;
};
var sortAndRemoveDuplicates = (array, tolerance = null) => {
var sortedArray = array.sort((a, b) => a - b);
var result = [];
sortedArray.forEach((value, index) => {
if (index === 0 ||
!EqualsOfFloatNumbers(result.slice(-1)[0], value, tolerance != null ? tolerance : 0.01))
result.push(value);
});
return result;
};
this._snapData.xAnchors = sortAndRemoveDuplicatesFromAnchorArray(xAnchors);
this._snapData.yAnchors = sortAndRemoveDuplicatesFromAnchorArray(yAnchors);
if (!isDrag)
return;
var startRectangleWithFramesAndBorder = startRectangleWithFrames.bounds;
this._snapData.xPoints = [
startRectangleWithBorder.left, startRectangle.centerX, startRectangleWithBorder.right
];
this._snapData.yPoints = [
startRectangleWithBorder.top, startRectangle.centerY, startRectangleWithBorder.bottom
];
var snapDataTolerance = this._getSnapLineTolerance(canvas, SnapElementType.Items);
this._snapData.xPoints = sortAndRemoveDuplicates([
startRectangleWithFramesAndBorder.left,
startRectangleWithBorder.left,
startRectangleWithFrames.centerX,
startRectangle.centerX,
startRectangleWithBorder.right,
startRectangleWithFramesAndBorder.right
], snapDataTolerance);
this._snapData.yPoints = sortAndRemoveDuplicates([
startRectangleWithFramesAndBorder.top,
startRectangleWithBorder.top,
startRectangleWithFrames.centerY,
startRectangle.centerY,
startRectangleWithBorder.bottom,
startRectangleWithFramesAndBorder.bottom
], snapDataTolerance);
}
clearSnapData() {
this._snapData.xAnchors = [];
this._snapData.yAnchors = [];
this._snapData.activeXAnchorIndex = null;
this._snapData.activeYAnchorIndex = null;
this._snapData.xPoints = [];
this._snapData.yPoints = [];
}
_getSnapLineTolerance(canvas, snapElementType) {
return Math.abs(this._conf.snapElements[snapElementType.toString()].tolerance / canvas.zoom);
}
get _getDefaultConf() {
const defaultElementConf = {
tolerance: 5,
color: "rgb(255,0,255)",
enabled: false,
priority: 0
};
return {
snapElements: {
items: Object.assign(Object.assign({}, defaultElementConf), { includeLocked: false }),
printArea: Object.assign({}, defaultElementConf),
safetyLines: Object.assign({}, defaultElementConf),
region: Object.assign({}, defaultElementConf),
grid: Object.assign({}, defaultElementConf)
}
};
}
get _enabled() {
const snapElements = this._conf.snapElements;
return snapElements.items.enabled ||
snapElements.printArea.enabled ||
snapElements.region.enabled ||
snapElements.safetyLines.enabled ||
snapElements.grid.enabled;
}
}
SnapLinesHandler.SnapResult = class {
constructor() {
this.diffValue = null;
this.activeAnchorIndex = null;
this.activePointIndex = null;
}
};
//# sourceMappingURL=SnapLinesHandler.js.map