UNPKG

@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
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