From 0ea0095f88a69d53bc32445157b10ed580408513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Wed, 9 Aug 2023 17:18:48 -0300 Subject: [PATCH 1/3] WIP --- .../lib/plugins/ImageEdit/ImageEdit.ts | 13 ++- .../plugins/ImageEdit/imageEditors/Rotator.ts | 33 +++++-- .../test/imageEdit/imageEditTest.ts | 16 ++-- .../test/imageEdit/rotatorTest.ts | 93 ++++++++++++++++--- 4 files changed, 122 insertions(+), 33 deletions(-) diff --git a/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/ImageEdit.ts b/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/ImageEdit.ts index 7714936ed52..991fc44cc46 100644 --- a/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/ImageEdit.ts +++ b/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/ImageEdit.ts @@ -270,7 +270,7 @@ export default class ImageEdit implements EditorPlugin { */ setEditingImage(image: null, selectImage?: boolean): void; - setEditingImage( + async setEditingImage( image: HTMLImageElement | null, operationOrSelect?: ImageEditOperation | CompatibleImageEditOperation | boolean ) { @@ -331,7 +331,7 @@ export default class ImageEdit implements EditorPlugin { this.editInfo = getEditInfoFromImage(image); //Check if the image is a gif and convert it to a png - this.pngSource = tryToConvertGifToPng(this.editInfo); + this.pngSource = await tryToConvertGifToPng(this.editInfo); //Check if the image was resized by the user this.wasResized = checkIfImageWasResized(this.image); @@ -596,7 +596,14 @@ export default class ImageEdit implements EditorPlugin { const viewport = this.editor?.getVisibleViewport(); const isSmall = isASmallImage(targetWidth, targetHeight); if (rotateHandle && rotateCenter && viewport) { - updateRotateHandleState(viewport, rotateCenter, rotateHandle, isSmall); + updateRotateHandleState( + viewport, + angleRad, + wrapper, + rotateCenter, + rotateHandle, + isSmall + ); } updateSideHandlesVisibility(resizeHandles, isSmall); diff --git a/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/imageEditors/Rotator.ts b/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/imageEditors/Rotator.ts index 9616c242bd1..ee3a3be0923 100644 --- a/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/imageEditors/Rotator.ts +++ b/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/imageEditors/Rotator.ts @@ -50,6 +50,8 @@ export const Rotator: DragAndDropHandler = { */ export function updateRotateHandleState( editorRect: Rect, + angleRad: number, + wrapper: HTMLElement, rotateCenter: HTMLElement, rotateHandle: HTMLElement, isSmallImage: boolean @@ -62,20 +64,33 @@ export function updateRotateHandleState( rotateCenter.style.display = ''; rotateHandle.style.display = ''; const rotateHandleRect = rotateHandle.getBoundingClientRect(); + const wrapperRect = wrapper.getBoundingClientRect(); - if (rotateHandleRect) { - const top = rotateHandleRect.top - editorRect.top; - const left = rotateHandleRect.left - editorRect.left; - const right = rotateHandleRect.right - editorRect.right; - const bottom = rotateHandleRect.bottom - editorRect.bottom; + if (rotateHandleRect && wrapperRect) { let adjustedDistance = Number.MAX_SAFE_INTEGER; - if (top <= 0) { + const angle = angleRad * DEG_PER_RAD; + if (angle < 45 && angle > -45 && wrapperRect.top - editorRect.top < ROTATE_GAP) { + const top = rotateHandleRect.top - editorRect.top; adjustedDistance = top; - } else if (left <= 0) { + } else if ( + angle <= -80 && + angle >= -100 && + wrapperRect.left - editorRect.left < ROTATE_GAP + ) { + const left = rotateHandleRect.left - editorRect.left; adjustedDistance = left; - } else if (right >= 0) { + } else if ( + angle >= 80 && + angle <= 100 && + editorRect.right - wrapperRect.right < ROTATE_GAP + ) { + const right = rotateHandleRect.right - editorRect.right; adjustedDistance = right; - } else if (bottom >= 0) { + } else if ( + (angle <= -160 || angle >= 160) && + editorRect.bottom - wrapperRect.bottom < ROTATE_GAP + ) { + const bottom = rotateHandleRect.bottom - editorRect.bottom; adjustedDistance = bottom; } diff --git a/packages/roosterjs-editor-plugins/test/imageEdit/imageEditTest.ts b/packages/roosterjs-editor-plugins/test/imageEdit/imageEditTest.ts index 04582424529..0fb62991af1 100644 --- a/packages/roosterjs-editor-plugins/test/imageEdit/imageEditTest.ts +++ b/packages/roosterjs-editor-plugins/test/imageEdit/imageEditTest.ts @@ -25,7 +25,7 @@ describe('ImageEdit | rotate and flip', () => { }); function runRotateTest(angle: number, editInfo?: ImageEditInfo) { - const IMG_ID = 'IMAGE_ID'; + const IMG_ID = 'IMAGE_ID_ROTATION'; const content = ``; editor.setContent(content); const image = document.getElementById(IMG_ID) as HTMLImageElement; @@ -46,7 +46,7 @@ describe('ImageEdit | rotate and flip', () => { flippedVertical?: boolean, editInfo?: ImageEditInfo ) { - const IMG_ID = 'IMAGE_ID'; + const IMG_ID = 'IMAGE_ID_FLIP'; const content = ``; editor.setContent(content); const image = document.getElementById(IMG_ID) as HTMLImageElement; @@ -212,7 +212,7 @@ describe('ImageEdit | rotate and flip', () => { }); it('start image editing', () => { - const IMG_ID = 'IMAGE_ID'; + const IMG_ID = 'IMAGE_ID_EDITING'; const content = ``; editor.setContent(content); const image = document.getElementById(IMG_ID) as HTMLImageElement; @@ -271,7 +271,7 @@ describe('ImageEdit | plugin events | quitting', () => { }; it('image selection quit editing', () => { - const IMG_ID = 'IMAGE_ID'; + const IMG_ID = 'IMAGE_ID_QUIT'; const SPAN_ID = 'SPAN_ID'; const content = ``; editor.setContent(content); @@ -286,7 +286,7 @@ describe('ImageEdit | plugin events | quitting', () => { }); it('mousedown quit editing', () => { - const IMG_ID = 'IMAGE_ID'; + const IMG_ID = 'IMAGE_ID_MOUSE'; const SPAN_ID = 'SPAN_ID'; const content = ``; editor.setContent(content); @@ -314,7 +314,7 @@ describe('ImageEdit | plugin events | quitting', () => { describe('ImageEdit | wrapper', () => { let editor: IEditor; - const TEST_ID = 'imageEditTest'; + const TEST_ID = 'imageEditTestWrapper'; let plugin: ImageEdit; beforeEach(() => { @@ -331,7 +331,7 @@ describe('ImageEdit | wrapper', () => { }); it('image selection, remove max-width', () => { - const IMG_ID = 'IMAGE_ID'; + const IMG_ID = 'IMAGE_ID_SELECTION'; const content = ``; editor.setContent(content); const image = document.getElementById(IMG_ID) as HTMLImageElement; @@ -345,7 +345,7 @@ describe('ImageEdit | wrapper', () => { }); it('image selection, cloned image should use style width/height attributes', () => { - const IMG_ID = 'IMAGE_ID'; + const IMG_ID = 'IMAGE_ID_SELECTION_2'; const content = ``; editor.setContent(content); const image = document.getElementById(IMG_ID) as HTMLImageElement; diff --git a/packages/roosterjs-editor-plugins/test/imageEdit/rotatorTest.ts b/packages/roosterjs-editor-plugins/test/imageEdit/rotatorTest.ts index 58f790c1004..5159be20093 100644 --- a/packages/roosterjs-editor-plugins/test/imageEdit/rotatorTest.ts +++ b/packages/roosterjs-editor-plugins/test/imageEdit/rotatorTest.ts @@ -92,7 +92,7 @@ describe('Rotate: rotate only', () => { describe('updateRotateHandlePosition', () => { let editor: IEditor; - const TEST_ID = 'imageEditTest'; + const TEST_ID = 'imageEditTest_rotateHandlePosition'; let plugin: ImageEdit; let editorGetVisibleViewport: any; beforeEach(() => { @@ -119,19 +119,25 @@ describe('updateRotateHandlePosition', () => { rotatePosition: DOMRect, rotateCenterTop: string, rotateCenterHeight: string, - rotateHandleTop: string + rotateHandleTop: string, + wrapperPosition: DOMRect, + angle: number ) { - const IMG_ID = 'IMAGE_ID'; - const content = ``; + const IMG_ID = 'IMAGE_ID_ROTATION'; + const WRAPPER_ID = 'WRAPPER_ID_ROTATION'; + const content = ``; editor.setContent(content); const image = document.getElementById(IMG_ID) as HTMLImageElement; plugin.setEditingImage(image, ImageEditOperation.Rotate); const rotate = getRotateHTML(options)[0]; const rotateHTML = createElement(rotate, document); - image.parentElement!.appendChild(rotateHTML!); + const imageParent = image.parentElement; + imageParent!.appendChild(rotateHTML!); + const wrapper = document.getElementById(WRAPPER_ID) as HTMLElement; const rotateCenter = document.getElementsByClassName('r_rotateC')[0] as HTMLElement; const rotateHandle = document.getElementsByClassName('r_rotateH')[0] as HTMLElement; spyOn(rotateHandle, 'getBoundingClientRect').and.returnValues(rotatePosition); + spyOn(wrapper, 'getBoundingClientRect').and.returnValues(wrapperPosition); const viewport: Rect = { top: 1, bottom: 200, @@ -139,8 +145,9 @@ describe('updateRotateHandlePosition', () => { right: 200, }; editorGetVisibleViewport.and.returnValue(viewport); + const angleRad = angle / DEG_PER_RAD; - updateRotateHandleState(viewport, rotateCenter, rotateHandle, false); + updateRotateHandleState(viewport, angleRad, wrapper, rotateCenter, rotateHandle, false); expect(rotateCenter.style.top).toBe(rotateCenterTop); expect(rotateCenter.style.height).toBe(rotateCenterHeight); @@ -162,7 +169,19 @@ describe('updateRotateHandlePosition', () => { }, '-6px', '0px', - '0px' + '0px', + { + top: 2, + bottom: 3, + left: 2, + right: 5, + height: 2, + width: 2, + x: 1, + y: 3, + toJSON: () => {}, + }, + 0 ); }); @@ -181,7 +200,19 @@ describe('updateRotateHandlePosition', () => { }, '-21px', '15px', - '-32px' + '-32px', + { + top: 0, + bottom: 20, + left: 3, + right: 5, + height: 2, + width: 2, + x: 1, + y: 3, + toJSON: () => {}, + }, + 50 ); }); @@ -190,7 +221,7 @@ describe('updateRotateHandlePosition', () => { { top: 2, bottom: 3, - left: 0, + left: 2, right: 5, height: 2, width: 2, @@ -198,9 +229,21 @@ describe('updateRotateHandlePosition', () => { y: 3, toJSON: () => {}, }, - '-6px', + '-7px', + '1px', '0px', - '0px' + { + top: 2, + bottom: 3, + left: 2, + right: 5, + height: 2, + width: 2, + x: 1, + y: 3, + toJSON: () => {}, + }, + -90 ); }); @@ -219,7 +262,19 @@ describe('updateRotateHandlePosition', () => { }, '-6px', '0px', - '0px' + '0px', + { + top: 0, + bottom: 190, + left: 3, + right: 190, + height: 2, + width: 2, + x: 1, + y: 3, + toJSON: () => {}, + }, + 180 ); }); @@ -238,7 +293,19 @@ describe('updateRotateHandlePosition', () => { }, '-6px', '0px', - '0px' + '0px', + { + top: 0, + bottom: 190, + left: 3, + right: 190, + height: 2, + width: 2, + x: 1, + y: 3, + toJSON: () => {}, + }, + 90 ); }); }); From be1ffcb93831e81ab3abc990952bcf03bcb89349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Wed, 9 Aug 2023 17:20:36 -0300 Subject: [PATCH 2/3] fix handles --- .../lib/plugins/ImageEdit/ImageEdit.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/ImageEdit.ts b/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/ImageEdit.ts index 991fc44cc46..b9e8f516e2e 100644 --- a/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/ImageEdit.ts +++ b/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/ImageEdit.ts @@ -270,7 +270,7 @@ export default class ImageEdit implements EditorPlugin { */ setEditingImage(image: null, selectImage?: boolean): void; - async setEditingImage( + setEditingImage( image: HTMLImageElement | null, operationOrSelect?: ImageEditOperation | CompatibleImageEditOperation | boolean ) { @@ -331,7 +331,7 @@ export default class ImageEdit implements EditorPlugin { this.editInfo = getEditInfoFromImage(image); //Check if the image is a gif and convert it to a png - this.pngSource = await tryToConvertGifToPng(this.editInfo); + this.pngSource = tryToConvertGifToPng(this.editInfo); //Check if the image was resized by the user this.wasResized = checkIfImageWasResized(this.image); From ce9f802a122c4cce98645b567b3f3325ab9e9cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Roldi?= Date: Wed, 9 Aug 2023 18:27:35 -0300 Subject: [PATCH 3/3] fixes --- .../lib/plugins/ImageEdit/imageEditors/Rotator.ts | 5 +++-- .../roosterjs-editor-plugins/test/imageEdit/imageEditTest.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/imageEditors/Rotator.ts b/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/imageEditors/Rotator.ts index ee3a3be0923..3625d74afd8 100644 --- a/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/imageEditors/Rotator.ts +++ b/packages/roosterjs-editor-plugins/lib/plugins/ImageEdit/imageEditors/Rotator.ts @@ -69,6 +69,7 @@ export function updateRotateHandleState( if (rotateHandleRect && wrapperRect) { let adjustedDistance = Number.MAX_SAFE_INTEGER; const angle = angleRad * DEG_PER_RAD; + if (angle < 45 && angle > -45 && wrapperRect.top - editorRect.top < ROTATE_GAP) { const top = rotateHandleRect.top - editorRect.top; adjustedDistance = top; @@ -85,13 +86,13 @@ export function updateRotateHandleState( editorRect.right - wrapperRect.right < ROTATE_GAP ) { const right = rotateHandleRect.right - editorRect.right; - adjustedDistance = right; + adjustedDistance = Math.min(editorRect.right - wrapperRect.right, right); } else if ( (angle <= -160 || angle >= 160) && editorRect.bottom - wrapperRect.bottom < ROTATE_GAP ) { const bottom = rotateHandleRect.bottom - editorRect.bottom; - adjustedDistance = bottom; + adjustedDistance = Math.min(editorRect.bottom - wrapperRect.bottom, bottom); } const rotateGap = Math.max(Math.min(ROTATE_GAP, adjustedDistance), 0); diff --git a/packages/roosterjs-editor-plugins/test/imageEdit/imageEditTest.ts b/packages/roosterjs-editor-plugins/test/imageEdit/imageEditTest.ts index 0fb62991af1..72dd5b694c8 100644 --- a/packages/roosterjs-editor-plugins/test/imageEdit/imageEditTest.ts +++ b/packages/roosterjs-editor-plugins/test/imageEdit/imageEditTest.ts @@ -220,7 +220,7 @@ describe('ImageEdit | rotate and flip', () => { editor.select(image); plugin.setEditingImage(image, ImageEditOperation.Resize); expect(editor.getContent()).toBe( - '' + '' ); }); });