mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-15 09:04:04 +02:00
146 lines
4.1 KiB
TypeScript
146 lines
4.1 KiB
TypeScript
import * as React from 'react';
|
|
import { useRef, useEffect, useState } from 'react';
|
|
import useViewIsReady from './hooks/useViewIsReady';
|
|
import useThemeCss from './hooks/useThemeCss';
|
|
const styled = require('styled-components').default;
|
|
|
|
export interface Props {
|
|
html: string;
|
|
scripts: string[];
|
|
onMessage: Function;
|
|
pluginId: string;
|
|
viewId: string;
|
|
themeId: number;
|
|
minWidth?: number;
|
|
minHeight?: number;
|
|
fitToContent?: boolean;
|
|
borderBottom?: boolean;
|
|
theme?: any;
|
|
}
|
|
|
|
interface Size {
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
const StyledFrame = styled.iframe`
|
|
padding: 0;
|
|
margin: 0;
|
|
width: ${(props: any) => props.fitToContent ? `${props.width}px` : '100%'};
|
|
height: ${(props: any) => props.fitToContent ? `${props.height}px` : '100%'};
|
|
border: none;
|
|
border-bottom: ${(props: Props) => props.borderBottom ? `1px solid ${props.theme.dividerColor}` : 'none'};
|
|
`;
|
|
|
|
export default function UserWebview(props: Props) {
|
|
const minWidth = props.minWidth ? props.minWidth : 200;
|
|
const minHeight = props.minHeight ? props.minHeight : 20;
|
|
|
|
const viewRef = useRef(null);
|
|
const isReady = useViewIsReady(viewRef);
|
|
const cssFilePath = useThemeCss({ pluginId: props.pluginId, themeId: props.themeId });
|
|
const [contentSize, setContentSize] = useState<Size>({ width: minWidth, height: minHeight });
|
|
|
|
function frameWindow() {
|
|
if (!viewRef.current) return null;
|
|
return viewRef.current.contentWindow;
|
|
}
|
|
|
|
function postMessage(name: string, args: any = null) {
|
|
const win = frameWindow();
|
|
if (!win) return;
|
|
win.postMessage({ target: 'webview', name, args }, '*');
|
|
}
|
|
|
|
function updateContentSize() {
|
|
const win = frameWindow();
|
|
if (!win) return null;
|
|
|
|
const rect = win.document.getElementById('joplin-plugin-content').getBoundingClientRect();
|
|
|
|
let w = rect.width;
|
|
let h = rect.height;
|
|
if (w < minWidth) w = minWidth;
|
|
if (h < minHeight) h = minHeight;
|
|
|
|
const newSize = { width: w, height: h };
|
|
|
|
setContentSize((current: Size) => {
|
|
if (current.width === newSize.width && current.height === newSize.height) return current;
|
|
return newSize;
|
|
});
|
|
|
|
return newSize;
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!isReady) return () => {};
|
|
let cancelled = false;
|
|
postMessage('setHtml', { html: props.html });
|
|
|
|
setTimeout(() => {
|
|
if (cancelled) return;
|
|
updateContentSize();
|
|
}, 100);
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [props.html, isReady]);
|
|
|
|
useEffect(() => {
|
|
if (!isReady) return;
|
|
postMessage('setScripts', { scripts: props.scripts });
|
|
}, [props.scripts, isReady]);
|
|
|
|
useEffect(() => {
|
|
if (!isReady || !cssFilePath) return;
|
|
postMessage('setScript', { script: cssFilePath, key: 'themeCss' });
|
|
}, [isReady, cssFilePath]);
|
|
|
|
useEffect(() => {
|
|
function onMessage(event: any) {
|
|
if (!event.data || event.data.target !== 'plugin') return;
|
|
props.onMessage({
|
|
pluginId: props.pluginId,
|
|
viewId: props.viewId,
|
|
message: event.data.message,
|
|
});
|
|
}
|
|
|
|
viewRef.current.contentWindow.addEventListener('message', onMessage);
|
|
|
|
return () => {
|
|
viewRef.current.contentWindow.removeEventListener('message', onMessage);
|
|
};
|
|
}, [props.onMessage, props.pluginId, props.viewId]);
|
|
|
|
useEffect(() => {
|
|
if (!props.fitToContent || !isReady) return () => {};
|
|
|
|
// The only reliable way to make sure that the iframe has the same dimensions
|
|
// as its content is to poll the dimensions at regular intervals. Other methods
|
|
// work most of the time but will fail in various edge cases. Most reliable way
|
|
// is probably iframe-resizer package, but still with 40 unfixed bugs.
|
|
//
|
|
// Polling in our case is fine since this is only used when displaying plugin
|
|
// dialogs, which should be short lived. updateContentSize() is also optimised
|
|
// to do nothing when size hasn't changed.
|
|
const updateFrameSizeIID = setInterval(updateContentSize, 2000);
|
|
|
|
return () => {
|
|
clearInterval(updateFrameSizeIID);
|
|
};
|
|
}, [props.fitToContent, isReady, minWidth, minHeight]);
|
|
|
|
return <StyledFrame
|
|
id={props.viewId}
|
|
width={contentSize.width}
|
|
height={contentSize.height}
|
|
fitToContent={props.fitToContent}
|
|
ref={viewRef}
|
|
src="services/plugins/UserWebviewIndex.html"
|
|
borderBottom={props.borderBottom}
|
|
></StyledFrame>;
|
|
}
|