Skip to content

Commit

Permalink
Mobile: Upgrade js-draw to 1.26.0 (#11589)
Browse files Browse the repository at this point in the history
  • Loading branch information
personalizedrefrigerator authored Jan 6, 2025
1 parent 1cdb74b commit 6220267
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 146 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/startAutosaveLoop.
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/types.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/watchEditorForTemplateChanges.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/utils/useEditorMessenger.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
packages/app-mobile/components/NoteEditor/SearchPanel.js
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/startAutosaveLoop.
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/types.js
packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/watchEditorForTemplateChanges.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/utils/useEditorMessenger.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
packages/app-mobile/components/NoteEditor/SearchPanel.js
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
// allow-popups-to-escape-sandbox: Allows PDF previews to work on target="_blank" links.
// allow-popups: Allows links to open in a new tab.
permissions: 'allow-scripts allow-modals allow-popups allow-popups-to-escape-sandbox',
allow: 'clipboard-write=(self) fullscreen=(self) autoplay=(self) local-fonts=* encrypted-media=*',
allow: 'clipboard-write; clipboard-read; fullscreen \'self\'; autoplay \'self\'; local-fonts \'self\'; encrypted-media \'self\'',
});

if (containerRef.current) {
Expand Down
133 changes: 32 additions & 101 deletions packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import { Theme } from '@joplin/lib/themes/type';
import { MutableRefObject, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { BackHandler, Platform } from 'react-native';
import ExtendedWebView from '../../ExtendedWebView';
import { WebViewControl } from '../../ExtendedWebView/types';
import { clearAutosave, writeAutosave } from './autosave';
import { OnMessageEvent, WebViewControl } from '../../ExtendedWebView/types';
import { clearAutosave } from './autosave';
import { LocalizedStrings } from './js-draw/types';
import VersionInfo from 'react-native-version-info';
import { DialogContext } from '../../DialogManager';
import { OnMessageEvent } from '../../ExtendedWebView/types';
import useEditorMessenger from './utils/useEditorMessenger';


const logger = Logger.create('ImageEditor');
Expand Down Expand Up @@ -172,7 +171,7 @@ const ImageEditor = (props: Props) => {
const appInfo = useMemo(() => {
return {
name: 'Joplin',
description: `v${VersionInfo.appVersion}`,
description: `v${shim.appVersion()}`,
};
}, []);

Expand All @@ -189,79 +188,23 @@ const ImageEditor = (props: Props) => {
);
};
const setImageHasChanges = (hasChanges) => {
window.ReactNativeWebView.postMessage(
JSON.stringify({
action: 'set-image-has-changes',
data: hasChanges,
}),
);
};
window.updateEditorTemplate = (templateData) => {
window.ReactNativeWebView.postMessage(
JSON.stringify({
action: 'set-image-template-data',
data: templateData,
}),
);
};
const notifyReadyToLoadSVG = () => {
window.ReactNativeWebView.postMessage(
JSON.stringify({
action: 'ready-to-load-data',
})
);
};
const saveDrawing = async (drawing, isAutosave) => {
window.ReactNativeWebView.postMessage(
JSON.stringify({
action: isAutosave ? 'autosave' : 'save',
data: drawing.outerHTML,
}),
);
};
const closeEditor = (promptIfUnsaved) => {
window.ReactNativeWebView.postMessage(JSON.stringify({
action: 'close',
promptIfUnsaved,
}));
};
const saveThenClose = (drawing) => {
window.ReactNativeWebView.postMessage(
JSON.stringify({
action: 'save-and-close',
data: drawing.outerHTML,
}),
);
};
try {
if (window.editorControl === undefined) {
${shim.injectedJs('svgEditorBundle')}
window.editorControl = svgEditorBundle.createJsDrawEditor(
{
saveDrawing,
closeEditor,
saveThenClose,
updateEditorTemplate,
setImageHasChanges,
},
svgEditorBundle.createMessenger().remoteApi,
${JSON.stringify(Setting.value('imageeditor.jsdrawToolbar'))},
${JSON.stringify(Setting.value('locale'))},
${JSON.stringify(localizedStrings)},
${JSON.stringify({ appInfo })},
${JSON.stringify({
appInfo,
...(shim.mobilePlatform() === 'web' ? {
// Use the browser-default clipboard API on web.
clipboardApi: null,
} : {}),
})},
);
// Start loading the SVG file (if present) after loading the editor.
// This shows the user that progress is being made (loading large SVGs
// from disk into memory can take several seconds).
notifyReadyToLoadSVG();
}
} catch(e) {
window.ReactNativeWebView.postMessage(
Expand All @@ -273,7 +216,7 @@ const ImageEditor = (props: Props) => {

useEffect(() => {
webviewRef.current?.injectJS(`
document.querySelector('#main-style').innerText = ${JSON.stringify(css)};
document.querySelector('#main-style').textContent = ${JSON.stringify(css)};
if (window.editorControl) {
window.editorControl.onThemeUpdate();
Expand Down Expand Up @@ -308,48 +251,36 @@ const ImageEditor = (props: Props) => {
})();`);
}, [webviewRef, props.resourceFilename]);

const onMessage = useCallback(async (event: OnMessageEvent) => {
const data = event.nativeEvent.data;
if (data.startsWith('error:')) {
logger.error('ImageEditor:', data);
return;
}

const json = JSON.parse(data);
if (json.action === 'save') {
await clearAutosave();
await props.onSave(json.data);
} else if (json.action === 'autosave') {
await writeAutosave(json.data);
} else if (json.action === 'save-toolbar') {
Setting.setValue('imageeditor.jsdrawToolbar', json.data);
} else if (json.action === 'close') {
onRequestCloseEditor(json.promptIfUnsaved);
} else if (json.action === 'save-and-close') {
await props.onSave(json.data);
onRequestCloseEditor(json.promptIfUnsaved);
} else if (json.action === 'ready-to-load-data') {
void onReadyToLoadData();
} else if (json.action === 'set-image-has-changes') {
setImageChanged(json.data);
} else if (json.action === 'set-image-template-data') {
Setting.setValue('imageeditor.imageTemplate', json.data);
} else {
logger.error('Unknown action,', json.action);
}
}, [props.onSave, onRequestCloseEditor, onReadyToLoadData]);

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onError = useCallback((event: any) => {
logger.error('ImageEditor: WebView error: ', event);
}, []);

const messenger = useEditorMessenger({
webviewRef,
setImageChanged,
onReadyToLoadData,
onSave: props.onSave,
onRequestCloseEditor,
});

const onMessage = useCallback((event: OnMessageEvent) => {
const data = event.nativeEvent.data;
if (typeof data === 'string' && data.startsWith('error:')) {
logger.error(data);
return;
}

messenger.onWebViewMessage(event);
}, [messenger]);

return (
<ExtendedWebView
html={html}
injectedJavaScript={injectedJavaScript}
allowFileAccessFromJs={true}
onMessage={onMessage}
onLoadEnd={messenger.onWebViewLoaded}
onError={onError}
ref={webviewRef}
webviewInstanceId={'image-editor-js-draw'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ const createEditorWithCallbacks = (callbacks: Partial<ImageEditorCallbacks>) =>
const locale = 'en';

const allCallbacks: ImageEditorCallbacks = {
saveDrawing: () => {},
save: () => {},
saveThenClose: ()=> {},
closeEditor: ()=> {},
setImageHasChanges: ()=> {},
updateEditorTemplate: ()=> {},
updateToolbarState: ()=> {},
onLoadedEditor: ()=> {},
writeClipboardText: async ()=>{},
readClipboardText: async ()=> '',

...callbacks,
};
Expand All @@ -51,7 +55,7 @@ describe('createJsDrawEditor', () => {

jest.useFakeTimers();
const editorControl = createEditorWithCallbacks({
saveDrawing: (_drawing: SVGElement, isAutosave: boolean) => {
save: (_drawing: string, isAutosave: boolean) => {
if (isAutosave) {
calledAutosaveCount ++;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@ import { MaterialIconProvider } from '@js-draw/material-icons';
import 'js-draw/bundledStyles';
import applyTemplateToEditor from './applyTemplateToEditor';
import watchEditorForTemplateChanges from './watchEditorForTemplateChanges';
import { ImageEditorCallbacks, LocalizedStrings } from './types';
import { ImageEditorCallbacks, ImageEditorControl, LocalizedStrings } from './types';
import startAutosaveLoop from './startAutosaveLoop';

declare namespace ReactNativeWebView {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const postMessage: (data: any)=> void;
}
import WebViewToRNMessenger from '../../../../utils/ipc/WebViewToRNMessenger';

const restoreToolbarState = (toolbar: AbstractToolbar, state: string) => {
if (state) {
Expand All @@ -23,19 +19,13 @@ const restoreToolbarState = (toolbar: AbstractToolbar, state: string) => {
}
};

const listenToolbarState = (editor: Editor, toolbar: AbstractToolbar) => {
editor.notifier.on(EditorEventType.ToolUpdated, () => {
const state = toolbar.serializeState();
ReactNativeWebView.postMessage(
JSON.stringify({
action: 'save-toolbar',
data: state,
}),
);
});
export const createMessenger = () => {
const messenger = new WebViewToRNMessenger<ImageEditorControl, ImageEditorCallbacks>(
'image-editor', {},
);
return messenger;
};


export const createJsDrawEditor = (
callbacks: ImageEditorCallbacks,
initialToolbarState: string,
Expand All @@ -54,6 +44,38 @@ export const createJsDrawEditor = (
...defaultLocalizations,
},
iconProvider: new MaterialIconProvider(),
clipboardApi: {
read: async () => {
const result = new Map<string, string>();

const clipboardText = await callbacks.readClipboardText();
if (clipboardText) {
result.set('text/plain', clipboardText);
}

return result;
},
write: async (data) => {
const getTextForMime = async (mime: string) => {
const text = data.get(mime);
if (typeof text === 'string') {
return text;
}
if (text) {
return await (await text).text();
}
return null;
};

const svgData = await getTextForMime('image/svg+xml');
if (svgData) {
return callbacks.writeClipboardText(svgData);
}

const textData = await getTextForMime('text/plain');
return callbacks.writeClipboardText(textData);
},
},
...editorSettings,
});

Expand Down Expand Up @@ -95,11 +117,11 @@ export const createJsDrawEditor = (
return editor.toSVG({
// Grow small images to this minimum size
minDimension: 50,
});
}).outerHTML;
};

const saveNow = () => {
callbacks.saveDrawing(getEditorSVG(), false);
callbacks.save(getEditorSVG(), false);

// The image is now up-to-date with the resource
setImageHasChanges(false);
Expand All @@ -109,7 +131,9 @@ export const createJsDrawEditor = (

// Load and save toolbar-related state (e.g. pen sizes/colors).
restoreToolbarState(toolbar, initialToolbarState);
listenToolbarState(editor, toolbar);
editor.notifier.on(EditorEventType.ToolUpdated, () => {
callbacks.updateToolbarState(toolbar.serializeState());
});

setImageHasChanges(false);

Expand Down Expand Up @@ -171,7 +195,7 @@ export const createJsDrawEditor = (
// We can now edit and save safely (without data loss).
editor.setReadOnly(false);

void startAutosaveLoop(editor, callbacks.saveDrawing);
void startAutosaveLoop(editor, callbacks.save);
watchEditorForTemplateChanges(editor, templateData, callbacks.updateEditorTemplate);
},
onThemeUpdate: () => {
Expand All @@ -187,6 +211,8 @@ export const createJsDrawEditor = (

editorControl.onThemeUpdate();

callbacks.onLoadedEditor();

return editorControl;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const startAutosaveLoop = async (

const createAutosave = async () => {
const savedSVG = await editor.toSVGAsync();
saveDrawing(savedSVG, true);
saveDrawing(savedSVG.outerHTML, true);
};

while (true) {
Expand Down
Loading

0 comments on commit 6220267

Please sign in to comment.