@aurigma/design-atoms
Version:
Design Atoms is a part of Customer's Canvas SDK which allows for manipulating individual design elements through your code.
304 lines • 18.1 kB
JavaScript
import { CanvasElementHandler } from "../../CanvasElementHandler";
import Environment from "@aurigma/design-atoms-model/Utils/Environment";
import { RotatedRectangleF, PointF, EqualsOfFloatNumbers } from "@aurigma/design-atoms-model/Math";
import { addClassToElement } from "../../Utils/Dom";
import { ArgumentException } from "@aurigma/design-atoms-model/Exception";
import { alignRectToRect } from "../../Utils/Math";
import { CoordinatesConvertUtils } from "../../Utils/CoordinatesConvertUtils";
import { OriginPointType } from "@aurigma/design-atoms-interfaces";
import { isEmpty } from "@aurigma/design-atoms-model";
export class FloatingToolbarElementHandler {
constructor(_canvasElementHandler, _viewer, _cssClass, _bigButtonCssClass) {
this._canvasElementHandler = _canvasElementHandler;
this._viewer = _viewer;
this._cssClass = _cssClass;
this._bigButtonCssClass = _bigButtonCssClass;
this._buttonElements = new Map();
this._toolbarElementId = "floatingItemToolbarDiv";
this._centerPositionBottomOffset = 10;
this._visible = false;
this._stopPropagationFnc = (e) => { e.stopPropagation(); };
this._createButtonFromDescription = (description) => {
const { handler, cssClass, defaultText } = description;
var toolbarButton = document.createElement("button");
toolbarButton.type = "button";
toolbarButton.addEventListener("mousedown", this._stopPropagationFnc);
toolbarButton.addEventListener("dblclick", this._stopPropagationFnc);
toolbarButton.addEventListener("touchstart", this._stopPropagationFnc);
toolbarButton.value = defaultText;
toolbarButton.addEventListener("click", handler);
addClassToElement(cssClass, toolbarButton);
return toolbarButton;
};
}
init(buttonDescriptions) {
if (this._viewer == null)
throw new ArgumentException(`FloatingToolbarElementHandler.initFloatingItemToolbar: this._viewer cannot be null!`);
if (buttonDescriptions == null || buttonDescriptions.size === 0)
throw new ArgumentException(`FloatingToolbarElementHandler.initFloatingItemToolbar: buttonDescriptions cannot be empty!`);
if (this._descriptions != null)
throw new ArgumentException(`FloatingToolbarElementHandler.initFloatingItemToolbar: toolbar have already initialized!`);
this._descriptions = buttonDescriptions;
this._toolbarElement = this._createRootElement();
const parent = this._toolbarElement.querySelector(`#${this._toolbarElementId}`);
buttonDescriptions.forEach((desc, key) => {
const buttonElement = this._createButtonFromDescription(desc);
this._buttonElements.set(key, buttonElement);
parent.appendChild(buttonElement);
});
this._canvasElementHandler.addChild(this._toolbarElement);
this.update();
}
changeButtonsVisibility(params) {
if (isEmpty(params))
throw new ArgumentException(`FloatingToolbarElementHandler.changeButtonVisibility: params cannot be empty`);
params.forEach(p => this._changeButtonVisibility(p.id, p.visible));
this.update();
}
_changeButtonVisibility(id, visible) {
if (!this._descriptions.has(id))
throw new ArgumentException(`FloatingToolbarElementHandler.changeButtonVisibility: There is no button with id='${id}' in descriptors map.`);
const descriptor = this._descriptions.get(id);
if (descriptor.visible === visible)
return false;
descriptor.visible = visible;
return true;
}
dispose() {
var _a, _b;
this._descriptions = null;
(_a = this._buttonElements) === null || _a === void 0 ? void 0 : _a.forEach(button => {
button.removeEventListener("click", this._stopPropagationFnc);
button.removeEventListener("mousedown", this._stopPropagationFnc);
button.removeEventListener("dblclick", this._stopPropagationFnc);
});
(_b = this._toolbarElement) === null || _b === void 0 ? void 0 : _b.remove();
}
changeVisibility(value) {
this._visible = value;
return this._updateVisibility();
}
get visible() {
return this._visible;
}
get isElementVisible() {
return this._visible && this._isAtLeastOneButtonElementIsVisible;
}
_updateVisibility() {
const atLeastOneButtonElementIsVisible = this._isAtLeastOneButtonElementIsVisible;
const visible = this._visible && atLeastOneButtonElementIsVisible;
this.isElementVisible ?
CanvasElementHandler.showElement(this._toolbarElement, !atLeastOneButtonElementIsVisible) :
CanvasElementHandler.hideElement(this._toolbarElement, !atLeastOneButtonElementIsVisible);
return visible;
}
get _isAtLeastOneButtonElementIsVisible() {
return Array.from(this._descriptions.values()).map(d => d.visible).some(v => v);
}
update() {
this._updateVisibility();
this._descriptions.forEach((description, key) => {
const element = this._buttonElements.get(key);
element.style.display = description.visible ? "block" : "none";
});
}
hitTest(pt) {
var divContentRect = this._viewer.canvas.getButtonGroupRectInGripsDiv(this._toolbarElement);
return divContentRect.contains(pt);
}
_createRootElement() {
var buttonDiv = document.createElement("div");
buttonDiv.style.position = "absolute";
buttonDiv.id = "buttonDiv";
var floatingItemToolbarDiv = document.createElement("div");
floatingItemToolbarDiv.style.display = "flex";
floatingItemToolbarDiv.id = this._toolbarElementId;
addClassToElement(this._cssClass, floatingItemToolbarDiv);
if (Environment.IsTouchDevice())
addClassToElement(this._bigButtonCssClass, floatingItemToolbarDiv);
buttonDiv.appendChild(floatingItemToolbarDiv);
return buttonDiv;
}
updatePosition(rectangle, mode = UpdatePositionMode.Smart) {
return mode === UpdatePositionMode.Smart || this.isToolbarLargerThanRectangle(rectangle) ?
this._smartUpdatePosition(rectangle) :
this._updatePositionOnCenter(rectangle);
}
isToolbarLargerThanRectangle(rectangle) {
var w = this._toolbarElement.offsetWidth;
var h = this._toolbarElement.offsetHeight + this._centerPositionBottomOffset;
const itemRect = CoordinatesConvertUtils.workspaceToControlRectangle(rectangle.bounds, this._viewer);
return w > itemRect.width || h > itemRect.height;
}
_smartUpdatePosition(rectangle) {
var currentAngleRotate = rectangle.angle % 360;
const rotatedRect = CanvasElementHandler.rotateRectangle(rectangle, this._viewer);
var z = this._viewer.zoom;
var hs = this._viewer.screenXDpi / 72 * z;
var vs = this._viewer.screenYDpi / 72 * z;
var rectWidth = Math.floor(rotatedRect.width * hs);
var rectHeight = Math.floor(rotatedRect.height * vs);
var containerWidth = this._toolbarElement.offsetWidth;
var containerHeight = this._toolbarElement.offsetHeight;
var abovePositionMultiplier = 3.5;
var isPositionAboveObject = (containerWidth * abovePositionMultiplier) > rectWidth ||
(containerHeight * abovePositionMultiplier) > rectHeight;
var margin = 8;
var offsetX = (isPositionAboveObject ? -containerWidth / 2 : margin) + containerWidth / 2;
var offsetY = (isPositionAboveObject ? -containerHeight : margin) + containerHeight / 2;
var offsetPoint = new PointF(offsetX, offsetY);
offsetPoint.rotate(rotatedRect.angle);
var containerPosition = CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getUpperRightCorner(), this._viewer);
var futurePositionContainer = new PointF(0, 0);
futurePositionContainer.x = Math.max(0, containerPosition.x);
futurePositionContainer.y = Math.max(0, containerPosition.y);
var rotateAngle = this._viewer.contentAngle;
if (currentAngleRotate >= 0 && currentAngleRotate < 90) {
const corners = [
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getUpperLeftCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getUpperRightCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getBottomRightCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getBottomLeftCorner(), this._viewer)
]; // topLeftCorner, topRightCorner, bottomRightCorner, bottomLeftCorner
futurePositionContainer = this._findBetter(corners, rotateAngle, futurePositionContainer, containerHeight, containerWidth);
}
else if (currentAngleRotate >= 180 && currentAngleRotate < 270) {
const corners = [
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getBottomRightCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getBottomLeftCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getUpperLeftCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getUpperRightCorner(), this._viewer)
]; // topLeftCorner, topRightCorner, bottomRightCorner, bottomLeftCorner
futurePositionContainer = this._findBetter(corners, rotateAngle, futurePositionContainer, containerHeight, containerWidth);
}
else if (currentAngleRotate >= 270) {
const corners = [
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getUpperRightCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getBottomRightCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getBottomLeftCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getUpperLeftCorner(), this._viewer)
]; // topLeftCorner, topRightCorner, bottomRightCorner, bottomLeftCorner
futurePositionContainer = this._findBetter(corners, rotateAngle, futurePositionContainer, containerHeight, containerWidth);
}
else { // 90-180
const corners = [
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getBottomLeftCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getUpperLeftCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getUpperRightCorner(), this._viewer),
CoordinatesConvertUtils.workspaceToControlPoint(rotatedRect.getBottomRightCorner(), this._viewer)
]; // topLeftCorner, topRightCorner, bottomRightCorner, bottomLeftCorner
futurePositionContainer = this._findBetter(corners, rotateAngle, futurePositionContainer, containerHeight, containerWidth);
}
this._toolbarElement.style.left = `${futurePositionContainer.x - 12}px`;
this._toolbarElement.style.top = `${futurePositionContainer.y}px`;
}
_findBetter(corners, rotateAngle, futurePositionContainer, containerHeight, containerWidth) {
/*
Search better position in the following order: top(1), right(2), left(3), bottom(4).
If found then find the best position in it:
- if the area is top or bottom, then the position is horizontal;
- if the area is to the right or left, then the position is vertical.
If don`t found better position, then top right corner(5).
*/
switch (rotateAngle) {
case 90:
corners = corners.concat(corners.splice(0, 3));
break;
case 180:
corners = corners.concat(corners.splice(0, 2));
break;
case 270:
corners = corners.concat(corners.splice(0, 1));
break;
default: // 0 degree`s
break;
}
var topCornerPosition = corners[0];
var topRightCornerPosition = corners[1]; // container position old
var bottomCornerPosition = corners[2];
var leftCornerPosition = corners[3];
var stopLineRight = this._viewer.element.offsetWidth - (topRightCornerPosition.x + (containerWidth / 2));
var stopLineBottom = this._viewer.element.offsetHeight - bottomCornerPosition.y;
let yCoordination = topCornerPosition.y - 12;
const rotateCircle = document.querySelector("div#rotate-circle");
const circleRadius = 15;
let rotateCirclePosition = null;
if (rotateCircle) {
const pageRotateCircleOffset = rotateCircle.getBoundingClientRect();
rotateCirclePosition = CoordinatesConvertUtils.pageToControlPoint(new PointF(pageRotateCircleOffset.left, pageRotateCircleOffset.top), this._viewer);
}
const rotateCircleCovered = () => rotateCirclePosition != null
&&
(rotateCirclePosition.y + circleRadius > futurePositionContainer.y && rotateCirclePosition.y - circleRadius < futurePositionContainer.y + containerHeight)
&&
(rotateCirclePosition.x + 2 * circleRadius > futurePositionContainer.x && rotateCirclePosition.x - 2 * circleRadius < futurePositionContainer.x + containerWidth);
let calculatedHorizontalPosition = () => {
if (stopLineRight < 0)
futurePositionContainer.x = topRightCornerPosition.x - (containerWidth / 2) - 5 + stopLineRight;
else
futurePositionContainer.x = topRightCornerPosition.x - (containerWidth / 2) - 5;
if (rotateCircleCovered())
if (stopLineRight > 0 && (this._viewer.element.offsetWidth - (rotateCirclePosition.x + 2 * circleRadius + containerWidth + 5)) > 0)
futurePositionContainer.x = rotateCirclePosition.x + 2 * circleRadius;
else
futurePositionContainer.x = rotateCirclePosition.x - circleRadius - containerWidth;
return futurePositionContainer.x;
};
let calculatedVerticalPosition = () => {
if (topCornerPosition.y - containerHeight > 0)
futurePositionContainer.y = topCornerPosition.y - containerHeight;
else
futurePositionContainer.y = 0;
if (rotateCircleCovered())
futurePositionContainer.y = rotateCirclePosition.y + circleRadius;
return futurePositionContainer.y;
};
if (yCoordination > containerHeight) { // 1
futurePositionContainer.y = topCornerPosition.y - containerHeight - 12;
futurePositionContainer.x = calculatedHorizontalPosition();
}
else {
if (stopLineRight - (containerWidth / 2 + 5) > 0) { // 2
futurePositionContainer.x = topRightCornerPosition.x + 5;
futurePositionContainer.y = calculatedVerticalPosition();
}
else if (stopLineRight - (containerWidth / 2 + 5) < 0) { // 3
if (leftCornerPosition.x - containerWidth - 5 > 5) {
futurePositionContainer.x = leftCornerPosition.x - containerWidth - 5;
futurePositionContainer.y = calculatedVerticalPosition();
}
else { // 4
futurePositionContainer.x = calculatedHorizontalPosition();
if (stopLineBottom > containerHeight) {
futurePositionContainer.y = bottomCornerPosition.y;
}
else { // 5
futurePositionContainer.y = calculatedVerticalPosition();
}
}
}
}
futurePositionContainer.x += this._viewer.element.scrollLeft;
futurePositionContainer.y += this._viewer.element.scrollTop;
return futurePositionContainer;
}
_updatePositionOnCenter(rectangle) {
if (!(EqualsOfFloatNumbers(rectangle.angle % 90, 0) && !EqualsOfFloatNumbers(rectangle.angle, 360)))
throw new ArgumentException("FloatingToolbarElementHandler._updatePositionOnCenter: mode supports only rectangles without angle");
const controlRectangle = CanvasElementHandler.getControlRectangle(rectangle, this._viewer);
var w = this._toolbarElement.offsetWidth;
var h = this._toolbarElement.offsetHeight;
const angle = rectangle.angle;
const toolbarRectangle = RotatedRectangleF.FromLTRB(0, 0, w, h);
const itemRectangle = RotatedRectangleF.fromRectangleF(RotatedRectangleF.fromRectangleF(controlRectangle, angle).bounds);
const targetRect = alignRectToRect(toolbarRectangle, itemRectangle, OriginPointType.Center, OriginPointType.Bottom).bounds;
this._toolbarElement.style.left = targetRect.left + "px";
this._toolbarElement.style.top = (targetRect.top - this._centerPositionBottomOffset) + "px";
}
}
export var UpdatePositionMode;
(function (UpdatePositionMode) {
UpdatePositionMode[UpdatePositionMode["Smart"] = 0] = "Smart";
UpdatePositionMode[UpdatePositionMode["Center"] = 1] = "Center";
})(UpdatePositionMode || (UpdatePositionMode = {}));
//# sourceMappingURL=FloatingToolbarElementHandler.js.map