Skip to content

Commit

Permalink
feat(api-client,app,react-api-client): implement ODD anonymous locali…
Browse files Browse the repository at this point in the history
…zation provider (#14741)

implements a localization provider for the ODD app that substitutes
anonymous translation values when the "enableOEMMode" robot setting is
on. pushes the localization provider further down the DOM to be within
the ODD api host provider for the robot settings request, and splits out
a separate instance for the desktop app.

refactors the `OnDeviceDisplayApp` component a bit to avoid rerenders of
providers - currently the entire ODD app rerenders on every route change
because of the scroll ref.

refactors the initial loading screen out of the route tree and up the
DOM to block localization provider render until the robot-server api is
up

adds api client and react api client functions for robot settings get
requests.

all translation keys that reference "opentrons" or "flex" are moved to
the new `branded.json` and `anonymous.json` files. anonymous copy will be finalized in PLAT-243

closes PLAT-265
  • Loading branch information
brenthagen authored and Carlos-fernandez committed May 20, 2024
1 parent 1077962 commit 416e417
Show file tree
Hide file tree
Showing 98 changed files with 722 additions and 741 deletions.
11 changes: 11 additions & 0 deletions api-client/src/robot/getRobotSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { GET, request } from '../request'

import type { ResponsePromise } from '../request'
import type { HostConfig } from '../types'
import type { RobotSettingsResponse } from './types'

export function getRobotSettings(
config: HostConfig
): ResponsePromise<RobotSettingsResponse> {
return request<RobotSettingsResponse>(GET, '/settings', null, config)
}
5 changes: 5 additions & 0 deletions api-client/src/robot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ export { getEstopStatus } from './getEstopStatus'
export { acknowledgeEstopDisengage } from './acknowledgeEstopDisengage'
export { getLights } from './getLights'
export { setLights } from './setLights'
export { getRobotSettings } from './getRobotSettings'

export type {
DoorStatus,
EstopPhysicalStatus,
EstopState,
EstopStatus,
Lights,
RobotSettings,
RobotSettingsField,
RobotSettingsResponse,
SetLightsData,
} from './types'
15 changes: 15 additions & 0 deletions api-client/src/robot/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,18 @@ export interface Lights {
export interface SetLightsData {
on: boolean
}

export interface RobotSettingsField {
id: string
title: string
description: string
value: boolean | null
restart_required?: boolean
}

export type RobotSettings = RobotSettingsField[]

export interface RobotSettingsResponse {
settings: RobotSettings
links?: { restart?: string }
}
82 changes: 43 additions & 39 deletions app/src/App/DesktopApp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react'
import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'
import { ErrorBoundary } from 'react-error-boundary'
import { I18nextProvider } from 'react-i18next'

import {
Box,
Expand All @@ -11,6 +12,7 @@ import {
import { ApiHostProvider } from '@opentrons/react-api-client'
import NiceModal from '@ebay/nice-modal-react'

import { i18n } from '../i18n'
import { Alerts } from '../organisms/Alerts'
import { Breadcrumbs } from '../organisms/Breadcrumbs'
import { ToasterOven } from '../organisms/ToasterOven'
Expand Down Expand Up @@ -101,45 +103,47 @@ export const DesktopApp = (): JSX.Element => {

return (
<NiceModal.Provider>
<ErrorBoundary FallbackComponent={DesktopAppFallback}>
<Navbar routes={desktopRoutes} />
<ToasterOven>
<EmergencyStopContext.Provider
value={{
isEmergencyStopModalDismissed,
setIsEmergencyStopModalDismissed,
}}
>
<Box width="100%">
<Alerts>
<Switch>
{desktopRoutes.map(
({ Component, exact, path }: RouteProps) => {
return (
<Route key={path} exact={exact} path={path}>
<Breadcrumbs />
<Box
position={POSITION_RELATIVE}
width="100%"
height="100%"
backgroundColor={COLORS.grey10}
overflow={OVERFLOW_AUTO}
>
<ModalPortalRoot />
<Component />
</Box>
</Route>
)
}
)}
<Redirect exact from="/" to="/protocols" />
</Switch>
<RobotControlTakeover />
</Alerts>
</Box>
</EmergencyStopContext.Provider>
</ToasterOven>
</ErrorBoundary>
<I18nextProvider i18n={i18n}>
<ErrorBoundary FallbackComponent={DesktopAppFallback}>
<Navbar routes={desktopRoutes} />
<ToasterOven>
<EmergencyStopContext.Provider
value={{
isEmergencyStopModalDismissed,
setIsEmergencyStopModalDismissed,
}}
>
<Box width="100%">
<Alerts>
<Switch>
{desktopRoutes.map(
({ Component, exact, path }: RouteProps) => {
return (
<Route key={path} exact={exact} path={path}>
<Breadcrumbs />
<Box
position={POSITION_RELATIVE}
width="100%"
height="100%"
backgroundColor={COLORS.grey10}
overflow={OVERFLOW_AUTO}
>
<ModalPortalRoot />
<Component />
</Box>
</Route>
)
}
)}
<Redirect exact from="/" to="/protocols" />
</Switch>
<RobotControlTakeover />
</Alerts>
</Box>
</EmergencyStopContext.Provider>
</ToasterOven>
</ErrorBoundary>
</I18nextProvider>
</NiceModal.Provider>
)
}
Expand Down
125 changes: 75 additions & 50 deletions app/src/App/OnDeviceDisplayApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ApiHostProvider } from '@opentrons/react-api-client'
import NiceModal from '@ebay/nice-modal-react'

import { SleepScreen } from '../atoms/SleepScreen'
import { OnDeviceLocalizationProvider } from '../LocalizationProvider'
import { ToasterOven } from '../organisms/ToasterOven'
import { MaintenanceRunTakeover } from '../organisms/TakeoverModal'
import { FirmwareUpdateTakeover } from '../organisms/FirmwareUpdateModal/FirmwareUpdateTakeover'
Expand Down Expand Up @@ -66,7 +67,6 @@ export const ON_DEVICE_DISPLAY_PATHS = [
'/emergency-stop',
'/instruments',
'/instruments/:mount',
'/loading',
'/network-setup',
'/network-setup/ethernet',
'/network-setup/usb',
Expand Down Expand Up @@ -97,8 +97,6 @@ function getPathComponent(
return <InstrumentsDashboard />
case '/instruments/:mount':
return <InstrumentDetail />
case '/loading':
return <InitialLoadingScreen />
case '/network-setup':
return <NetworkSetupMenu />
case '/network-setup/ethernet':
Expand Down Expand Up @@ -151,12 +149,75 @@ export const OnDeviceDisplayApp = (): JSX.Element => {
}
const dispatch = useDispatch<Dispatch>()
const isIdle = useIdle(sleepTime, options)

React.useEffect(() => {
if (isIdle) {
dispatch(updateBrightness(TURN_OFF_BACKLIGHT))
} else {
dispatch(
updateConfigValue(
'onDeviceDisplaySettings.brightness',
userSetBrightness
)
)
}
}, [dispatch, isIdle, userSetBrightness])

// TODO (sb:6/12/23) Create a notification manager to set up preference and order of takeover modals
return (
<ApiHostProvider hostname="127.0.0.1">
<InitialLoadingScreen>
<OnDeviceLocalizationProvider>
<ErrorBoundary FallbackComponent={OnDeviceDisplayAppFallback}>
<Box width="100%" css="user-select: none;">
{isIdle ? (
<SleepScreen />
) : (
<>
<EstopTakeover />
<MaintenanceRunTakeover>
<FirmwareUpdateTakeover />
<NiceModal.Provider>
<ToasterOven>
<ProtocolReceiptToasts />
<OnDeviceDisplayAppRoutes />
</ToasterOven>
</NiceModal.Provider>
</MaintenanceRunTakeover>
</>
)}
</Box>
</ErrorBoundary>
<TopLevelRedirects />
</OnDeviceLocalizationProvider>
</InitialLoadingScreen>
</ApiHostProvider>
)
}

const getTargetPath = (unfinishedUnboxingFlowRoute: string | null): string => {
if (unfinishedUnboxingFlowRoute != null) {
return unfinishedUnboxingFlowRoute
}

return '/dashboard'
}

// split to a separate function because scrollRef rerenders on every route change
// this avoids rerendering parent providers as well
export function OnDeviceDisplayAppRoutes(): JSX.Element {
const [currentNode, setCurrentNode] = React.useState<null | HTMLElement>(null)
const scrollRef = React.useCallback((node: HTMLElement | null) => {
setCurrentNode(node)
}, [])
const isScrolling = useScrolling(currentNode)

const { unfinishedUnboxingFlowRoute } = useSelector(
getOnDeviceDisplaySettings
)

const targetPath = getTargetPath(unfinishedUnboxingFlowRoute)

const TOUCH_SCREEN_STYLE = css`
position: ${POSITION_RELATIVE};
width: 100%;
Expand All @@ -176,54 +237,18 @@ export const OnDeviceDisplayApp = (): JSX.Element => {
}
`

React.useEffect(() => {
if (isIdle) {
dispatch(updateBrightness(TURN_OFF_BACKLIGHT))
} else {
dispatch(
updateConfigValue(
'onDeviceDisplaySettings.brightness',
userSetBrightness
)
)
}
}, [dispatch, isIdle, userSetBrightness])

// TODO (sb:6/12/23) Create a notification manager to set up preference and order of takeover modals
return (
<ApiHostProvider hostname="127.0.0.1">
<ErrorBoundary FallbackComponent={OnDeviceDisplayAppFallback}>
<Box width="100%" css="user-select: none;">
{isIdle ? (
<SleepScreen />
) : (
<>
<EstopTakeover />
<MaintenanceRunTakeover>
<FirmwareUpdateTakeover />
<NiceModal.Provider>
<ToasterOven>
<ProtocolReceiptToasts />
<Switch>
{ON_DEVICE_DISPLAY_PATHS.map(path => (
<Route key={path} exact path={path}>
<Box css={TOUCH_SCREEN_STYLE} ref={scrollRef}>
<ModalPortalRoot />
{getPathComponent(path)}
</Box>
</Route>
))}
<Redirect exact from="/" to={'/loading'} />
</Switch>
</ToasterOven>
</NiceModal.Provider>
</MaintenanceRunTakeover>
</>
)}
</Box>
</ErrorBoundary>
<TopLevelRedirects />
</ApiHostProvider>
<Switch>
{ON_DEVICE_DISPLAY_PATHS.map(path => (
<Route key={path} exact path={path}>
<Box css={TOUCH_SCREEN_STYLE} ref={scrollRef}>
<ModalPortalRoot />
{getPathComponent(path)}
</Box>
</Route>
))}
{targetPath != null && <Redirect exact from="/" to={targetPath} />}
</Switch>
)
}

Expand Down
6 changes: 4 additions & 2 deletions app/src/App/OnDeviceDisplayAppFallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import type { ModalHeaderBaseProps } from '../molecules/Modal/types'
export function OnDeviceDisplayAppFallback({
error,
}: FallbackProps): JSX.Element {
const { t } = useTranslation('app_settings')
const { t } = useTranslation(['app_settings', 'branded'])
const trackEvent = useTrackEvent()
const dispatch = useDispatch<Dispatch>()
const localRobot = useSelector(getLocalRobot)
Expand Down Expand Up @@ -59,7 +59,9 @@ export function OnDeviceDisplayAppFallback({
alignItems={ALIGN_CENTER}
justifyContent={JUSTIFY_CENTER}
>
<StyledText as="p">{t('error_boundary_description')}</StyledText>
<StyledText as="p">
{t('branded:error_boundary_description')}
</StyledText>
<MediumButton
width="100%"
buttonType="alert"
Expand Down
Loading

0 comments on commit 416e417

Please sign in to comment.