diff --git a/src/components/DashboardPluginWrapper/DashboardPluginWrapper.js b/src/components/DashboardPluginWrapper/DashboardPluginWrapper.js new file mode 100644 index 000000000..e21986a5e --- /dev/null +++ b/src/components/DashboardPluginWrapper/DashboardPluginWrapper.js @@ -0,0 +1,96 @@ +import { + useCacheableSection, + CacheableSection, + useConfig, +} from '@dhis2/app-runtime' +import { CenteredContent, CircularLoader, CssVariables, Layer } from '@dhis2/ui' +import PropTypes from 'prop-types' +import React, { useEffect } from 'react' +import { getPWAInstallationStatus } from '../../modules/getPWAInstallationStatus.js' + +const LoadingMask = () => { + return ( + + + + + + ) +} + +const CacheableSectionWrapper = ({ id, children, isParentCached }) => { + const { startRecording, isCached, remove } = useCacheableSection(id) + + useEffect(() => { + if (isParentCached && !isCached) { + startRecording({ onError: console.error }) + } else if (!isParentCached && isCached) { + // Synchronize cache state on load or prop update + // -- a back-up to imperative `removeCachedData` + remove() + } + }, [isCached, isParentCached, remove, startRecording]) + + return ( + }> + {children} + + ) +} + +CacheableSectionWrapper.propTypes = { + children: PropTypes.node, + id: PropTypes.string, + isParentCached: PropTypes.bool, +} + +export const DashboardPluginWrapper = ({ + onInstallationStatusChange, + children, + cacheId, + isParentCached, + ...props +}) => { + const { pwaEnabled } = useConfig() + + useEffect(() => { + // Get & send PWA installation status now + getPWAInstallationStatus({ + onStateChange: onInstallationStatusChange, + }).then(onInstallationStatusChange) + }, [onInstallationStatusChange]) + + return props ? ( +
+ {pwaEnabled ? ( + + {children(props)} + + ) : ( + children(props) + )} + +
+ ) : null +} + +DashboardPluginWrapper.defaultProps = { + isParentCached: false, + onInstallationStatusChange: Function.prototype, +} + +DashboardPluginWrapper.propTypes = { + cacheId: PropTypes.string, + children: PropTypes.func, + isParentCached: PropTypes.bool, + onInstallationStatusChange: PropTypes.func, +} diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js index fbcdf146c..1999647a6 100644 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuDropdown.js @@ -1,5 +1,4 @@ -import { Popper } from '@dhis2-ui/popper' -import { Portal } from '@dhis2-ui/portal' +import { Popper, Portal } from '@dhis2/ui' import cx from 'classnames' import PropTypes from 'prop-types' import React, { useRef } from 'react' diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuList.js b/src/components/Toolbar/HoverMenuBar/HoverMenuList.js index 6709977af..11f50796f 100644 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuList.js +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuList.js @@ -1,4 +1,4 @@ -import { colors, elevations, spacers } from '@dhis2/ui-constants' +import { colors, elevations, spacers } from '@dhis2/ui' import PropTypes from 'prop-types' import React, { createContext, useCallback, useContext, useState } from 'react' import { useHoverMenubarContext } from './HoverMenuBar.js' diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js index b336d7e87..2ad12d02b 100644 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.js @@ -1,6 +1,4 @@ -import { IconChevronRight24 } from '@dhis2/ui-icons' -import { Popper } from '@dhis2-ui/popper' -import { Portal } from '@dhis2-ui/portal' +import { IconChevronRight24, Popper, Portal } from '@dhis2/ui' import cx from 'classnames' import PropTypes from 'prop-types' import React, { useRef } from 'react' diff --git a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js index 3e3d42197..31e0f12eb 100644 --- a/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js +++ b/src/components/Toolbar/HoverMenuBar/HoverMenuListItem.styles.js @@ -1,4 +1,4 @@ -import { colors, spacers } from '@dhis2/ui-constants' +import { colors, spacers } from '@dhis2/ui' import css from 'styled-jsx/css' export default css` diff --git a/src/components/Toolbar/MenuButton.styles.js b/src/components/Toolbar/MenuButton.styles.js index 683567cb8..63982d282 100644 --- a/src/components/Toolbar/MenuButton.styles.js +++ b/src/components/Toolbar/MenuButton.styles.js @@ -1,4 +1,4 @@ -import { colors, spacers, theme } from '@dhis2/ui-constants' +import { colors, spacers, theme } from '@dhis2/ui' import css from 'styled-jsx/css' export default css` diff --git a/src/components/Toolbar/Toolbar.js b/src/components/Toolbar/Toolbar.js index f6dcce5e7..8f4cd10a6 100644 --- a/src/components/Toolbar/Toolbar.js +++ b/src/components/Toolbar/Toolbar.js @@ -1,4 +1,4 @@ -import { colors } from '@dhis2/ui-constants' +import { colors } from '@dhis2/ui' import PropTypes from 'prop-types' import React from 'react' diff --git a/src/components/Toolbar/ToolbarSidebar.js b/src/components/Toolbar/ToolbarSidebar.js index 948a9fbed..2a0b4935c 100644 --- a/src/components/Toolbar/ToolbarSidebar.js +++ b/src/components/Toolbar/ToolbarSidebar.js @@ -1,4 +1,4 @@ -import { colors } from '@dhis2/ui-constants' +import { colors } from '@dhis2/ui' import cx from 'classnames' import PropTypes from 'prop-types' import React from 'react' diff --git a/src/components/Toolbar/UpdateButton.js b/src/components/Toolbar/UpdateButton.js index 5ac678afa..ee3b2c6ef 100644 --- a/src/components/Toolbar/UpdateButton.js +++ b/src/components/Toolbar/UpdateButton.js @@ -1,7 +1,5 @@ import i18n from '@dhis2/d2-i18n' -import { colors } from '@dhis2/ui-constants' -import { IconSync16 } from '@dhis2/ui-icons' -import { CircularLoader } from '@dhis2-ui/loader' +import { CircularLoader, IconSync16, colors } from '@dhis2/ui' import PropTypes from 'prop-types' import React from 'react' import menuButtonStyles from './MenuButton.styles.js' diff --git a/src/index.js b/src/index.js index 7f4c2f2eb..0d3270c5f 100644 --- a/src/index.js +++ b/src/index.js @@ -49,6 +49,8 @@ export { export * from './components/RichText/index.js' +export { DashboardPluginWrapper } from './components/DashboardPluginWrapper/DashboardPluginWrapper.js' + // Api export { default as Analytics } from './api/analytics/Analytics.js' diff --git a/src/modules/getPWAInstallationStatus.js b/src/modules/getPWAInstallationStatus.js new file mode 100644 index 000000000..028cd10f2 --- /dev/null +++ b/src/modules/getPWAInstallationStatus.js @@ -0,0 +1,63 @@ +export const INSTALLATION_STATES = { + READY: 'READY', + INSTALLING: 'INSTALLING', +} + +function handleInstallingWorker({ installingWorker, onStateChange }) { + installingWorker.onstatechange = () => { + if (installingWorker.state === 'activated') { + // ... and update state to 'ready' + onStateChange(INSTALLATION_STATES.READY) + } + } +} + +/** + * Gets the current installation state of the PWA features, which is intended + * to be reported from this plugin to the parent app to indicate that the + * static assets are cached and ready to be accessed locally instead of over + * the network. + * + * Returns either READY, INSTALLING, or `null` for not installed/won't install + */ +export async function getPWAInstallationStatus({ onStateChange }) { + if (!navigator.serviceWorker) { + // Nothing to do here + return null + } + + const registration = await navigator.serviceWorker.getRegistration() + if (!registration) { + // This shouldn't happen since this is a PWA app, but return null + return null + } + + if (registration.active) { + return INSTALLATION_STATES.READY + } + // note that 'registration.waiting' is skipped - it implies there's an active one + if (registration.installing) { + handleInstallingWorker({ + installingWorker: registration.installing, + onStateChange, + }) + return INSTALLATION_STATES.INSTALLING + } + + // It shouldn't normally be possible to get here, but just in case, + // listen for installations + registration.onupdatefound = () => { + // update state for this plugin to 'installing' + onStateChange(INSTALLATION_STATES.INSTALLING) + + // also listen for the installing worker to become active + const installingWorker = registration.installing + if (!installingWorker) { + return + } + handleInstallingWorker({ installingWorker, onStateChange }) + } + + // and in the mean time, return null to show 'not installed' + return null +}