-
Notifications
You must be signed in to change notification settings - Fork 167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
U/juliaroldi/image editing refactor #1345
Changes from 23 commits
4518dd0
08f2eca
e679c15
d00b413
6a8eb94
82781fd
ad4f6d7
28b74d3
e03a7df
7fe3b15
bd0d44c
5de5a1c
d990969
8dc542c
04bdf52
f7ab210
0c5e98e
d17dad7
2fc5a53
59e4814
0021149
974e6eb
4d1ac7e
64708a6
f84a800
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,6 @@ import { | |
getEntityFromElement, | ||
getEntitySelector, | ||
getObjectKeys, | ||
matchesSelector, | ||
safeInstanceOf, | ||
toArray, | ||
wrap, | ||
|
@@ -29,8 +28,8 @@ import { | |
doubleCheckResize, | ||
getSideResizeHTML, | ||
getCornerResizeHTML, | ||
getResizeBordersHTML, | ||
OnShowResizeHandle, | ||
getResizeBordersHTML, | ||
} from './imageEditors/Resizer'; | ||
import { | ||
ExperimentalFeatures, | ||
|
@@ -42,19 +41,14 @@ import { | |
PluginEvent, | ||
PluginEventType, | ||
EntityOperation, | ||
Entity, | ||
Keys, | ||
PositionType, | ||
CreateElementData, | ||
KnownCreateElementDataIndex, | ||
ModeIndependentColor, | ||
SelectionRangeTypes, | ||
Entity, | ||
} from 'roosterjs-editor-types'; | ||
import type { CompatibleImageEditOperation } from 'roosterjs-editor-types/lib/compatibleTypes'; | ||
|
||
const SHIFT_KEYCODE = 16; | ||
const CTRL_KEYCODE = 17; | ||
const ALT_KEYCODE = 18; | ||
|
||
const DIRECTIONS = 8; | ||
const DirectionRad = (Math.PI * 2) / DIRECTIONS; | ||
const DirectionOrder = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']; | ||
|
@@ -184,45 +178,28 @@ export default class ImageEdit implements EditorPlugin { | |
*/ | ||
onPluginEvent(e: PluginEvent) { | ||
switch (e.eventType) { | ||
case PluginEventType.MouseDown: | ||
this.setEditingImage(null); | ||
break; | ||
|
||
case PluginEventType.MouseUp: | ||
const target = e.rawEvent.target; | ||
case PluginEventType.SelectionChanged: | ||
if ( | ||
e.isClicking && | ||
e.rawEvent.button == 0 && | ||
safeInstanceOf(target, 'HTMLImageElement') && | ||
target.isContentEditable && | ||
matchesSelector(target, this.options.imageSelector) | ||
e.selectionRangeEx && | ||
e.selectionRangeEx.type === SelectionRangeTypes.ImageSelection | ||
) { | ||
this.setEditingImage(target, ImageEditOperation.ResizeAndRotate); | ||
this.setEditingImage( | ||
e.selectionRangeEx.image, | ||
ImageEditOperation.ResizeAndRotate | ||
); | ||
} | ||
|
||
break; | ||
|
||
case PluginEventType.MouseDown: | ||
this.setEditingImage(null); | ||
JiuqingSong marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This works. But I think that is what I concerned about current implementation. We can go with this for now, and revisit it later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can change this implementation, but we would have to change the select function to trigger selection change event, when no range is selected. |
||
break; | ||
case PluginEventType.KeyDown: | ||
const key = e.rawEvent.which; | ||
if (key == Keys.DELETE || key == Keys.BACKSPACE) { | ||
// Set current editing image to null and select the image if any, and do not prevent default of the event | ||
// so that browser will delete the selected image for us | ||
this.setEditingImage(null, true /*selectImage*/); | ||
} else if (key == Keys.ESCAPE && this.image) { | ||
// Press ESC should cancel current editing operation, resume back to original edit info | ||
this.editInfo = getEditInfoFromImage(this.image); | ||
this.setEditingImage(null); | ||
e.rawEvent.preventDefault(); | ||
} else if (key != SHIFT_KEYCODE && key != CTRL_KEYCODE && key != ALT_KEYCODE) { | ||
// For other key, just unselect current image and select it. If this is an input key, the image will be replaced | ||
this.setEditingImage(null, true /*selectImage*/); | ||
} | ||
this.setEditingImage(null); | ||
break; | ||
|
||
case PluginEventType.ContentChanged: | ||
if ( | ||
e.source != ChangeSource.InsertEntity || | ||
(<Entity>e.data).type != IMAGE_EDIT_WRAPPER_ENTITY_TYPE | ||
(e.source !== ChangeSource.Format && e.source !== ChangeSource.InsertEntity) || | ||
(e.source === ChangeSource.InsertEntity && | ||
(<Entity>e.data)?.type != IMAGE_EDIT_WRAPPER_ENTITY_TYPE) | ||
) { | ||
// After contentChanged event, the current image wrapper may not be valid any more, remove all of them if any | ||
this.editor.queryElements( | ||
|
@@ -232,7 +209,6 @@ export default class ImageEdit implements EditorPlugin { | |
} | ||
|
||
break; | ||
|
||
case PluginEventType.EntityOperation: | ||
if (e.entity.type == IMAGE_EDIT_WRAPPER_ENTITY_TYPE) { | ||
if (e.operation == EntityOperation.ReplaceTemporaryContent) { | ||
|
@@ -288,7 +264,7 @@ export default class ImageEdit implements EditorPlugin { | |
typeof operationOrSelect === 'number' ? operationOrSelect : ImageEditOperation.None; | ||
const selectImage = typeof operationOrSelect === 'number' ? false : !!operationOrSelect; | ||
|
||
if (this.image) { | ||
if (!image && this.image) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why need this check? What if I select another image? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid a loop. In line 315, the image is being selected again, because we add the wrapper we remove the selection from the image, then we need to select the image again and we do this selection setEditingImage will be called, then to avoid select the image again we do not have the wrapper, unless we use setEditingImage is called with null. |
||
// When there is image in editing, clean up any cached objects and elements | ||
this.clearDndHelpers(); | ||
|
||
|
@@ -306,7 +282,6 @@ export default class ImageEdit implements EditorPlugin { | |
if (selectImage) { | ||
this.editor.select(this.image); | ||
} | ||
|
||
this.image = null; | ||
this.editInfo = null; | ||
this.lastSrc = null; | ||
|
@@ -328,7 +303,7 @@ export default class ImageEdit implements EditorPlugin { | |
this.allowedOperations; | ||
|
||
// Create and update editing wrapper and elements | ||
const wrapper = this.createWrapper(operation); | ||
this.createWrapper(operation); | ||
this.updateWrapper(); | ||
|
||
// Init drag and drop | ||
|
@@ -339,16 +314,15 @@ export default class ImageEdit implements EditorPlugin { | |
...this.createDndHelpers(ImageEditElementClass.CropContainer, Cropper), | ||
]; | ||
|
||
// Put cursor next to the image | ||
this.editor.select(wrapper, PositionType.After); | ||
this.editor.select(this.image); | ||
} | ||
} | ||
|
||
/** | ||
* quit editing mode when editor lose focus | ||
*/ | ||
private onBlur = () => { | ||
this.setEditingImage(null); | ||
this.setEditingImage(null, true); | ||
}; | ||
|
||
/** | ||
|
@@ -367,12 +341,11 @@ export default class ImageEdit implements EditorPlugin { | |
wrapper.style.position = 'relative'; | ||
wrapper.style.maxWidth = '100%'; | ||
// keep the same vertical align | ||
const originalVerticalAlign = this.image.ownerDocument.defaultView | ||
.getComputedStyle(this.image) | ||
.getPropertyValue('vertical-align'); | ||
const originalVerticalAlign = this.getStylePropertyValue(this.image, 'vertical-align'); | ||
if (originalVerticalAlign) { | ||
wrapper.style.verticalAlign = originalVerticalAlign; | ||
} | ||
|
||
wrapper.style.display = Browser.isSafari ? 'inline-block' : 'inline-flex'; | ||
|
||
// Cache current src so that we can compare it after edit see if src is changed | ||
|
@@ -418,6 +391,12 @@ export default class ImageEdit implements EditorPlugin { | |
return wrapper; | ||
} | ||
|
||
private getStylePropertyValue(element: HTMLElement, property: string): string { | ||
return element.ownerDocument.defaultView | ||
.getComputedStyle(this.image) | ||
.getPropertyValue(property); | ||
} | ||
|
||
/** | ||
* Get image wrapper from image | ||
* @param image The image to get wrapper from | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,11 +11,7 @@ import { ImageEditElementClass } from '../types/ImageEditElementClass'; | |
* will be picked up by ImageEdit code | ||
*/ | ||
export interface OnShowResizeHandle { | ||
( | ||
elementData: CreateElementData, | ||
x: DNDDirectionX, | ||
y: DnDDirectionY | ||
): void | ||
(elementData: CreateElementData, x: DNDDirectionX, y: DnDDirectionY): void; | ||
} | ||
|
||
const enum HandleTypes { | ||
|
@@ -55,17 +51,17 @@ export const Resizer: DragAndDropHandler<DragAndDropContext, ResizeInfo> = { | |
// first sure newHeight is right,calculate newWidth | ||
newWidth = newHeight * ratio; | ||
if (newWidth < options.minWidth) { | ||
newWidth = options.minWidth; | ||
newHeight = newWidth / ratio; | ||
newWidth = options.minWidth; | ||
newHeight = newWidth / ratio; | ||
} | ||
} else { | ||
} else { | ||
// first sure newWidth is right,calculate newHeight | ||
newHeight = newWidth / ratio; | ||
if (newHeight < options.minHeight) { | ||
newHeight = options.minHeight; | ||
newWidth = newHeight * ratio; | ||
newHeight = options.minHeight; | ||
newWidth = newHeight * ratio; | ||
} | ||
} | ||
} | ||
} | ||
|
||
editInfo.widthPx = newWidth; | ||
|
@@ -142,18 +138,19 @@ export function getCornerResizeHTML( | |
|
||
Xs.forEach(x => | ||
Ys.forEach(y => { | ||
let elementData = (x == '') == (y == '') | ||
? getResizeHandleHTML( | ||
x, | ||
y, | ||
resizeBorderColor, | ||
handlesExperimentalFeatures | ||
? HandleTypes.CircularHandlesCorner | ||
: HandleTypes.SquareHandles | ||
) | ||
: null; | ||
let elementData = | ||
(x == '') == (y == '') | ||
? getResizeHandleHTML( | ||
x, | ||
y, | ||
resizeBorderColor, | ||
handlesExperimentalFeatures | ||
? HandleTypes.CircularHandlesCorner | ||
: HandleTypes.SquareHandles | ||
) | ||
: null; | ||
if (onShowResizeHandle) { | ||
onShowResizeHandle(elementData, x, y) | ||
onShowResizeHandle(elementData, x, y); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. elementData can be null, should we add a null check? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this had to be node in another PR to enable strict mode. I didn't made any changes in this files, but the beautify extension add the semicolon |
||
} | ||
result.push(elementData); | ||
}) | ||
|
@@ -179,16 +176,17 @@ export function getSideResizeHTML( | |
const result: CreateElementData[] = []; | ||
Xs.forEach(x => | ||
Ys.forEach(y => { | ||
let elementData = (x == '') != (y == '') | ||
? getResizeHandleHTML( | ||
x, | ||
y, | ||
resizeBorderColor, | ||
handlesExperimentalFeatures | ||
? HandleTypes.CircularHandlesCorner | ||
: HandleTypes.SquareHandles | ||
) | ||
: null; | ||
let elementData = | ||
(x == '') != (y == '') | ||
? getResizeHandleHTML( | ||
x, | ||
y, | ||
resizeBorderColor, | ||
handlesExperimentalFeatures | ||
? HandleTypes.CircularHandlesCorner | ||
: HandleTypes.SquareHandles | ||
) | ||
: null; | ||
if (onShowResizeHandle) { | ||
onShowResizeHandle(elementData, x, y); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use editor.queryElements() instead to avoid query image outside editor