Skip to content

Commit

Permalink
feat(app): factory mode desktop toggle (#14911)
Browse files Browse the repository at this point in the history
adds the desktop advanced setting toggle to enable factory mode.

closes PLAT-280, PLAT-282
  • Loading branch information
brenthagen committed Apr 16, 2024
1 parent 8cc2fc7 commit dfb572c
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 10 deletions.
1 change: 1 addition & 0 deletions app/src/assets/localization/en/anonymous.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"module_calibration_get_started": "<block>To get started, remove labware from the deck and clean up the working area to make the calibration easier. Also gather the needed equipment shown to the right.</block><block>The calibration adapter came with your module. The pipette probe came with your pipette.</block>",
"module_error_contact_support": "Try powering the module off and on again. If the error persists, contact support.",
"network_setup_menu_description": "You’ll use this connection to run software updates and load protocols onto your robot.",
"oem_mode_description": "Enable OEM Mode to remove all instances of Opentrons from the Flex touchscreen.",
"opentrons_app_successfully_updated": "The app was successfully updated.",
"opentrons_app_update": "app update",
"opentrons_app_update_available": "App Update Available",
Expand Down
1 change: 1 addition & 0 deletions app/src/assets/localization/en/branded.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"module_calibration_get_started": "<block>To get started, remove labware from the deck and clean up the working area to make the calibration easier. Also gather the needed equipment shown to the right.</block><block>The calibration adapter came with your module. The pipette probe came with your Flex pipette.</block>",
"module_error_contact_support": "Try powering the module off and on again. If the error persists, contact Opentrons Support.",
"network_setup_menu_description": "You’ll use this connection to run software updates and load protocols onto your Opentrons Flex.",
"oem_mode_description": "Enable OEM Mode to remove all instances of Opentrons from the Flex touchscreen.",
"opentrons_app_successfully_updated": "The Opentrons App was successfully updated.",
"opentrons_app_update": "Opentrons App update",
"opentrons_app_update_available": "Opentrons App Update Available",
Expand Down
9 changes: 9 additions & 0 deletions app/src/assets/localization/en/device_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"clear_option_runs_history_subtext": "Clears information about past runs of all protocols.",
"clear_option_tip_length_calibrations": "Clear tip length calibrations",
"cancel_software_update": "Cancel software update",
"complete_and_restart_robot": "Complete and restart robot",
"confirm_device_reset_description": "This will permanently delete all protocol, calibration, and other data. You’ll have to redo initial setup before using the robot again.",
"confirm_device_reset_heading": "Are you sure you want to reset your device?",
"connect": "Connect",
Expand Down Expand Up @@ -107,6 +108,7 @@
"enable_status_light": "Enable status light",
"enable_status_light_description": "Turn on or off the strip of color LEDs on the front of the robot.",
"engaged": "Engaged",
"enter_factory_password": "Enter factory password",
"enter_network_name": "Enter network name",
"enter_password": "Enter password",
"estop": "E-stop",
Expand All @@ -118,6 +120,7 @@
"ethernet": "Ethernet",
"ethernet_connection_description": "Connect an Ethernet cable to the back of the robot and a network switch or hub.",
"exit": "exit",
"factory_mode": "Factory Mode",
"factory_reset": "Factory Reset",
"factory_reset_description": "Resets all settings. You’ll have to redo initial setup before using the robot again.",
"factory_reset_modal_description": "This data cannot be retrieved later.",
Expand All @@ -140,6 +143,7 @@
"install_e_stop": "Install the E-stop",
"installing_software": "Installing software...",
"installing_update": "Installing update...",
"invalid_password": "Invalid password",
"ip_address": "IP Address",
"join_other_network": "Join other network",
"join_other_network_error_message": "Must be 2–32 characters long",
Expand All @@ -151,6 +155,7 @@
"launch_jupyter_notebook": "Launch Jupyter Notebook",
"legacy_settings": "Legacy Settings",
"mac_address": "MAC Address",
"manage_oem_settings": "Manage OEM settings",
"minutes": "{{minute}} minutes",
"missing_calibration": "Missing calibration",
"model_and_serial": "Pipette Model and Serial",
Expand Down Expand Up @@ -186,7 +191,10 @@
"not_connected_via_wifi": "Not connected via Wi-Fi",
"not_connected_via_wired_usb": "Not connected via wired USB",
"not_now": "Not now",
"oem_mode": "OEM Mode",
"off": "Off",
"one_hour": "1 hour",
"on": "On",
"other_networks": "Other Networks",
"password": "Password",
"password_error_message": "Must be at least 8 characters",
Expand Down Expand Up @@ -252,6 +260,7 @@
"select_authentication_method": "Select authentication method for your selected network.",
"sending_software": "Sending software...",
"serial": "Serial",
"setup_mode": "Setup mode",
"short_trash_bin": "Short trash bin",
"short_trash_bin_description": "For pre-2019 robots with trash bins that are 55mm tall (instead of 77mm default)",
"show": "Show",
Expand Down
13 changes: 3 additions & 10 deletions app/src/atoms/Slideout/MultiSlideout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import * as React from 'react'
import { Slideout } from './index'

interface MultiSlideoutProps {
title: string | React.ReactElement
children: React.ReactNode
onCloseClick: () => void
currentStep: number
maxSteps: number
// isExpanded is for collapse and expand animation
isExpanded?: boolean
footer?: React.ReactNode
}
import type { MultiSlideoutSpecs, SlideoutProps } from './index'

type MultiSlideoutProps = SlideoutProps & MultiSlideoutSpecs

export const MultiSlideout = (props: MultiSlideoutProps): JSX.Element => {
const {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import * as React from 'react'
import { useDispatch } from 'react-redux'
import { useForm, Controller } from 'react-hook-form'
import { useTranslation } from 'react-i18next'

import {
ALIGN_CENTER,
COLORS,
DIRECTION_COLUMN,
Flex,
PrimaryButton,
SPACING,
StyledText,
TYPOGRAPHY,
} from '@opentrons/components'
import { useRobotSettingsQuery } from '@opentrons/react-api-client'

import { ToggleButton } from '../../../../../atoms/buttons'
import { InputField } from '../../../../../atoms/InputField'
import { MultiSlideout } from '../../../../../atoms/Slideout/MultiSlideout'
import { restartRobot } from '../../../../../redux/robot-admin'
import { updateSetting } from '../../../../../redux/robot-settings'

import type { RobotSettingsField } from '@opentrons/api-client'
import type { Dispatch } from '../../../../../redux/types'

interface FactoryModeSlideoutProps {
isExpanded: boolean
onCloseClick: () => void
robotName: string
}

interface FormValues {
passwordInput: string
}

export function FactoryModeSlideout({
isExpanded,
onCloseClick,
robotName,
}: FactoryModeSlideoutProps): JSX.Element {
const { t } = useTranslation(['device_settings', 'shared', 'branded'])

const dispatch = useDispatch<Dispatch>()

const { settings } = useRobotSettingsQuery().data ?? {}
const oemModeSetting = (settings ?? []).find(
(setting: RobotSettingsField) => setting?.id === 'enableOEMMode'
)
const isOEMMode = oemModeSetting?.value ?? null

const [currentStep, setCurrentStep] = React.useState<number>(1)
const [toggleValue, setToggleValue] = React.useState<boolean>(false)

const {
handleSubmit,
control,
formState: { errors },
trigger,
} = useForm({
defaultValues: {
passwordInput: '',
},
})
const onSubmit = (data: FormValues): void => {
setCurrentStep(2)
}

const handleSubmitFactoryPassword = (): void => {
// TODO: validation and errors: PLAT-281
void handleSubmit(onSubmit)()
}

const handleToggleClick: React.MouseEventHandler<Element> = () => {
setToggleValue(toggleValue => !toggleValue)
}

const handleCompleteClick: React.MouseEventHandler<Element> = () => {
dispatch(updateSetting(robotName, 'enableOEMMode', toggleValue))
dispatch(restartRobot(robotName))
onCloseClick()
}

React.useEffect(() => {
// initialize local state to OEM mode value
if (isOEMMode != null) {
setToggleValue(isOEMMode)
}
}, [isOEMMode])

return (
<MultiSlideout
title={currentStep === 1 ? t('enter_password') : t('manage_oem_settings')}
maxSteps={2}
currentStep={currentStep}
onCloseClick={onCloseClick}
isExpanded={isExpanded}
footer={
<>
{currentStep === 1 ? (
<PrimaryButton onClick={handleSubmitFactoryPassword} width="100%">
{t('shared:next')}
</PrimaryButton>
) : null}
{currentStep === 2 ? (
<PrimaryButton onClick={handleCompleteClick} width="100%">
{t('complete_and_restart_robot')}
</PrimaryButton>
) : null}
</>
}
>
{currentStep === 1 ? (
<Flex flexDirection={DIRECTION_COLUMN}>
<Controller
control={control}
name="passwordInput"
render={({ field, fieldState }) => (
<InputField
id="passwordInput"
name="passwordInput"
type="text"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
field.onChange(e)
trigger('passwordInput')
}}
value={field.value}
error={fieldState.error?.message && ' '}
onBlur={field.onBlur}
title={t('enter_factory_password')}
/>
)}
/>
{errors.passwordInput != null ? (
<StyledText
as="label"
color={COLORS.red50}
marginTop={SPACING.spacing4}
>
{errors.passwordInput.message}
</StyledText>
) : null}
</Flex>
) : null}
{currentStep === 2 ? (
<Flex flexDirection={DIRECTION_COLUMN}>
<StyledText
css={TYPOGRAPHY.pSemiBold}
paddingBottom={SPACING.spacing4}
>
{t('oem_mode')}
</StyledText>
<Flex alignItems={ALIGN_CENTER} gridGap={SPACING.spacing6}>
<ToggleButton
label="oem_mode_toggle"
toggledOn={toggleValue}
onClick={handleToggleClick}
/>
<StyledText as="p" marginBottom={SPACING.spacing4}>
{toggleValue ? t('on') : t('off')}
</StyledText>
</Flex>
<StyledText as="p">{t('branded:oem_mode_description')}</StyledText>
</Flex>
) : null}
</MultiSlideout>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'

import {
ALIGN_CENTER,
Box,
Flex,
JUSTIFY_SPACE_BETWEEN,
SPACING_AUTO,
SPACING,
StyledText,
TYPOGRAPHY,
} from '@opentrons/components'

import { TertiaryButton } from '../../../../atoms/buttons'

interface FactoryModeProps {
isRobotBusy: boolean
setShowFactoryModeSlideout: React.Dispatch<React.SetStateAction<boolean>>
}

export function FactoryMode({
isRobotBusy,
setShowFactoryModeSlideout,
}: FactoryModeProps): JSX.Element {
const { t } = useTranslation('device_settings')

return (
<Flex
alignItems={ALIGN_CENTER}
justifyContent={JUSTIFY_SPACE_BETWEEN}
marginTop={SPACING.spacing24}
>
<Box width="70%">
<StyledText as="p" fontWeight={TYPOGRAPHY.fontWeightSemiBold}>
{t('factory_mode')}
</StyledText>
</Box>
<TertiaryButton
disabled={isRobotBusy}
marginLeft={SPACING_AUTO}
onClick={() => {
setShowFactoryModeSlideout(true)
}}
>
{t('setup_mode')}
</TertiaryButton>
</Flex>
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './DeviceReset'
export * from './DisplayRobotName'
export * from './EnableStatusLight'
export * from './FactoryMode'
export * from './GantryHoming'
export * from './LegacySettings'
export * from './OpenJupyterControl'
Expand Down
22 changes: 22 additions & 0 deletions app/src/organisms/Devices/RobotSettings/RobotSettingsAdvanced.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
DeviceReset,
DisplayRobotName,
EnableStatusLight,
FactoryMode,
GantryHoming,
LegacySettings,
OpenJupyterControl,
Expand All @@ -39,6 +40,7 @@ import {
import { RenameRobotSlideout } from './AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout'
import { DeviceResetSlideout } from './AdvancedTab/AdvancedTabSlideouts/DeviceResetSlideout'
import { DeviceResetModal } from './AdvancedTab/AdvancedTabSlideouts/DeviceResetModal'
import { FactoryModeSlideout } from './AdvancedTab/AdvancedTabSlideouts/FactoryModeSlideout'
import { handleUpdateBuildroot } from './UpdateBuildroot'
import { UNREACHABLE } from '../../../redux/discovery'
import { getTopPortalEl } from '../../../App/portal'
Expand Down Expand Up @@ -72,6 +74,10 @@ export function RobotSettingsAdvanced({
showDeviceResetModal,
setShowDeviceResetModal,
] = React.useState<boolean>(false)
const [
showFactoryModeSlideout,
setShowFactoryModeSlideout,
] = React.useState<boolean>(false)

const isRobotBusy = useIsRobotBusy({ poll: true })
const isEstopNotDisengaged = useIsEstopNotDisengaged(robotName)
Expand Down Expand Up @@ -131,6 +137,13 @@ export function RobotSettingsAdvanced({
robotName={robotName}
/>
)}
{showFactoryModeSlideout && (
<FactoryModeSlideout
isExpanded={showFactoryModeSlideout}
onCloseClick={() => setShowFactoryModeSlideout(false)}
robotName={robotName}
/>
)}
{showDeviceResetSlideout && (
<DeviceResetSlideout
isExpanded={showDeviceResetSlideout}
Expand Down Expand Up @@ -195,6 +208,15 @@ export function RobotSettingsAdvanced({
isRobotBusy={isRobotBusy || isEstopNotDisengaged}
onUpdateStart={() => handleUpdateBuildroot(robot)}
/>
{isFlex ? (
<>
<Divider marginY={SPACING.spacing16} />
<FactoryMode
isRobotBusy={isRobotBusy || isEstopNotDisengaged}
setShowFactoryModeSlideout={setShowFactoryModeSlideout}
/>
</>
) : null}
<Troubleshooting
robotName={robotName}
isEstopNotDisengaged={isEstopNotDisengaged}
Expand Down

0 comments on commit dfb572c

Please sign in to comment.