2024-09-07 07:11:08 -07:00
|
|
|
import * as React from 'react';
|
2023-10-02 07:15:51 -07:00
|
|
|
import { _ } from '@joplin/lib/locale';
|
|
|
|
|
import Logger from '@joplin/utils/Logger';
|
2025-07-29 12:25:43 -07:00
|
|
|
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
2024-08-02 06:51:49 -07:00
|
|
|
import ExtendedWebView from '../../ExtendedWebView';
|
2025-01-06 09:32:19 -08:00
|
|
|
import { OnMessageEvent, WebViewControl } from '../../ExtendedWebView/types';
|
2025-07-29 12:25:43 -07:00
|
|
|
import { clearAutosave, writeAutosave } from './autosave';
|
2024-08-02 06:51:49 -07:00
|
|
|
import { DialogContext } from '../../DialogManager';
|
2025-06-10 16:10:11 -07:00
|
|
|
import BackButtonService from '../../../services/BackButtonService';
|
2025-07-29 12:25:43 -07:00
|
|
|
import useWebViewSetup, { ImageEditorControl } from '../../../contentScripts/imageEditorBundle/useWebViewSetup';
|
2023-10-02 07:15:51 -07:00
|
|
|
|
|
|
|
|
const logger = Logger.create('ImageEditor');
|
|
|
|
|
|
2024-09-07 07:11:08 -07:00
|
|
|
type OnSaveCallback = (svgData: string)=> Promise<void>;
|
2023-10-02 07:15:51 -07:00
|
|
|
type OnCancelCallback = ()=> void;
|
2023-10-31 07:57:26 -07:00
|
|
|
|
2023-10-02 07:15:51 -07:00
|
|
|
interface Props {
|
|
|
|
|
themeId: number;
|
2023-11-12 07:06:16 -08:00
|
|
|
resourceFilename: string|null;
|
2023-10-02 07:15:51 -07:00
|
|
|
onSave: OnSaveCallback;
|
|
|
|
|
onExit: OnCancelCallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const ImageEditor = (props: Props) => {
|
2025-07-29 12:25:43 -07:00
|
|
|
const webViewRef = useRef<WebViewControl|null>(null);
|
2023-10-02 07:15:51 -07:00
|
|
|
const [imageChanged, setImageChanged] = useState(false);
|
|
|
|
|
|
2025-07-29 12:25:43 -07:00
|
|
|
const editorControlRef = useRef<ImageEditorControl|null>(null);
|
2024-08-02 06:51:49 -07:00
|
|
|
const dialogs = useContext(DialogContext);
|
|
|
|
|
|
2025-07-29 12:25:43 -07:00
|
|
|
const onRequestCloseEditor = useCallback((promptIfUnsaved = true) => {
|
2023-10-02 07:15:51 -07:00
|
|
|
const discardChangesAndClose = async () => {
|
|
|
|
|
await clearAutosave();
|
|
|
|
|
props.onExit();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (!imageChanged || !promptIfUnsaved) {
|
|
|
|
|
void discardChangesAndClose();
|
2025-07-29 12:25:43 -07:00
|
|
|
return;
|
2023-10-02 07:15:51 -07:00
|
|
|
}
|
|
|
|
|
|
2024-08-02 06:51:49 -07:00
|
|
|
dialogs.prompt(
|
2023-10-02 07:15:51 -07:00
|
|
|
_('Save changes?'), _('This drawing may have unsaved changes.'), [
|
|
|
|
|
{
|
|
|
|
|
text: _('Discard changes'),
|
|
|
|
|
onPress: discardChangesAndClose,
|
|
|
|
|
style: 'destructive',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
text: _('Save changes'),
|
|
|
|
|
onPress: () => {
|
|
|
|
|
// saveDrawing calls props.onSave(...) which may close the
|
|
|
|
|
// editor.
|
2025-07-29 12:25:43 -07:00
|
|
|
void editorControlRef.current.saveThenExit();
|
2023-10-02 07:15:51 -07:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
);
|
2025-07-29 12:25:43 -07:00
|
|
|
}, [dialogs, props.onExit, imageChanged]);
|
2023-10-02 07:15:51 -07:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const hardwareBackPressListener = () => {
|
|
|
|
|
onRequestCloseEditor(true);
|
|
|
|
|
return true;
|
|
|
|
|
};
|
2025-06-10 16:10:11 -07:00
|
|
|
BackButtonService.addHandler(hardwareBackPressListener);
|
2023-10-02 07:15:51 -07:00
|
|
|
|
|
|
|
|
return () => {
|
2025-06-10 16:10:11 -07:00
|
|
|
BackButtonService.removeHandler(hardwareBackPressListener);
|
2023-10-02 07:15:51 -07:00
|
|
|
};
|
|
|
|
|
}, [onRequestCloseEditor]);
|
|
|
|
|
|
2025-07-29 12:25:43 -07:00
|
|
|
const { pageSetup, api: editorControl, webViewEventHandlers } = useWebViewSetup({
|
|
|
|
|
webViewRef,
|
|
|
|
|
themeId: props.themeId,
|
|
|
|
|
onSetImageChanged: setImageChanged,
|
|
|
|
|
onAutoSave: writeAutosave,
|
|
|
|
|
onSave: props.onSave,
|
|
|
|
|
onRequestCloseEditor,
|
|
|
|
|
resourceFilename: props.resourceFilename,
|
|
|
|
|
});
|
|
|
|
|
editorControlRef.current = editorControl;
|
2023-11-02 02:53:38 -07:00
|
|
|
|
2025-07-29 12:25:43 -07:00
|
|
|
const [html, setHtml] = useState('');
|
2023-11-02 02:53:38 -07:00
|
|
|
useEffect(() => {
|
|
|
|
|
setHtml(`
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8"/>
|
|
|
|
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"/>
|
|
|
|
|
|
2025-07-29 12:25:43 -07:00
|
|
|
<style>
|
|
|
|
|
${pageSetup.css}
|
2023-11-02 02:53:38 -07:00
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body></body>
|
|
|
|
|
</html>
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
// Only set HTML initially (and don't reset). Changing the HTML reloads
|
|
|
|
|
// the page.
|
|
|
|
|
//
|
|
|
|
|
// We need the HTML to initially have the correct CSS to prevent color
|
|
|
|
|
// changes on load.
|
|
|
|
|
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps
|
|
|
|
|
}, []);
|
2023-10-02 07:15:51 -07:00
|
|
|
|
2024-04-05 12:16:49 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
2023-10-02 07:15:51 -07:00
|
|
|
const onError = useCallback((event: any) => {
|
|
|
|
|
logger.error('ImageEditor: WebView error: ', event);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2025-07-29 12:25:43 -07:00
|
|
|
const onWebViewMessage = webViewEventHandlers.onMessage;
|
2025-01-06 09:32:19 -08:00
|
|
|
const onMessage = useCallback((event: OnMessageEvent) => {
|
|
|
|
|
const data = event.nativeEvent.data;
|
|
|
|
|
if (typeof data === 'string' && data.startsWith('error:')) {
|
|
|
|
|
logger.error(data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-29 12:25:43 -07:00
|
|
|
onWebViewMessage(event);
|
|
|
|
|
}, [onWebViewMessage]);
|
2025-01-06 09:32:19 -08:00
|
|
|
|
2023-10-02 07:15:51 -07:00
|
|
|
return (
|
|
|
|
|
<ExtendedWebView
|
|
|
|
|
html={html}
|
2025-07-29 12:25:43 -07:00
|
|
|
injectedJavaScript={pageSetup.js}
|
2023-11-12 07:06:16 -08:00
|
|
|
allowFileAccessFromJs={true}
|
2023-10-02 07:15:51 -07:00
|
|
|
onMessage={onMessage}
|
2025-07-29 12:25:43 -07:00
|
|
|
onLoadEnd={webViewEventHandlers.onLoadEnd}
|
2023-10-02 07:15:51 -07:00
|
|
|
onError={onError}
|
2025-07-29 12:25:43 -07:00
|
|
|
ref={webViewRef}
|
2023-10-02 07:15:51 -07:00
|
|
|
webviewInstanceId={'image-editor-js-draw'}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default ImageEditor;
|