diff --git a/src/actions/app.ts b/src/actions/app.ts index 49c1d161..e4143d25 100644 --- a/src/actions/app.ts +++ b/src/actions/app.ts @@ -7,6 +7,8 @@ import { Action } from 'redux'; /** App action types. */ export enum AppActionType { + /** Reload the app. */ + Reload = 'app.action.reload', /** The app has just ben started. */ DidStart = 'app.action.didStart', /** Open settings dialog. */ @@ -23,6 +25,14 @@ export enum AppActionType { CloseLicenseDialog = 'app.action.closeLicenseDialog', } +/** Action that requests the app to reload. */ +export type AppReloadAction = Action; + +/** Creates an action that requests the app to reload. */ +export function reload(): AppReloadAction { + return { type: AppActionType.Reload }; +} + /** Action that indicates the app has just started. */ export type AppDidStartAction = Action; @@ -81,6 +91,7 @@ export function closeLicenseDialog(): AppCloseLicenseDialogAction { /** common type for all app actions. */ export type AppAction = + | AppReloadAction | AppDidStartAction | AppOpenSettingsAction | AppCloseSettingsAction diff --git a/src/components/notification-i18n.en.json b/src/components/notification-i18n.en.json index 8eafe558..f4e861be 100644 --- a/src/components/notification-i18n.en.json +++ b/src/components/notification-i18n.en.json @@ -13,6 +13,9 @@ "error": "{errorMessage}" }, "serviceWorker": { - "update": "New content is available and will be used when all tabs for this page are closed.'" + "update": { + "message": "A new version of {appName} is available. Click {action} to start using the new version.", + "action": "Restart" + } } } diff --git a/src/components/notification-i18n.ts b/src/components/notification-i18n.ts index c6a157a3..b28abff2 100644 --- a/src/components/notification-i18n.ts +++ b/src/components/notification-i18n.ts @@ -9,7 +9,8 @@ export enum MessageId { BleGattServiceNotFound = 'ble.gattServiceNotFound', BleNoWebBluetooth = 'ble.noWebBluetooth', ProgramChanged = 'editor.programChanged', - ServiceWorkerUpdate = 'serviceWorker.update', + ServiceWorkerUpdateMessage = 'serviceWorker.update.message', + ServiceWorkerUpdateAction = 'serviceWorker.update.action', YesReloadProgram = 'editor.yesReloadProgram', MpyError = 'mpy.error', } diff --git a/src/sagas/app.ts b/src/sagas/app.ts new file mode 100644 index 00000000..ceae5066 --- /dev/null +++ b/src/sagas/app.ts @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2021 The Pybricks Authors + +import { call, takeEvery } from 'redux-saga/effects'; +import { AppActionType } from '../actions/app'; + +function* reload(): Generator { + console.log('reload'); + + // unregister the service worker so that when the page reloads, it uses + // the new version + const registrations = (yield call(() => + navigator.serviceWorker.getRegistrations(), + )) as ServiceWorkerRegistration[]; + + for (const r of registrations) { + yield call(() => r.unregister()); + } + + location.reload(); +} + +export default function* app(): Generator { + yield takeEvery(AppActionType.Reload, reload); +} diff --git a/src/sagas/index.ts b/src/sagas/index.ts index 16e2a986..1e88d206 100644 --- a/src/sagas/index.ts +++ b/src/sagas/index.ts @@ -3,6 +3,7 @@ import { all, put } from 'redux-saga/effects'; import { didStart } from '../actions/app'; +import app from './app'; import bleUart from './ble-uart'; import editor from './editor'; import errorLog from './error-log'; @@ -19,6 +20,7 @@ import terminal from './terminal'; /* istanbul ignore next */ export default function* (): Generator { yield all([ + app(), bleUart(), lwp3BootloaderBle(), lwp3BootloaderProtocol(), diff --git a/src/sagas/notification.ts b/src/sagas/notification.ts index 4af52ee9..7a947e91 100644 --- a/src/sagas/notification.ts +++ b/src/sagas/notification.ts @@ -14,6 +14,7 @@ import { Replacements } from '@shopify/react-i18n'; import React from 'react'; import { channel } from 'redux-saga'; import { delay, getContext, put, take, takeEvery } from 'redux-saga/effects'; +import { reload } from '../actions/app'; import { BleDeviceActionType, BleDeviceDidFailToConnectAction, @@ -30,6 +31,7 @@ import { NotificationActionType, NotificationAddAction } from '../actions/notifi import { ServiceWorkerActionType } from '../actions/service-worker'; import Notification from '../components/Notification'; import { MessageId } from '../components/notification-i18n'; +import { appName } from '../settings/ui'; type NotificationContext = { toaster: IToaster; @@ -229,12 +231,26 @@ function* addNotification(action: NotificationAddAction): Generator { } function* showServiceWorkerUpdate(): Generator { + const ch = channel>(); + const action = dispatchAction( + MessageId.ServiceWorkerUpdateAction, + ch.put, + 'refresh', + ); yield* showSingleton( Level.Info, - MessageId.ServiceWorkerUpdate, - undefined, - helpAction('https://github.com/pybricks/pybricks-code/issues/102'), + MessageId.ServiceWorkerUpdateMessage, + { + appName, + action: React.createElement('strong', undefined, action.text), + }, + action, + ch.close, ); + + yield take(ch); + + yield put(reload()); } export default function* (): Generator {