diff --git a/app-shell/src/main.ts b/app-shell/src/main.ts
index b1ef492b949..db68d33a753 100644
--- a/app-shell/src/main.ts
+++ b/app-shell/src/main.ts
@@ -2,7 +2,7 @@
import { app, ipcMain } from 'electron'
import contextMenu from 'electron-context-menu'
-import { createUi } from './ui'
+import { createUi, registerReloadUi } from './ui'
import { initializeMenu } from './menu'
import { createLogger } from './log'
import { registerProtocolAnalysis } from './protocol-analysis'
@@ -88,6 +88,7 @@ function startUp(): void {
registerSystemInfo(dispatch),
registerProtocolStorage(dispatch),
registerUsb(dispatch),
+ registerReloadUi(mainWindow),
]
ipcMain.on('dispatch', (_, action) => {
diff --git a/app-shell/src/ui.ts b/app-shell/src/ui.ts
index 6bdd1240edf..e36a0ac5625 100644
--- a/app-shell/src/ui.ts
+++ b/app-shell/src/ui.ts
@@ -1,9 +1,14 @@
// sets up the main window ui
import { app, shell, BrowserWindow } from 'electron'
import path from 'path'
+
+import { RELOAD_UI } from '@opentrons/app/src/redux/shell/actions'
+
import { getConfig } from './config'
import { createLogger } from './log'
+import type { Action } from './types'
+
const config = getConfig('ui')
const log = createLogger('ui')
@@ -61,3 +66,17 @@ export function createUi(): BrowserWindow {
return mainWindow
}
+
+export function registerReloadUi(
+ browserWindow: BrowserWindow
+): (action: Action) => unknown {
+ return function handleAction(action: Action) {
+ switch (action.type) {
+ case RELOAD_UI:
+ log.info(`reloading UI: ${action.payload.message}`)
+ browserWindow.webContents.reload()
+
+ break
+ }
+ }
+}
diff --git a/app/src/App/DesktopApp.tsx b/app/src/App/DesktopApp.tsx
index 86e81abcfd9..f42ef7e0e80 100644
--- a/app/src/App/DesktopApp.tsx
+++ b/app/src/App/DesktopApp.tsx
@@ -1,5 +1,6 @@
import * as React from 'react'
import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'
+import { ErrorBoundary } from 'react-error-boundary'
import {
Box,
@@ -29,6 +30,7 @@ import { OPENTRONS_USB } from '../redux/discovery'
import { appShellRequestor } from '../redux/shell/remote'
import { useRobot, useIsFlex } from '../organisms/Devices/hooks'
import { PortalRoot as ModalPortalRoot } from './portal'
+import { DesktopAppFallback } from './DesktopAppFallback'
import type { RouteProps, DesktopRouteParams } from './types'
@@ -99,41 +101,45 @@ export const DesktopApp = (): JSX.Element => {
return (
-
-
-
-
-
-
- {desktopRoutes.map(({ Component, exact, path }: RouteProps) => {
- return (
-
-
-
-
-
-
-
- )
- })}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {desktopRoutes.map(
+ ({ Component, exact, path }: RouteProps) => {
+ return (
+
+
+
+
+
+
+
+ )
+ }
+ )}
+
+
+
+
+
+
+
+
)
}
diff --git a/app/src/App/DesktopAppFallback.tsx b/app/src/App/DesktopAppFallback.tsx
new file mode 100644
index 00000000000..f1e1ce1e990
--- /dev/null
+++ b/app/src/App/DesktopAppFallback.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react'
+import { useDispatch } from 'react-redux'
+import { useHistory } from 'react-router-dom'
+import { useTranslation } from 'react-i18next'
+
+import { useTrackEvent, ANALYTICS_DESKTOP_APP_ERROR } from '../redux/analytics'
+
+import type { FallbackProps } from 'react-error-boundary'
+
+import {
+ AlertPrimaryButton,
+ ALIGN_FLEX_END,
+ DIRECTION_COLUMN,
+ Flex,
+ SPACING,
+ TYPOGRAPHY,
+} from '@opentrons/components'
+
+import { StyledText } from '../atoms/text'
+import { LegacyModal } from '../molecules/LegacyModal'
+import { reloadUi } from '../redux/shell'
+
+import type { Dispatch } from '../redux/types'
+
+export function DesktopAppFallback({ error }: FallbackProps): JSX.Element {
+ const { t } = useTranslation('app_settings')
+ const trackEvent = useTrackEvent()
+ const dispatch = useDispatch()
+ const history = useHistory()
+ const handleReloadClick = (): void => {
+ trackEvent({
+ name: ANALYTICS_DESKTOP_APP_ERROR,
+ properties: { errorMessage: error.message },
+ })
+ // route to the root page and initiate an electron browser window reload via app-shell
+ history.push('/')
+ dispatch(reloadUi(error.message))
+ }
+
+ return (
+
+
+
+
+ {t('error_boundary_desktop_app_description')}
+
+
+ {error.message}
+
+
+
+ {t('reload_app')}
+
+
+
+ )
+}
diff --git a/app/src/assets/localization/en/app_settings.json b/app/src/assets/localization/en/app_settings.json
index 747956b0b27..e94a64cedfb 100644
--- a/app/src/assets/localization/en/app_settings.json
+++ b/app/src/assets/localization/en/app_settings.json
@@ -34,6 +34,7 @@
"enable_dev_tools": "Developer Tools",
"enable_dev_tools_description": "Enabling this setting opens Developer Tools on app launch, enables additional logging and gives access to feature flags.",
"error_boundary_description": "You need to restart the touchscreen. Then download the robot logs from the Opentrons App and send them to support@opentrons.com for assistance.",
+ "error_boundary_desktop_app_description": "You need to reload the app. Contact support with the following error message:",
"error_boundary_title": "An unknown error has occurred",
"feature_flags": "Feature Flags",
"general": "General",
@@ -71,6 +72,7 @@
"prompt": "Always show the prompt to choose calibration block or trash bin",
"receive_alert": "Receive an alert when an Opentrons software update is available.",
"release_notes": "Release notes",
+ "reload_app": "Reload app",
"remind_later": "Remind me later",
"reset_to_default": "Reset to default",
"restart_touchscreen": "Restart touchscreen",
diff --git a/app/src/redux/analytics/constants.ts b/app/src/redux/analytics/constants.ts
index da66e4fe84a..108601a9de7 100644
--- a/app/src/redux/analytics/constants.ts
+++ b/app/src/redux/analytics/constants.ts
@@ -42,3 +42,4 @@ export const ANALYTICS_OPEN_LABWARE_CREATOR_FROM_BOTTOM_OF_LABWARE_LIBRARY_LIST
'openLabwareCreatorFromBottomOfLabwareLibraryList'
export const ANALYTICS_SENT_TO_FLEX = 'sendToFlex' // This would be changed
export const ANALYTICS_ODD_APP_ERROR = 'oddError'
+export const ANALYTICS_DESKTOP_APP_ERROR = 'desktopAppError'
diff --git a/app/src/redux/shell/actions.ts b/app/src/redux/shell/actions.ts
index 4c20d59bd73..3374b4729cc 100644
--- a/app/src/redux/shell/actions.ts
+++ b/app/src/redux/shell/actions.ts
@@ -2,6 +2,7 @@ import type {
UiInitializedAction,
UsbRequestsAction,
AppRestartAction,
+ ReloadUiAction,
SendLogAction,
UpdateBrightnessAction,
RobotMassStorageDeviceAdded,
@@ -15,6 +16,7 @@ export const USB_HTTP_REQUESTS_START: 'shell:USB_HTTP_REQUESTS_START' =
export const USB_HTTP_REQUESTS_STOP: 'shell:USB_HTTP_REQUESTS_STOP' =
'shell:USB_HTTP_REQUESTS_STOP'
export const APP_RESTART: 'shell:APP_RESTART' = 'shell:APP_RESTART'
+export const RELOAD_UI: 'shell:RELOAD_UI' = 'shell:RELOAD_UI'
export const SEND_LOG: 'shell:SEND_LOG' = 'shell:SEND_LOG'
export const UPDATE_BRIGHTNESS: 'shell:UPDATE_BRIGHTNESS' =
'shell:UPDATE_BRIGHTNESS'
@@ -48,6 +50,14 @@ export const appRestart = (message: string): AppRestartAction => ({
meta: { shell: true },
})
+export const reloadUi = (message: string): ReloadUiAction => ({
+ type: RELOAD_UI,
+ payload: {
+ message: message,
+ },
+ meta: { shell: true },
+})
+
export const sendLog = (message: string): SendLogAction => ({
type: SEND_LOG,
payload: {
diff --git a/app/src/redux/shell/types.ts b/app/src/redux/shell/types.ts
index cc6524f3544..e5aff7672ac 100644
--- a/app/src/redux/shell/types.ts
+++ b/app/src/redux/shell/types.ts
@@ -63,6 +63,14 @@ export interface AppRestartAction {
meta: { shell: true }
}
+export interface ReloadUiAction {
+ type: 'shell:RELOAD_UI'
+ payload: {
+ message: string
+ }
+ meta: { shell: true }
+}
+
export interface SendLogAction {
type: 'shell:SEND_LOG'
payload: {
@@ -110,6 +118,7 @@ export type ShellAction =
| RobotSystemAction
| UsbRequestsAction
| AppRestartAction
+ | ReloadUiAction
| SendLogAction
| UpdateBrightnessAction
| RobotMassStorageDeviceAdded