Skip to content

Commit

Permalink
feat: headerbar PWA update notifications [LIBS-344] (#748)
Browse files Browse the repository at this point in the history
  • Loading branch information
amcgee committed Oct 6, 2022
1 parent 47fc9ff commit b245bf1
Show file tree
Hide file tree
Showing 22 changed files with 924 additions and 739 deletions.
63 changes: 27 additions & 36 deletions adapter/i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,33 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2021-10-12T12:20:51.045Z\n"
"PO-Revision-Date: 2021-10-12T12:20:51.045Z\n"
"POT-Creation-Date: 2022-09-22T19:11:17.660Z\n"
"PO-Revision-Date: 2022-09-22T19:11:17.660Z\n"

msgid "Save your data"
msgstr "Save your data"

msgid ""
"Updating will reload all {{n}} open instances of this app, and any unsaved "
"data will be lost. Save any data you need to, then click 'Reload' when "
"ready."
msgstr ""
"Updating will reload all {{n}} open instances of this app, and any unsaved "
"data will be lost. Save any data you need to, then click 'Reload' when "
"ready."

msgid ""
"Updating will reload all open instances of this app, and any unsaved data "
"will be lost. Save any data you need to, then click 'Reload' when ready."
msgstr ""
"Updating will reload all open instances of this app, and any unsaved data "
"will be lost. Save any data you need to, then click 'Reload' when ready."

msgid "Cancel"
msgstr "Cancel"

msgid "Reload"
msgstr "Reload"

msgid "An error occurred in the DHIS2 application."
msgstr "An error occurred in the DHIS2 application."
Expand Down Expand Up @@ -46,37 +71,3 @@ msgstr "Password"

msgid "Sign in"
msgstr "Sign in"

msgid "Save your data"
msgstr "Save your data"

msgid ""
"Updating will reload all {{n}} open instances of this app, and any unsaved "
"data will be lost. Save any data you need to, then click 'Reload' when "
"ready."
msgstr ""
"Updating will reload all {{n}} open instances of this app, and any unsaved "
"data will be lost. Save any data you need to, then click 'Reload' when "
"ready."

msgid ""
"Updating will reload all open instances of this app, and any unsaved data "
"will be lost. Save any data you need to, then click 'Reload' when ready."
msgstr ""
"Updating will reload all open instances of this app, and any unsaved data "
"will be lost. Save any data you need to, then click 'Reload' when ready."

msgid "Cancel"
msgstr "Cancel"

msgid "Reload"
msgstr "Reload"

msgid "There's an update available for this app."
msgstr "There's an update available for this app."

msgid "Update and reload"
msgstr "Update and reload"

msgid "Not now"
msgstr "Not now"
4 changes: 2 additions & 2 deletions adapter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
"test": "d2-app-scripts test"
},
"peerDependencies": {
"@dhis2/app-runtime": "^3",
"@dhis2/app-runtime": "^3.5",
"@dhis2/d2-i18n": "^1",
"@dhis2/ui": ">=7",
"@dhis2/ui": ">=8.5",
"classnames": "^2",
"moment": "^2",
"prop-types": "^15",
Expand Down
10 changes: 3 additions & 7 deletions adapter/src/components/AppWrapper.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { HeaderBar } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React from 'react'
import { useCurrentUserLocale } from '../utils/useLocale.js'
import { useVerifyLatestUser } from '../utils/useVerifyLatestUser.js'
import { Alerts } from './Alerts.js'
import { ConnectedHeaderBar } from './ConnectedHeaderBar.js'
import { ErrorBoundary } from './ErrorBoundary.js'
import { LoadingMask } from './LoadingMask.js'
import PWAUpdateManager from './PWAUpdateManager.js'
import { styles } from './styles/AppWrapper.style.js'

export const AppWrapper = ({ appName, children, offlineInterface }) => {
export const AppWrapper = ({ children }) => {
const { loading: localeLoading } = useCurrentUserLocale()
const { loading: latestUserLoading } = useVerifyLatestUser()

Expand All @@ -20,20 +19,17 @@ export const AppWrapper = ({ appName, children, offlineInterface }) => {
return (
<div className="app-shell-adapter">
<style jsx>{styles}</style>
<HeaderBar appName={appName} />
<ConnectedHeaderBar />
<div className="app-shell-app">
<ErrorBoundary onRetry={() => window.location.reload()}>
{children}
</ErrorBoundary>
</div>
<Alerts />
<PWAUpdateManager offlineInterface={offlineInterface} />
</div>
)
}

AppWrapper.propTypes = {
appName: PropTypes.string.isRequired,
offlineInterface: PropTypes.object.isRequired,
children: PropTypes.node,
}
43 changes: 43 additions & 0 deletions adapter/src/components/ConfirmUpdateModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
Button,
ButtonStrip,
Modal,
ModalActions,
ModalContent,
ModalTitle,
} from '@dhis2/ui'
import PropTypes from 'prop-types'
import React from 'react'
import i18n from '../locales'

export function ConfirmUpdateModal({ clientsCount, onCancel, onConfirm }) {
return (
<Modal position="middle">
<ModalTitle>{i18n.t('Save your data')}</ModalTitle>
<ModalContent>
{clientsCount
? i18n.t(
"Updating will reload all {{n}} open instances of this app, and any unsaved data will be lost. Save any data you need to, then click 'Reload' when ready.",
{ n: clientsCount }
)
: // Fallback if clientsCount is unavailable:
i18n.t(
"Updating will reload all open instances of this app, and any unsaved data will be lost. Save any data you need to, then click 'Reload' when ready."
)}
</ModalContent>
<ModalActions>
<ButtonStrip end>
<Button onClick={onCancel}>{i18n.t('Cancel')}</Button>
<Button destructive onClick={onConfirm}>
{i18n.t('Reload')}
</Button>
</ButtonStrip>
</ModalActions>
</Modal>
)
}
ConfirmUpdateModal.propTypes = {
clientsCount: PropTypes.number,
onCancel: PropTypes.func,
onConfirm: PropTypes.func,
}
42 changes: 42 additions & 0 deletions adapter/src/components/ConnectedHeaderBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useConfig } from '@dhis2/app-runtime'
import { HeaderBar } from '@dhis2/ui'
import React from 'react'
import { usePWAUpdateState } from '../utils/usePWAUpdateState'
import { ConfirmUpdateModal } from './ConfirmUpdateModal'

/**
* Check for SW updates or a first activation, displaying an update notification
* message in the HeaderBar profile menu. When an update is applied, if there are
* multiple tabs of this app open, there's anadditional warning step because all
* clients of the service worker will reload when there's an update, which may
* cause data loss.
*/

export function ConnectedHeaderBar() {
const { appName } = useConfig()
const {
updateAvailable,
confirmReload,
confirmationRequired,
clientsCount,
onConfirmUpdate,
onCancelUpdate,
} = usePWAUpdateState()

return (
<>
<HeaderBar
appName={appName}
updateAvailable={updateAvailable}
onApplyAvailableUpdate={confirmReload}
/>
{confirmationRequired ? (
<ConfirmUpdateModal
clientsCount={clientsCount}
onConfirm={onConfirmUpdate}
onCancel={onCancelUpdate}
/>
) : null}
</>
)
}
18 changes: 18 additions & 0 deletions adapter/src/components/OfflineInterfaceContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { OfflineInterface } from '@dhis2/pwa'
import PropTypes from 'prop-types'
import React, { createContext, useContext } from 'react'

const theOfflineInterface = new OfflineInterface()
const OfflineInterfaceContext = createContext(theOfflineInterface)

export const OfflineInterfaceProvider = ({ children }) => (
<OfflineInterfaceContext.Provider value={theOfflineInterface}>
{children}
</OfflineInterfaceContext.Provider>
)

OfflineInterfaceProvider.propTypes = {
children: PropTypes.node,
}

export const useOfflineInterface = () => useContext(OfflineInterfaceContext)
40 changes: 40 additions & 0 deletions adapter/src/components/PWALoadingBoundary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
REGISTRATION_STATE_WAITING,
REGISTRATION_STATE_FIRST_ACTIVATION,
} from '@dhis2/pwa'
import PropTypes from 'prop-types'
import { useEffect, useState } from 'react'
import { useOfflineInterface } from './OfflineInterfaceContext'

export const PWALoadingBoundary = ({ children }) => {
const [pwaReady, setPWAReady] = useState(false)
const offlineInterface = useOfflineInterface()

useEffect(() => {
const checkRegistration = async () => {
const registrationState =
await offlineInterface.getRegistrationState()
const clientsInfo = await offlineInterface.getClientsInfo()
if (
(registrationState === REGISTRATION_STATE_WAITING ||
registrationState ===
REGISTRATION_STATE_FIRST_ACTIVATION) &&
clientsInfo.clientsCount === 1
) {
console.log(
'Reloading on startup to activate waiting service worker'
)
offlineInterface.useNewSW()
} else {
setPWAReady(true)
}
}
checkRegistration()
}, [offlineInterface])

return pwaReady ? children : null
}

PWALoadingBoundary.propTypes = {
children: PropTypes.node.isRequired,
}
110 changes: 0 additions & 110 deletions adapter/src/components/PWAUpdateManager.js

This file was deleted.

Loading

0 comments on commit b245bf1

Please sign in to comment.