diff --git a/packages/ketcher-core/src/application/editor/operations/rasterImage/rasterImageUpsertDelete.ts b/packages/ketcher-core/src/application/editor/operations/rasterImage/rasterImageUpsertDelete.ts index f6bc3f9c0c..2791a43992 100644 --- a/packages/ketcher-core/src/application/editor/operations/rasterImage/rasterImageUpsertDelete.ts +++ b/packages/ketcher-core/src/application/editor/operations/rasterImage/rasterImageUpsertDelete.ts @@ -58,6 +58,7 @@ export class RasterImageDelete extends BaseOperation { return; } + this.rasterImage = reRasterImage.rasterImage.clone(); reStruct.clearVisel(reRasterImage.visel); reRasterImage.remove(); reStruct.markItemRemoved(); diff --git a/packages/ketcher-core/src/domain/serializers/ket/schema.json b/packages/ketcher-core/src/domain/serializers/ket/schema.json index 47b9513661..bf36f4f5a2 100644 --- a/packages/ketcher-core/src/domain/serializers/ket/schema.json +++ b/packages/ketcher-core/src/domain/serializers/ket/schema.json @@ -70,6 +70,11 @@ } } }, + "imageFile": { + "type": "string", + "pattern": "^data:image/(png|jpeg|bmp|webp|x-icon|svg\\+xml);base64,", + "minLength": 160 + }, "header": { "type": "object", "properties": { @@ -808,11 +813,21 @@ "required": ["bitmap", "halfSize"], "properties": { "bitmap": { - "type": "string", - "minLength": 1 + "$ref": "#/definitions/imageFile" }, "halfSize": { - "$ref": "#/definitions/2dCoordinates" + "type": "object", + "required": ["x", "y"], + "properties": { + "x": { + "type": "number", + "exclusiveMinimum": 0 + }, + "y": { + "type": "number", + "exclusiveMinimum": 0 + } + } } } } diff --git a/packages/ketcher-react/src/script/editor/shared/closest.ts b/packages/ketcher-react/src/script/editor/shared/closest.ts index 9d5e76ec9f..894651da52 100644 --- a/packages/ketcher-react/src/script/editor/shared/closest.ts +++ b/packages/ketcher-react/src/script/editor/shared/closest.ts @@ -710,6 +710,9 @@ function findClosestRasterImage( const distanceToPoint = item.rasterImage.calculateDistanceToPoint(cursorPosition); if (distanceToPoint < SELECTION_DISTANCE_COEFFICIENT) { + if (acc && acc.dist < distanceToPoint) { + return acc; + } return { id, dist: distanceToPoint }; } return acc; diff --git a/packages/ketcher-react/src/script/editor/tool/rasterImage.ts b/packages/ketcher-react/src/script/editor/tool/rasterImage.ts index 94bc49a8c3..66566f4f8a 100644 --- a/packages/ketcher-react/src/script/editor/tool/rasterImage.ts +++ b/packages/ketcher-react/src/script/editor/tool/rasterImage.ts @@ -4,9 +4,14 @@ import { Vec2, fromRasterImageCreation, Editor, + KetcherLogger, } from 'ketcher-core'; import { Tool } from './Tool'; +const TAG = 'tool/rasterImage.ts'; +const allowList = /image\/(png|jpeg|bmp|webp|x-icon|svg\+xml)/; +const MIN_DIMENSION_SIZE = 16; + export class RasterImageTool implements Tool { static readonly INPUT_ID = 'image-upload'; private element: HTMLInputElement; @@ -30,16 +35,43 @@ export class RasterImageTool implements Tool { } onFileUpload(clickPosition: Vec2): void { + const errorHandler = this.editor.errorHandler; this.element.onchange = null; if (this.element.files && this.element.files[0]) { + const file = this.element.files[0]; const image = new Image(); const reader = new FileReader(); + if (!file.type.match(allowList)) { + const errorMesssage = `Wrong mime type: ${file.type}`; + KetcherLogger.error(`${TAG}:onFileUpload`, errorMesssage); + if (errorHandler) { + errorHandler(errorMesssage); + } + + this.resetElementValue(); + return; + } + reader.addEventListener('load', () => { image.src = reader.result as string; }); image.onload = () => { + this.resetElementValue(); + if ( + image.width < MIN_DIMENSION_SIZE || + image.height < MIN_DIMENSION_SIZE + ) { + const errorMessage = + 'Image should have be at least 16px wide and 16px tall'; + KetcherLogger.error(`${TAG}:onLoad`, errorMessage); + if (errorHandler) { + errorHandler(errorMessage); + } + return; + } + const halfSize = Scale.canvasToModel( new Vec2(image.width / 2, image.height / 2), this.editor.render.options, @@ -53,7 +85,15 @@ export class RasterImageTool implements Tool { halfSize, ), ); + }; + + image.onerror = (e) => { this.resetElementValue(); + const errorMessage = 'Cannot load image'; + KetcherLogger.error(`${TAG}:onerror`, errorMessage, e); + if (errorHandler) { + errorHandler(errorMessage); + } }; reader.readAsDataURL(this.element.files[0]);