mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Mobile: Improve image editor load performance (#9281)
This commit is contained in:
parent
bd1ddb8522
commit
bcbba0973f
@ -48,6 +48,8 @@ interface Props {
|
||||
// See react-native-webview's prop with the same name.
|
||||
mixedContentMode?: 'never' | 'always';
|
||||
|
||||
allowFileAccessFromJs?: boolean;
|
||||
|
||||
// Initial javascript. Must evaluate to true.
|
||||
injectedJavaScript: string;
|
||||
|
||||
@ -143,6 +145,7 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
|
||||
originWhitelist={['file://*', './*', 'http://*', 'https://*']}
|
||||
mixedContentMode={props.mixedContentMode}
|
||||
allowFileAccess={true}
|
||||
allowFileAccessFromFileURLs={props.allowFileAccessFromJs}
|
||||
injectedJavaScript={props.injectedJavaScript}
|
||||
onMessage={props.onMessage}
|
||||
onError={props.onError}
|
||||
|
@ -19,12 +19,9 @@ const logger = Logger.create('ImageEditor');
|
||||
type OnSaveCallback = (svgData: string)=> void;
|
||||
type OnCancelCallback = ()=> void;
|
||||
|
||||
// Returns the empty string to load from a template.
|
||||
type LoadInitialSVGCallback = ()=> Promise<string>;
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
loadInitialSVGData: LoadInitialSVGCallback;
|
||||
resourceFilename: string|null;
|
||||
onSave: OnSaveCallback;
|
||||
onExit: OnCancelCallback;
|
||||
}
|
||||
@ -178,7 +175,13 @@ const ImageEditor = (props: Props) => {
|
||||
const injectedJavaScript = useMemo(() => `
|
||||
window.onerror = (message, source, lineno) => {
|
||||
window.ReactNativeWebView.postMessage(
|
||||
"error: " + message + " in file://" + source + ", line " + lineno
|
||||
"error: " + message + " in file://" + source + ", line " + lineno,
|
||||
);
|
||||
};
|
||||
|
||||
window.onunhandledrejection = (error) => {
|
||||
window.ReactNativeWebView.postMessage(
|
||||
"error: " + error.reason,
|
||||
);
|
||||
};
|
||||
|
||||
@ -265,19 +268,17 @@ const ImageEditor = (props: Props) => {
|
||||
}, [css]);
|
||||
|
||||
const onReadyToLoadData = useCallback(async () => {
|
||||
const initialSVGData = await props.loadInitialSVGData?.() ?? '';
|
||||
|
||||
// It can take some time for initialSVGData to be transferred to the WebView.
|
||||
// Thus, do so after the main content has been loaded.
|
||||
webviewRef.current.injectJS(`(async () => {
|
||||
if (window.editorControl) {
|
||||
const initialSVGData = ${JSON.stringify(initialSVGData)};
|
||||
const initialSVGPath = ${JSON.stringify(props.resourceFilename)};
|
||||
const initialTemplateData = ${JSON.stringify(Setting.value('imageeditor.imageTemplate'))};
|
||||
|
||||
editorControl.loadImageOrTemplate(initialSVGData, initialTemplateData);
|
||||
editorControl.loadImageOrTemplate(initialSVGPath, initialTemplateData);
|
||||
}
|
||||
})();`);
|
||||
}, [webviewRef, props.loadInitialSVGData]);
|
||||
}, [webviewRef, props.resourceFilename]);
|
||||
|
||||
const onMessage = useCallback(async (event: WebViewMessageEvent) => {
|
||||
const data = event.nativeEvent.data;
|
||||
@ -316,6 +317,7 @@ const ImageEditor = (props: Props) => {
|
||||
themeId={props.themeId}
|
||||
html={html}
|
||||
injectedJavaScript={injectedJavaScript}
|
||||
allowFileAccessFromJs={true}
|
||||
onMessage={onMessage}
|
||||
onError={onError}
|
||||
ref={webviewRef}
|
||||
|
@ -57,7 +57,7 @@ describe('createJsDrawEditor', () => {
|
||||
});
|
||||
|
||||
// Load no image and an empty template so that autosave can start
|
||||
await editorControl.loadImageOrTemplate(undefined, '{}');
|
||||
await editorControl.loadImageOrTemplate('', '{}');
|
||||
|
||||
expect(calledAutosaveCount).toBe(0);
|
||||
|
||||
|
@ -120,20 +120,48 @@ export const createJsDrawEditor = (
|
||||
editor.showLoadingWarning(0);
|
||||
editor.setReadOnly(true);
|
||||
|
||||
const fetchInitialSvgData = (resourceUrl: string) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (!resourceUrl) {
|
||||
resolve('');
|
||||
}
|
||||
|
||||
// fetch seems to be unable to request file:// URLs.
|
||||
// https://github.com/react-native-webview/react-native-webview/issues/1560#issuecomment-1783611805
|
||||
const request = new XMLHttpRequest();
|
||||
|
||||
const onError = () => {
|
||||
reject(`Failed to load initial SVG data: ${request.status}, ${request.statusText}, ${request.responseText}`);
|
||||
};
|
||||
|
||||
request.addEventListener('load', _ => {
|
||||
resolve(request.responseText);
|
||||
});
|
||||
request.addEventListener('error', onError);
|
||||
request.addEventListener('abort', onError);
|
||||
|
||||
request.open('GET', resourceUrl);
|
||||
request.send();
|
||||
});
|
||||
};
|
||||
|
||||
const editorControl = {
|
||||
editor,
|
||||
loadImageOrTemplate: async (svgData: string|undefined, templateData: string) => {
|
||||
loadImageOrTemplate: async (resourceUrl: string, templateData: string) => {
|
||||
// loadFromSVG shows its own loading message. Hide the original.
|
||||
editor.hideLoadingWarning();
|
||||
|
||||
if (svgData && svgData.length > 0) {
|
||||
await editor.loadFromSVG(svgData);
|
||||
} else {
|
||||
const svgData = await fetchInitialSvgData(resourceUrl);
|
||||
|
||||
// Load from a template if no initial data
|
||||
if (svgData === '') {
|
||||
await applyTemplateToEditor(editor, templateData);
|
||||
|
||||
// The editor expects to be saved initially (without
|
||||
// unsaved changes). Save now.
|
||||
saveNow();
|
||||
} else {
|
||||
await editor.loadFromSVG(svgData);
|
||||
}
|
||||
|
||||
// We can now edit and save safely (without data loss).
|
||||
|
@ -81,7 +81,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
fromShare: false,
|
||||
showCamera: false,
|
||||
showImageEditor: false,
|
||||
loadImageEditorData: null,
|
||||
imageEditorResource: null,
|
||||
noteResources: {},
|
||||
|
||||
@ -851,9 +850,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
const filePath = Resource.fullPath(item);
|
||||
this.setState({
|
||||
showImageEditor: true,
|
||||
loadImageEditorData: async () => {
|
||||
return await shim.fsDriver().readFile(filePath);
|
||||
},
|
||||
imageEditorResourceFilepath: filePath,
|
||||
imageEditorResource: item,
|
||||
});
|
||||
}
|
||||
@ -1302,7 +1299,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return <CameraView themeId={this.props.themeId} style={{ flex: 1 }} onPhoto={this.cameraView_onPhoto} onCancel={this.cameraView_onCancel} />;
|
||||
} else if (this.state.showImageEditor) {
|
||||
return <ImageEditor
|
||||
loadInitialSVGData={this.state.loadImageEditorData}
|
||||
resourceFilename={this.state.imageEditorResourceFilepath}
|
||||
themeId={this.props.themeId}
|
||||
onSave={this.onSaveDrawing}
|
||||
onExit={this.onCloseDrawing}
|
||||
|
Loading…
Reference in New Issue
Block a user