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