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.

491 lines 24.6 kB
import { RgbColors } from "@aurigma/design-atoms-model/Colors"; import { EqualsOfFloatNumbers, normalizeAngle, Path, PointF, Transform } from "@aurigma/design-atoms-model/Math"; import { HorizontalContentAlignment, ImageItem, ResizeMode, VerticalContentAlignment } from "@aurigma/design-atoms-model/Product/Items"; import Environment from "@aurigma/design-atoms-model/Utils/Environment"; import * as Utils from "@aurigma/design-atoms-model/Utils/Utils"; import { Graphics } from "../Graphics"; import { ContentType } from "./ContentType"; import { ImageContainer } from "./ImageContainer"; import { RectangleItemHandler } from "./RectangleItemHandler"; import { ImageCropperHandler } from "./ImageCropperHandler"; import { PlaceholderFitModeType } from "../Viewer/Interfaces"; import { getQueryParams, queryParamsChanged } from "../Utils/UrlUtils"; // ReSharper disable once InconsistentNaming export class ContentItemHandler extends RectangleItemHandler { constructor(item, textWhizz = null, apiClient, colorPreviewService, options) { var _a, _b; super(item, textWhizz, apiClient, colorPreviewService); this._onCanvasChangedDelegate = null; this._contentType = ContentType.NotContent; this._parentPlaceholder = null; this._pixelWidth = 0; this._pixelHeight = 0; this._needToDownloadImage = false; this._isCropMode = false; this._imageContainer = new ImageContainer(this._onImageLoaded.bind(this)); this._isCropMode = (_a = options === null || options === void 0 ? void 0 : options.isCropMode) !== null && _a !== void 0 ? _a : false; this._fitModeType = (_b = options === null || options === void 0 ? void 0 : options.fitModeType) !== null && _b !== void 0 ? _b : PlaceholderFitModeType.Always; if (this._isCropMode) { this._copperHandler = new ImageCropperHandler(); } } get isCropMode() { return this._isCropMode; } get isAlwaysFitMode() { var _a; return this._fitModeType == PlaceholderFitModeType.Always && ((_a = this.parentPlaceholder) === null || _a === void 0 ? void 0 : _a.item.contentResizeMode) == ResizeMode.Fit; } get isPlaceholderContent() { return this._parentPlaceholder != null; } get inEdit() { return this.isPlaceholderContent && this._parentPlaceholder.editing; } get item() { return this._getItem(); } get parentPlaceholder() { return this._parentPlaceholder; } set parentPlaceholder(value) { this._parentPlaceholder = value; } _getLayer() { if (this.parentPlaceholder != null) { return this.parentPlaceholder.layer; } return super._getLayer(); } get contentType() { return this._contentType; } set contentType(value) { this._contentType = value; } isVisible() { if (this._parentPlaceholder != null) return this._parentPlaceholder.isVisible(); return super.isVisible(); } quickUpdate() { this._updateImageUrl(); } _getScaleMultiplier() { const parentPlaceholder = this._parentPlaceholder; const placeholderRectangle = parentPlaceholder.rectangle; const oldRect = this.rectangle; const oldPlaceholderRatio = parentPlaceholder.previousRectangle.width / parentPlaceholder.previousRectangle.height; const placeholderRatio = placeholderRectangle.width / placeholderRectangle.height; const placeholderContentRatio = oldRect.width / oldRect.height; const ratioChanged = (placeholderRatio > placeholderContentRatio) !== (oldPlaceholderRatio > placeholderContentRatio); if (placeholderRatio > placeholderContentRatio) { if (ratioChanged) { const ratioHeight = (placeholderRectangle.width / placeholderContentRatio); return ratioHeight / parentPlaceholder.previousRectangle.height; } else { return placeholderRectangle.width / parentPlaceholder.previousRectangle.width; } } else { if (ratioChanged) { const ratioWidth = (placeholderRectangle.height * placeholderContentRatio); return ratioWidth / parentPlaceholder.previousRectangle.width; } else { return placeholderRectangle.height / parentPlaceholder.previousRectangle.height; } } } setRectangle(rectangle, suppressOnChanged) { if (this.isPlaceholderContent) { if (this._isCropMode && !this.inEdit) { this._cropContent(rectangle, suppressOnChanged); return; } const contentTypeIsFrame = this._contentType === ContentType.TopFrame || this._contentType === ContentType.BottomFrame; if (this.isPlaceholderContent && this._parentPlaceholder.item.isCoverMode && !this._parentPlaceholder.item.isStubContent && !contentTypeIsFrame) { this._coverContent(rectangle, suppressOnChanged); return; } if (this.isAlwaysFitMode && !this.inEdit) { this._fitContent(rectangle, suppressOnChanged); return; } super.setRectangle(rectangle, suppressOnChanged); } else { super.setRectangle(rectangle, suppressOnChanged); } } _cropContent(rectangle, suppressOnChanged) { const { rectangle: modifiedRect, needUpdate } = this._copperHandler.modifyRectangle(this.parentPlaceholder, this.rectangle, rectangle); super.setRectangle(modifiedRect, suppressOnChanged); if (needUpdate) { this.update(); } } _coverContent(rectangle, suppressOnChanged) { const parentPlaceholder = this._parentPlaceholder; let diff; const placeholderRectangle = parentPlaceholder.rectangle; const targetRectangle = rectangle.clone(); const widthAndHeightReplace = EqualsOfFloatNumbers(normalizeAngle(targetRectangle.angle - placeholderRectangle.angle) % 180, 90); const oldRect = this.rectangle; if (oldRect != null) { if (parentPlaceholder.previousRectangle != null && !parentPlaceholder.editing) { const scaleMultiplier = this._getScaleMultiplier(); targetRectangle.width = oldRect.width * scaleMultiplier; targetRectangle.height = oldRect.height * scaleMultiplier; // When resizing a placeholder, it is necessary to position the content correctly: // The content point that was in the center of the placeholder before transformation should be in the center of the new placeholder. // But if empty areas appear, the content shifts and this condition will not be met. targetRectangle.center = placeholderRectangle.center; const targetRectangleAngle = targetRectangle.angle; targetRectangle.angle = 0; const diffX = (parentPlaceholder.previousRectangle.centerX - oldRect.centerX + (oldRect.width / 2)) * scaleMultiplier - targetRectangle.width / 2; const diffY = (parentPlaceholder.previousRectangle.centerY - oldRect.centerY + (oldRect.height / 2)) * scaleMultiplier - targetRectangle.height / 2; diff = new PointF(-diffX, -diffY); diff.rotateAt(placeholderRectangle.angle - parentPlaceholder.previousRectangle.angle); targetRectangle.centerX = placeholderRectangle.centerX + diff.x; targetRectangle.centerY = placeholderRectangle.centerY + diff.y; targetRectangle.rotateAt(targetRectangleAngle, targetRectangle.center); } } //The positioning algorithm, which prevents the appearance of empty areas inside the placeholder. //It is needed in case of disproportional resizing. Also, TopToolbar allows for resizing the content without grips. const rectWidth = (widthAndHeightReplace ? targetRectangle.height : targetRectangle.width); const rectHeight = (widthAndHeightReplace ? targetRectangle.width : targetRectangle.height); //First, check that the content is the right size to fit correctly into the placeholder. //If not, increase the scale to the required size. if (rectWidth < placeholderRectangle.width || rectHeight < placeholderRectangle.height) { const multiplier = Math.max(placeholderRectangle.width / rectWidth, placeholderRectangle.height / rectHeight); targetRectangle.width *= multiplier; targetRectangle.height *= multiplier; } //Rotate the placeholder to zero angle. const angle = placeholderRectangle.angle; placeholderRectangle.angle = 0; const placeholderBounds = placeholderRectangle.bounds; //Rotate the content to zero angle. At the same time, the content can remain its own angle, which is a multiple of 90 degrees. const contentRectangleWithoutAngle = targetRectangle.clone(); contentRectangleWithoutAngle.rotateAt(-angle, placeholderRectangle.center); const bounds = contentRectangleWithoutAngle.bounds; //Calculate whether the content should be moved (i.e. for transparent areas). //Move the content if needed. diff = new PointF(0, 0); diff.x = placeholderBounds.left < bounds.left ? placeholderBounds.left - bounds.left : 0; diff.x = placeholderBounds.right > bounds.right ? placeholderBounds.right - bounds.right : diff.x; diff.y = placeholderBounds.top < bounds.top ? placeholderBounds.top - bounds.top : 0; diff.y = placeholderBounds.bottom > bounds.bottom ? placeholderBounds.bottom - bounds.bottom : diff.y; diff.rotate(angle); targetRectangle.centerX += diff.x; targetRectangle.centerY += diff.y; super.setRectangle(targetRectangle, suppressOnChanged); } _fitContent(newRectangle, suppressOnChanged) { const container = this._parentPlaceholder.rectangle; const rectangle = this.rectangle.clone().rotateAt(-this.rectangle.angle, container.center); const scaleX = container.width / rectangle.width; const scaleY = container.height / rectangle.height; const scaleFactor = Math.min(scaleX, scaleY); rectangle.width = rectangle.width * scaleFactor; rectangle.height = rectangle.height * scaleFactor; rectangle.bounds.left = (container.width - rectangle.width) / 2 + container.bounds.left; rectangle.bounds.top = (container.height - rectangle.height) / 2 + container.bounds.top; this._updatePosition(rectangle, this._parentPlaceholder); super.setRectangle(rectangle.rotateAt(newRectangle.angle, this.rectangle.center), suppressOnChanged); } updateRectangle(keepLocation, resizeMode, parentPlaceholder, sourceWidth, sourceHeight, hiResOutputDpi) { if (resizeMode == null) resizeMode = ResizeMode.Fit; if (keepLocation == null) keepLocation = false; let rectangle = this.getTransformedRectangle(false); sourceWidth = sourceWidth != null ? sourceWidth : this.bounds.width; sourceHeight = sourceHeight != null ? sourceHeight : this.bounds.height; if (keepLocation) { const ratio = sourceWidth / sourceHeight; const location = rectangle.location; rectangle.width = rectangle.height * ratio; rectangle.location = location; } else { if (this.parentPlaceholder != null) rectangle = this.parentPlaceholder.getTransformedRectangle(false); if (resizeMode === ResizeMode.Original && this.item instanceof ImageItem) { const originalSize = this.item.getOriginalImageSize(); const scale = hiResOutputDpi != null ? this.item.source.dpiX / hiResOutputDpi : 1; rectangle.width = originalSize.width * scale; rectangle.height = originalSize.height * scale; } else { let scale = 1; if (resizeMode === ResizeMode.Fit) { scale = Math.min(rectangle.width / sourceWidth, rectangle.height / sourceHeight); } else if (resizeMode === ResizeMode.Fill) { scale = Math.max(rectangle.width / sourceWidth, rectangle.height / sourceHeight); } rectangle.width = sourceWidth * scale; rectangle.height = sourceHeight * scale; } } // Do not use this.parentPlaceholder because alignment is needed only when inserting an image. this._updatePosition(rectangle, parentPlaceholder); this.setTransformedRectangle(rectangle); } _updatePosition(rectangle, parentPlaceholder) { if (parentPlaceholder == null) return; const phRect = parentPlaceholder.getTransformedRectangle(false); switch (parentPlaceholder.item.contentVerticalAlignment) { case VerticalContentAlignment.Top: rectangle.centerY = phRect.centerY - (phRect.height - rectangle.height) / 2; break; case VerticalContentAlignment.Center: case VerticalContentAlignment.None: rectangle.centerY = phRect.centerY; break; case VerticalContentAlignment.Bottom: rectangle.centerY = phRect.centerY + (phRect.height - rectangle.height) / 2; break; } switch (parentPlaceholder.item.contentHorizontalAlignment) { case HorizontalContentAlignment.Left: rectangle.centerX = phRect.centerX - (phRect.width - rectangle.width) / 2; break; case HorizontalContentAlignment.Center: case HorizontalContentAlignment.None: rectangle.centerX = phRect.centerX; break; case HorizontalContentAlignment.Right: rectangle.centerX = phRect.centerX + (phRect.width - rectangle.width) / 2; break; } } drawItemHandler(itemHandlerCtx, originalCtx) { if (itemHandlerCtx == null || this.canvas == null) { return; } if (!this._isReadyToDraw) { this.canvas.drawWaitClock(itemHandlerCtx, this.rectangle.center); return; } const placeholder = this._parentPlaceholder != null ? this._parentPlaceholder.rectangle : this.rectangle; const content = this.rectangle; const { fillColorPreview, borderColorPreview, altBorderColorPreview } = this._getItemColorPreviews(); if (this._hasVectorMask()) { itemHandlerCtx.save(); this._clip(itemHandlerCtx); } const opacity = this.isNormalRenderingType ? this.item.opacity : 1; Graphics.fillRectangle(itemHandlerCtx, content, fillColorPreview === null || fillColorPreview === void 0 ? void 0 : fillColorPreview.toString(), opacity); if (this._imageContainer.isLoaded) { this._drawImage(itemHandlerCtx, this.canvas.disableSmoothing); } else if (this._imageContainer.isLoading) { this.canvas.drawWaitClock(originalCtx != null ? originalCtx : itemHandlerCtx, new PointF(placeholder.centerX, placeholder.centerY)); } const borderWidth = this._getActualBorderWidth(); content.width += borderWidth; content.height += borderWidth; Graphics.drawStroke(itemHandlerCtx, Path.rotatedRectangle(content), content.center, new Transform(), borderWidth, borderColorPreview === null || borderColorPreview === void 0 ? void 0 : borderColorPreview.toString(), altBorderColorPreview === null || altBorderColorPreview === void 0 ? void 0 : altBorderColorPreview.toString(), this.item.opacity, this.item.dash); if (this._hasVectorMask()) itemHandlerCtx.restore(); } drawMaskedContent(ctx) { this._drawImage(ctx, this.canvas.disableSmoothing, this.item.maskOpacity); if (this._parentPlaceholder == null) return; ctx.globalCompositeOperation = "destination-out"; const placeholder = this._parentPlaceholder; Graphics.fillPath(ctx, placeholder.originalPath, placeholder.getControlCenter(), placeholder.item.transform, "#fff"); ctx.globalCompositeOperation = "source-over"; } transformByMatrix(matrix, finished, newAngle = null) { var _a; if ((_a = this.parentPlaceholder) === null || _a === void 0 ? void 0 : _a.editing) { this.parentPlaceholder.setContentUpdating(!finished); } super.transformByMatrix(matrix, finished, newAngle); } dispose() { this._imageContainer.dispose(); if (this._onCanvasChangedDelegate) { const cv = this.canvas; if (cv) cv.remove_zoomChanged(this._onCanvasChangedDelegate); delete this._onCanvasChangedDelegate; } super.dispose(); } _isReady() { return super._isReady() && (Utils.isNullOrEmptyOrWhiteSpace(this._imageContainer.source) || this._imageContainer.isLoaded); } _getDrawingFillColor() { return this.item.fillColor; } _isLoadingImage() { return this._imageContainer.isLoading; } _setIsLoadingImage(value) { this._imageContainer.isLoading = value; } _onItemPropertyChanged(sender, propertyName) { var _a; switch (propertyName) { case "maskOpacity": break; case "parentPlaceholder": this.parentPlaceholder = sender.parentPlaceholder != null ? (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getItemHandler(sender.parentPlaceholder) : null; break; default: } super._onItemPropertyChanged(sender, propertyName); } _getSrc() { const src = this._imageContainer.source; return !Utils.isNullOrEmptyOrWhiteSpace(src) ? src : this._createImageUrl(); } _setSrc(value) { this._imageContainer.source = value; } _onImageLoaded(e, target) { try { if (this.canvas != null) this.canvas.redraw(); this.isLoadingImage = false; this._dispatchReadyEvent(); } catch (ex) { console.error(`Exception in [${this.getTypeName()}#${this.name}]._onImageLoaded`, ex); throw ex; } } _createImageUrl() { return null; } _updateImageUrl() { var _a, _b; if (!this.isVisible()) return; const cv = this.canvas; if (!(cv === null || cv === void 0 ? void 0 : cv.isInitialized)) return; const url = this._createImageUrl(); if (url === this._imageContainer.source) return; if (!url) { this._imageContainer.clear(); cv.redraw(); return; } if (!this._imageContainer.source) { this._imageContainer.updateUrl(url); return; } const oldQueryParams = getQueryParams(this._imageContainer.source); const newQueryParams = getQueryParams(url); const isDifferent = queryParamsChanged(oldQueryParams, newQueryParams, ["w", "h", "rw", "rh"]); if (isDifferent) { this._imageContainer.updateUrl(url); return; } const { w: oldImageWidth, h: oldImageHeight } = oldQueryParams; const { w: newImageWidth, h: newImageHeight } = newQueryParams; const oldSize = Number(oldImageWidth) * Number(oldImageHeight); const newSize = Number(newImageWidth) * Number(newImageHeight); if (this._shouldRequest(oldSize, newSize, (_b = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.viewerConfiguration) === null || _b === void 0 ? void 0 : _b.imageResizeRequestConfig)) { this._imageContainer.updateUrl(url); } } _shouldRequest(prevSize, nextSize, config) { if (prevSize === 0) return true; if (nextSize <= prevSize) return false; const { width: originalWidth, height: originalHeight } = this.item.source; const originalSize = originalWidth * originalHeight; if (nextSize >= originalSize) return true; const requestRatio = this._getRequestRatio(originalWidth, originalHeight, config); if (requestRatio <= 0) return true; const growthRatio = (nextSize - prevSize) / prevSize; return growthRatio >= requestRatio; } _getRequestRatio(originalWidth, originalHeight, config) { const area = originalWidth * originalHeight; for (const rule of config.rules) { if ((rule.minOriginalArea === undefined || area >= rule.minOriginalArea) && (rule.maxOriginalArea === undefined || area <= rule.maxOriginalArea)) { return rule.requestRatio; } } return config.defaultRequestRatio; } _onCanvasChanged(args) { if (args.fullUpdate) this._updateImageUrl(); } _getItemColorPreviews() { const { borderColorPreview, altBorderColorPreview } = super._getItemColorPreviews(); const fillColorPreview = this._getFillColorPreview(); return { fillColorPreview: fillColorPreview, borderColorPreview: borderColorPreview, altBorderColorPreview: altBorderColorPreview }; } _drawImage(ctx, disableSmoothing, opacity, image) { const canvas = this.canvas; const scaleX = Environment.screenDpi * canvas.zoom / 72; const scaleY = Environment.screenDpi * canvas.zoom / 72; const objectOpacity = this.isNormalRenderingType ? this.item.opacity : 1; const targetOpacity = opacity != null ? objectOpacity * opacity : objectOpacity; Graphics.drawImage(ctx, image != null ? image : this._imageContainer.image, this.rectangle, scaleX, scaleY, disableSmoothing, null, targetOpacity); } _getBoundsMargin() { return this._getActualBorderWidth() * 2; } _onAddedOnCanvas(canvas, supressUpdate) { super._onAddedOnCanvas(canvas, supressUpdate || this.parentPlaceholder != null); this._updateImageUrl(); if (!this._onCanvasChangedDelegate) { const cv = this.canvas; if (cv) { this._onCanvasChangedDelegate = this._onCanvasChanged.bind(this); cv.add_zoomChanged(this._onCanvasChangedDelegate); if (this._needToDownloadImage) this.update(); } } if (this._copperHandler) { this._copperHandler.canvas = canvas; } } _onRemovedFromCanvas(canvas) { super._onRemovedFromCanvas(canvas); if (canvas && this._onCanvasChangedDelegate != null) { canvas.remove_zoomChanged(this._onCanvasChangedDelegate); delete this._onCanvasChangedDelegate; } } _getFillColorPreview() { const drawingFillColor = this._getDrawingFillColor(); const fillColor = (this.isNormalRenderingType || (drawingFillColor === null || drawingFillColor === void 0 ? void 0 : drawingFillColor.isTransparent)) ? drawingFillColor : RgbColors.black; const fillColorPreview = this._colorPreviewService.getPreviews([fillColor])[0]; if (this.item.fillColor != null && fillColorPreview == null) { this._colorPreviewService.subscribeToPreviewLoaded(fillColor, this._onColorPreviewLoaded); } return fillColorPreview; } } ContentItemHandler.typeName = "ContentItemHandler"; //# sourceMappingURL=ContentItemHandler.js.map