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.
|
// See react-native-webview's prop with the same name.
|
||||||
mixedContentMode?: 'never' | 'always';
|
mixedContentMode?: 'never' | 'always';
|
||||||
|
|
||||||
|
allowFileAccessFromJs?: boolean;
|
||||||
|
|
||||||
// Initial javascript. Must evaluate to true.
|
// Initial javascript. Must evaluate to true.
|
||||||
injectedJavaScript: string;
|
injectedJavaScript: string;
|
||||||
|
|
||||||
@ -143,6 +145,7 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
|
|||||||
originWhitelist={['file://*', './*', 'http://*', 'https://*']}
|
originWhitelist={['file://*', './*', 'http://*', 'https://*']}
|
||||||
mixedContentMode={props.mixedContentMode}
|
mixedContentMode={props.mixedContentMode}
|
||||||
allowFileAccess={true}
|
allowFileAccess={true}
|
||||||
|
allowFileAccessFromFileURLs={props.allowFileAccessFromJs}
|
||||||
injectedJavaScript={props.injectedJavaScript}
|
injectedJavaScript={props.injectedJavaScript}
|
||||||
onMessage={props.onMessage}
|
onMessage={props.onMessage}
|
||||||
onError={props.onError}
|
onError={props.onError}
|
||||||
|
@ -19,12 +19,9 @@ const logger = Logger.create('ImageEditor');
|
|||||||
type OnSaveCallback = (svgData: string)=> void;
|
type OnSaveCallback = (svgData: string)=> void;
|
||||||
type OnCancelCallback = ()=> void;
|
type OnCancelCallback = ()=> void;
|
||||||
|
|
||||||
// Returns the empty string to load from a template.
|
|
||||||
type LoadInitialSVGCallback = ()=> Promise<string>;
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
themeId: number;
|
themeId: number;
|
||||||
loadInitialSVGData: LoadInitialSVGCallback;
|
resourceFilename: string|null;
|
||||||
onSave: OnSaveCallback;
|
onSave: OnSaveCallback;
|
||||||
onExit: OnCancelCallback;
|
onExit: OnCancelCallback;
|
||||||
}
|
}
|
||||||
@ -178,7 +175,13 @@ const ImageEditor = (props: Props) => {
|
|||||||
const injectedJavaScript = useMemo(() => `
|
const injectedJavaScript = useMemo(() => `
|
||||||
window.onerror = (message, source, lineno) => {
|
window.onerror = (message, source, lineno) => {
|
||||||
window.ReactNativeWebView.postMessage(
|
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]);
|
}, [css]);
|
||||||
|
|
||||||
const onReadyToLoadData = useCallback(async () => {
|
const onReadyToLoadData = useCallback(async () => {
|
||||||
const initialSVGData = await props.loadInitialSVGData?.() ?? '';
|
|
||||||
|
|
||||||
// It can take some time for initialSVGData to be transferred to the WebView.
|
// It can take some time for initialSVGData to be transferred to the WebView.
|
||||||
// Thus, do so after the main content has been loaded.
|
// Thus, do so after the main content has been loaded.
|
||||||
webviewRef.current.injectJS(`(async () => {
|
webviewRef.current.injectJS(`(async () => {
|
||||||
if (window.editorControl) {
|
if (window.editorControl) {
|
||||||
const initialSVGData = ${JSON.stringify(initialSVGData)};
|
const initialSVGPath = ${JSON.stringify(props.resourceFilename)};
|
||||||
const initialTemplateData = ${JSON.stringify(Setting.value('imageeditor.imageTemplate'))};
|
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 onMessage = useCallback(async (event: WebViewMessageEvent) => {
|
||||||
const data = event.nativeEvent.data;
|
const data = event.nativeEvent.data;
|
||||||
@ -316,6 +317,7 @@ const ImageEditor = (props: Props) => {
|
|||||||
themeId={props.themeId}
|
themeId={props.themeId}
|
||||||
html={html}
|
html={html}
|
||||||
injectedJavaScript={injectedJavaScript}
|
injectedJavaScript={injectedJavaScript}
|
||||||
|
allowFileAccessFromJs={true}
|
||||||
onMessage={onMessage}
|
onMessage={onMessage}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
ref={webviewRef}
|
ref={webviewRef}
|
||||||
|
@ -57,7 +57,7 @@ describe('createJsDrawEditor', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Load no image and an empty template so that autosave can start
|
// Load no image and an empty template so that autosave can start
|
||||||
await editorControl.loadImageOrTemplate(undefined, '{}');
|
await editorControl.loadImageOrTemplate('', '{}');
|
||||||
|
|
||||||
expect(calledAutosaveCount).toBe(0);
|
expect(calledAutosaveCount).toBe(0);
|
||||||
|
|
||||||
|
@ -120,20 +120,48 @@ export const createJsDrawEditor = (
|
|||||||
editor.showLoadingWarning(0);
|
editor.showLoadingWarning(0);
|
||||||
editor.setReadOnly(true);
|
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 = {
|
const editorControl = {
|
||||||
editor,
|
editor,
|
||||||
loadImageOrTemplate: async (svgData: string|undefined, templateData: string) => {
|
loadImageOrTemplate: async (resourceUrl: string, templateData: string) => {
|
||||||
// loadFromSVG shows its own loading message. Hide the original.
|
// loadFromSVG shows its own loading message. Hide the original.
|
||||||
editor.hideLoadingWarning();
|
editor.hideLoadingWarning();
|
||||||
|
|
||||||
if (svgData && svgData.length > 0) {
|
const svgData = await fetchInitialSvgData(resourceUrl);
|
||||||
await editor.loadFromSVG(svgData);
|
|
||||||
} else {
|
// Load from a template if no initial data
|
||||||
|
if (svgData === '') {
|
||||||
await applyTemplateToEditor(editor, templateData);
|
await applyTemplateToEditor(editor, templateData);
|
||||||
|
|
||||||
// The editor expects to be saved initially (without
|
// The editor expects to be saved initially (without
|
||||||
// unsaved changes). Save now.
|
// unsaved changes). Save now.
|
||||||
saveNow();
|
saveNow();
|
||||||
|
} else {
|
||||||
|
await editor.loadFromSVG(svgData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can now edit and save safely (without data loss).
|
// We can now edit and save safely (without data loss).
|
||||||
|
@ -81,7 +81,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
fromShare: false,
|
fromShare: false,
|
||||||
showCamera: false,
|
showCamera: false,
|
||||||
showImageEditor: false,
|
showImageEditor: false,
|
||||||
loadImageEditorData: null,
|
|
||||||
imageEditorResource: null,
|
imageEditorResource: null,
|
||||||
noteResources: {},
|
noteResources: {},
|
||||||
|
|
||||||
@ -851,9 +850,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
const filePath = Resource.fullPath(item);
|
const filePath = Resource.fullPath(item);
|
||||||
this.setState({
|
this.setState({
|
||||||
showImageEditor: true,
|
showImageEditor: true,
|
||||||
loadImageEditorData: async () => {
|
imageEditorResourceFilepath: filePath,
|
||||||
return await shim.fsDriver().readFile(filePath);
|
|
||||||
},
|
|
||||||
imageEditorResource: item,
|
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} />;
|
return <CameraView themeId={this.props.themeId} style={{ flex: 1 }} onPhoto={this.cameraView_onPhoto} onCancel={this.cameraView_onCancel} />;
|
||||||
} else if (this.state.showImageEditor) {
|
} else if (this.state.showImageEditor) {
|
||||||
return <ImageEditor
|
return <ImageEditor
|
||||||
loadInitialSVGData={this.state.loadImageEditorData}
|
resourceFilename={this.state.imageEditorResourceFilepath}
|
||||||
themeId={this.props.themeId}
|
themeId={this.props.themeId}
|
||||||
onSave={this.onSaveDrawing}
|
onSave={this.onSaveDrawing}
|
||||||
onExit={this.onCloseDrawing}
|
onExit={this.onCloseDrawing}
|
||||||
|
Loading…
Reference in New Issue
Block a user