diff --git a/images/icons/v2/pen-medium-20.svg b/images/icons/v2/pen-medium-20.svg index ab289df4c..e75ed1583 100644 --- a/images/icons/v2/pen-medium-20.svg +++ b/images/icons/v2/pen-medium-20.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/images/icons/v2/pen-regular-20.svg b/images/icons/v2/pen-regular-20.svg index e75ed1583..ab289df4c 100644 --- a/images/icons/v2/pen-regular-20.svg +++ b/images/icons/v2/pen-regular-20.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/ts/components/MediaEditor.tsx b/ts/components/MediaEditor.tsx index 0d874003c..8d18f387f 100644 --- a/ts/components/MediaEditor.tsx +++ b/ts/components/MediaEditor.tsx @@ -94,6 +94,12 @@ export const MediaEditor = ({ // Initial image load and Fabric canvas setup useEffect(() => { + // This is important. We can't re-run this function if we've already setup + // a canvas since Fabric doesn't like that. + if (fabricCanvas) { + return; + } + const img = new Image(); img.onload = () => { setImage(img); @@ -117,7 +123,7 @@ export const MediaEditor = ({ img.onload = noop; img.onerror = noop; }; - }, [canvasId, imageSrc, onClose]); + }, [canvasId, fabricCanvas, imageSrc, onClose]); const history = useFabricHistory(fabricCanvas); @@ -142,7 +148,22 @@ export const MediaEditor = ({ ev => isCmdOrCtrl(ev) && ev.key === 't', () => setEditMode(EditMode.Text), ], - [ev => isCmdOrCtrl(ev) && ev.key === 'z', () => history?.undo()], + [ + ev => isCmdOrCtrl(ev) && ev.key === 'z', + () => { + if (history?.canUndo()) { + history?.undo(); + } + }, + ], + [ + ev => isCmdOrCtrl(ev) && ev.shiftKey && ev.key === 'z', + () => { + if (history?.canRedo()) { + history?.redo(); + } + }, + ], [ ev => ev.key === 'Escape', () => { @@ -519,6 +540,7 @@ export const MediaEditor = ({ return; } + obj.exitEditing(); obj.set(getTextStyleAttributes(textStyle, sliderValue)); fabricCanvas.requestRenderAll(); }, [editMode, fabricCanvas, sliderValue, textStyle]); @@ -610,6 +632,8 @@ export const MediaEditor = ({ textStyle, ]); + const [isSaving, setIsSaving] = useState(false); + // In an ideal world we'd use to get the nice animation benefits // but because of the way IText is implemented -- with a hidden textarea -- to // capture keyboard events, we can't use ModalHost since that traps focus, and @@ -963,12 +987,12 @@ export const MediaEditor = ({ 'MediaEditor__control--selected': editMode === EditMode.Text, })} onClick={() => { - if (!fabricCanvas) { - return; - } - if (editMode === EditMode.Text) { setEditMode(undefined); + const obj = fabricCanvas?.getActiveObject(); + if (obj instanceof MediaEditorFabricIText) { + obj.exitEditing(); + } } else { setEditMode(EditMode.Text); } @@ -1069,12 +1093,37 @@ export const MediaEditor = ({ />