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