From 13af1c88e0f23227a9592aefee70d3058c8a7fba Mon Sep 17 00:00:00 2001 From: Kai Vandivier Date: Tue, 13 Feb 2024 16:46:56 +0100 Subject: [PATCH 1/8] feat: use strings for plugin height and width for more unit options --- services/plugin/src/Plugin.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/plugin/src/Plugin.tsx b/services/plugin/src/Plugin.tsx index f8c7d6cb..ebc92fb5 100644 --- a/services/plugin/src/Plugin.tsx +++ b/services/plugin/src/Plugin.tsx @@ -60,8 +60,8 @@ export const Plugin = ({ useState(false) const [inErrorState, setInErrorState] = useState(false) - const [pluginHeight, setPluginHeight] = useState(150) - const [pluginWidth, setPluginWidth] = useState(500) + const [pluginHeight, setPluginHeight] = useState('150px') + const [pluginWidth, setPluginWidth] = useState('500px') useEffect(() => { if (height) { @@ -155,8 +155,8 @@ export const Plugin = ({ return (
) From 382d0dab9d3ff131aaedc7a6cf2009a3ca1b021d Mon Sep 17 00:00:00 2001 From: Kai Vandivier Date: Thu, 15 Feb 2024 09:29:41 +0100 Subject: [PATCH 4/8] fix: allow string or number for plugin sizes --- services/plugin/src/Plugin.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/plugin/src/Plugin.tsx b/services/plugin/src/Plugin.tsx index f57aa76c..a7c77af5 100644 --- a/services/plugin/src/Plugin.tsx +++ b/services/plugin/src/Plugin.tsx @@ -69,8 +69,8 @@ export const Plugin = ({ useState(false) const [inErrorState, setInErrorState] = useState(false) - const [pluginHeight, setPluginHeight] = useState('150px') - const [pluginWidth, setPluginWidth] = useState('500px') + const [pluginHeight, setPluginHeight] = useState('150px') + const [pluginWidth, setPluginWidth] = useState('500px') useEffect(() => { if (height) { From 1ba9b3cf4517cd3481e96c15d84221e3243baeee Mon Sep 17 00:00:00 2001 From: Kai Vandivier Date: Fri, 23 Feb 2024 21:51:35 +0100 Subject: [PATCH 5/8] feat: support communication across multiple windows (navigations, reloads) --- services/plugin/src/Plugin.tsx | 112 +++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/services/plugin/src/Plugin.tsx b/services/plugin/src/Plugin.tsx index a7c77af5..e109b9b2 100644 --- a/services/plugin/src/Plugin.tsx +++ b/services/plugin/src/Plugin.tsx @@ -3,6 +3,7 @@ import { useDataQuery } from '@dhis2/app-service-data' import postRobot from 'post-robot' import React, { ReactEventHandler, + useCallback, useContext, useEffect, useMemo, @@ -63,11 +64,6 @@ export const Plugin = ({ appShortName: pluginShortName, }) - const [communicationReceived, setCommunicationReceived] = - useState(false) - const [prevCommunicationReceived, setPrevCommunicationReceived] = - useState(false) - const [inErrorState, setInErrorState] = useState(false) const [pluginHeight, setPluginHeight] = useState('150px') const [pluginWidth, setPluginWidth] = useState('500px') @@ -93,63 +89,83 @@ export const Plugin = ({ /* eslint-enable react-hooks/exhaustive-deps */ ) - useEffect(() => { - setCommunicationReceived(false) - }, [pluginEntryPoint]) + const propsFromParentListenerRef = useRef() + const communicationReceivedRef = useRef(false) - useEffect(() => { - // if communicationReceived switches from false to true, the props have been sent - const prevCommunication = prevCommunicationReceived - setPrevCommunicationReceived(communicationReceived) - if (prevCommunication === false && communicationReceived === true) { + const setUpCommunication = useCallback(() => { + if (!iframeRef.current) { return } - if (iframeRef?.current) { - const iframeProps = { - ...memoizedPropsToPass, - alertsAdd, - setPluginHeight, - setPluginWidth, - setInErrorState, - setCommunicationReceived, - } + const iframeProps = { + ...memoizedPropsToPass, + alertsAdd, + setPluginHeight, + setPluginWidth, + setInErrorState, + // todo: what does this do? resets state from error component + // seems to work without... + // setCommunicationReceived, + } - // if iframe has not sent initial request, set up a listener - if (!communicationReceived && !inErrorState) { - const listener = postRobot.on( + // if iframe has not sent initial request, set up a listener + if (!communicationReceivedRef.current && !inErrorState) { + // avoid setting up twice + if (!propsFromParentListenerRef.current) { + propsFromParentListenerRef.current = postRobot.on( 'getPropsFromParent', // listen for messages coming only from the iframe rendered by this component { window: iframeRef.current.contentWindow }, (): any => { - setCommunicationReceived(true) + communicationReceivedRef.current = true return iframeProps } ) - return () => listener.cancel() } - - // if iframe has sent initial request, send new props - if ( - communicationReceived && - iframeRef.current.contentWindow && - !inErrorState - ) { - postRobot - .send( - iframeRef.current.contentWindow, - 'updated', - iframeProps - ) - .catch((err) => { - // log postRobot errors, but do not bubble them up - console.error(err) - }) + // return clean-up function + return () => { + propsFromParentListenerRef.current.cancel() + propsFromParentListenerRef.current = null } } - // prevCommunicationReceived update should not retrigger this hook - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [memoizedPropsToPass, communicationReceived, inErrorState, alertsAdd]) + + // if iframe has sent initial request, send new props + // (don't do before to avoid sending messages before listeners + // are ready) + if (iframeRef.current.contentWindow && !inErrorState) { + postRobot + .send(iframeRef.current.contentWindow, 'updated', iframeProps) + .catch((err) => { + // log postRobot errors, but do not bubble them up + console.error(err) + }) + } + }, [memoizedPropsToPass, inErrorState, alertsAdd]) + + useEffect(() => { + // return the clean-up function + return setUpCommunication() + }, [setUpCommunication]) + + const handleLoad = useCallback( + (event) => { + // reset communication received + communicationReceivedRef.current = false + if (propsFromParentListenerRef.current) { + propsFromParentListenerRef.current.cancel() + propsFromParentListenerRef.current = null + } + + // Need to set this up again whenever the iframe contentWindow + // changes (e.g. navigations or reloads) + setUpCommunication() + + if (onLoad) { + onLoad(event) + } + }, + [onLoad, setUpCommunication] + ) if (data && !pluginEntryPoint) { return ( @@ -176,7 +192,7 @@ export const Plugin = ({ height: '100%', border: 'none', }} - onLoad={onLoad} + onLoad={handleLoad} > ) From 64fbe3e5811fb842fc22bcc0b4a351de6fcd40f4 Mon Sep 17 00:00:00 2001 From: Kai Vandivier Date: Wed, 28 Feb 2024 22:23:47 +0100 Subject: [PATCH 6/8] fix: ping endpoint for absolute paths --- .../src/lib/dhis2-connection-status/use-ping-query.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/offline/src/lib/dhis2-connection-status/use-ping-query.ts b/services/offline/src/lib/dhis2-connection-status/use-ping-query.ts index 05274983..1b15cd7e 100644 --- a/services/offline/src/lib/dhis2-connection-status/use-ping-query.ts +++ b/services/offline/src/lib/dhis2-connection-status/use-ping-query.ts @@ -8,7 +8,10 @@ export function usePingQuery(): () => Promise { // This endpoint doesn't need any extra headers or handling since it's // public. It doesn't extend the user session. See DHIS2-14531 - const ping = useCallback(() => fetch(baseUrl + '/api/ping'), [baseUrl]) + const ping = useCallback( + () => fetch(new URL('./api/ping', baseUrl).href), + [baseUrl] + ) return ping } From 33e3ef7130fd9407549eb561e07b214e226f5ea7 Mon Sep 17 00:00:00 2001 From: Kai Vandivier Date: Fri, 31 Jan 2025 17:35:41 +0100 Subject: [PATCH 7/8] fix: revert ping change --- .../src/lib/dhis2-connection-status/use-ping-query.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/services/offline/src/lib/dhis2-connection-status/use-ping-query.ts b/services/offline/src/lib/dhis2-connection-status/use-ping-query.ts index 1b15cd7e..05274983 100644 --- a/services/offline/src/lib/dhis2-connection-status/use-ping-query.ts +++ b/services/offline/src/lib/dhis2-connection-status/use-ping-query.ts @@ -8,10 +8,7 @@ export function usePingQuery(): () => Promise { // This endpoint doesn't need any extra headers or handling since it's // public. It doesn't extend the user session. See DHIS2-14531 - const ping = useCallback( - () => fetch(new URL('./api/ping', baseUrl).href), - [baseUrl] - ) + const ping = useCallback(() => fetch(baseUrl + '/api/ping'), [baseUrl]) return ping } From 6cc5b460c1bd8051a66f9ca0db4f37d68e5c6954 Mon Sep 17 00:00:00 2001 From: Kai Vandivier Date: Fri, 31 Jan 2025 17:45:42 +0100 Subject: [PATCH 8/8] fix: add dependencies to useCallback --- services/plugin/src/Plugin.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/services/plugin/src/Plugin.tsx b/services/plugin/src/Plugin.tsx index 9d69eafd..0c0f711b 100644 --- a/services/plugin/src/Plugin.tsx +++ b/services/plugin/src/Plugin.tsx @@ -181,7 +181,14 @@ export const Plugin = ({ console.error(err) }) } - }, [memoizedPropsToPass, inErrorState, alertsAdd]) + }, [ + memoizedPropsToPass, + inErrorState, + alertsAdd, + height, + width, + clientWidth, + ]) useEffect(() => { // return the clean-up function