Skip to content

Commit 433ef44

Browse files
authored
feat(api-client,app,react-api-client): upload splash logo from desktop app (#14941)
adds the upload input component, api-client, and react-api-client functions needed to upload a splash logo from the factory mode slideout closes PLAT-283
1 parent 737c58c commit 433ef44

File tree

14 files changed

+397
-36
lines changed

14 files changed

+397
-36
lines changed

api-client/src/robot/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { acknowledgeEstopDisengage } from './acknowledgeEstopDisengage'
44
export { getLights } from './getLights'
55
export { setLights } from './setLights'
66
export { getRobotSettings } from './getRobotSettings'
7+
export { updateRobotSetting } from './updateRobotSetting'
78

89
export type {
910
DoorStatus,
@@ -15,4 +16,5 @@ export type {
1516
RobotSettingsField,
1617
RobotSettingsResponse,
1718
SetLightsData,
19+
UpdateRobotSettingRequest,
1820
} from './types'

api-client/src/robot/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ export interface RobotSettingsField {
3838

3939
export type RobotSettings = RobotSettingsField[]
4040

41+
export interface UpdateRobotSettingRequest {
42+
id: string
43+
value: boolean | null
44+
}
45+
4146
export interface RobotSettingsResponse {
4247
settings: RobotSettings
4348
links?: { restart?: string }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { POST, request } from '../request'
2+
3+
import type { ResponsePromise } from '../request'
4+
import type { HostConfig } from '../types'
5+
import type { RobotSettingsResponse, UpdateRobotSettingRequest } from './types'
6+
7+
export function updateRobotSetting(
8+
config: HostConfig,
9+
id: string,
10+
value: boolean
11+
): ResponsePromise<RobotSettingsResponse> {
12+
return request<RobotSettingsResponse, UpdateRobotSettingRequest>(
13+
POST,
14+
'/settings',
15+
{ id, value },
16+
config
17+
)
18+
}

api-client/src/system/createSplash.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { POST, request } from '../request'
2+
import type { ResponsePromise } from '../request'
3+
import type { HostConfig } from '../types'
4+
5+
export function createSplash(
6+
config: HostConfig,
7+
file: File
8+
): ResponsePromise<void> {
9+
// sanitize file name to ensure no spaces
10+
const renamedFile = new File([file], file.name.replace(' ', '_'), {
11+
type: 'image/png',
12+
})
13+
14+
const formData = new FormData()
15+
formData.append('file', renamedFile)
16+
17+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
18+
return request<void, FormData>(
19+
POST,
20+
'/system/oem_mode/upload_splash',
21+
formData,
22+
config
23+
)
24+
}

api-client/src/system/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { createAuthorization } from './createAuthorization'
22
export { createRegistration } from './createRegistration'
3+
export { createSplash } from './createSplash'
34
export { getConnections } from './getConnections'
45
export * from './types'

app/src/assets/localization/en/device_settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"check_for_updates": "Check for updates",
3030
"checking_for_updates": "Checking for updates",
3131
"choose": "Choose...",
32+
"choose_file": "Choose file",
3233
"choose_network_type": "Choose network type",
3334
"choose_reset_settings": "Choose reset settings",
3435
"clear_all_data": "Clear all data",
@@ -293,6 +294,9 @@
293294
"update_robot_software": "Update robot software manually with a local file (.zip)",
294295
"updating": "Updating",
295296
"update_requires_restarting_robot": "Updating the robot software requires restarting the robot",
297+
"upload_custom_logo_description": "Upload a logo for the robot to display during boot up. If no file is uploaded, we will display an anonymous logo.",
298+
"upload_custom_logo_dimensions": "The logo must fit within dimensions 1024 x 600 and be a PNG file (.png).",
299+
"upload_custom_logo": "Upload custom logo",
296300
"usage_settings": "Usage Settings",
297301
"usb": "USB",
298302
"usb_to_ethernet_description": "Looking for USB-to-Ethernet Adapter info?",
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as React from 'react'
2+
import { css } from 'styled-components'
3+
4+
import {
5+
ALIGN_CENTER,
6+
BORDERS,
7+
Btn,
8+
COLORS,
9+
DIRECTION_COLUMN,
10+
Flex,
11+
Icon,
12+
JUSTIFY_SPACE_BETWEEN,
13+
SPACING,
14+
StyledText,
15+
} from '@opentrons/components'
16+
17+
const FILE_UPLOAD_STYLE = css`
18+
&:hover > svg {
19+
background: ${COLORS.black90}${COLORS.opacity20HexCode};
20+
}
21+
&:active > svg {
22+
background: ${COLORS.black90}${COLORS.opacity20HexCode}};
23+
}
24+
`
25+
26+
interface FileUploadProps {
27+
file: File
28+
fileError: string | null
29+
handleClick: () => unknown
30+
}
31+
32+
export function FileUpload({
33+
file,
34+
fileError,
35+
handleClick,
36+
}: FileUploadProps): JSX.Element {
37+
return (
38+
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
39+
<Btn onClick={handleClick} aria-label="remove_file">
40+
<Flex
41+
alignItems={ALIGN_CENTER}
42+
backgroundColor={fileError == null ? COLORS.grey20 : COLORS.red30}
43+
borderRadius={BORDERS.borderRadius4}
44+
height={SPACING.spacing44}
45+
justifyContent={JUSTIFY_SPACE_BETWEEN}
46+
padding={SPACING.spacing8}
47+
css={FILE_UPLOAD_STYLE}
48+
>
49+
<StyledText as="p">{file.name}</StyledText>
50+
<Icon name="close" size="1.5rem" borderRadius="50%" />
51+
</Flex>
52+
</Btn>
53+
{fileError != null ? (
54+
<StyledText as="label" color={COLORS.red50}>
55+
{fileError}
56+
</StyledText>
57+
) : null}
58+
</Flex>
59+
)
60+
}

app/src/molecules/UploadInput/index.tsx

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,19 @@ const StyledInput = styled.input`
4545
export interface UploadInputProps {
4646
onUpload: (file: File) => unknown
4747
onClick?: () => void
48+
uploadButtonText?: string
4849
uploadText?: string | JSX.Element
4950
dragAndDropText?: string | JSX.Element
5051
}
5152

5253
export function UploadInput(props: UploadInputProps): JSX.Element | null {
54+
const {
55+
dragAndDropText,
56+
onClick,
57+
onUpload,
58+
uploadButtonText,
59+
uploadText,
60+
} = props
5361
const { t } = useTranslation('protocol_info')
5462

5563
const fileInput = React.useRef<HTMLInputElement>(null)
@@ -60,7 +68,7 @@ export function UploadInput(props: UploadInputProps): JSX.Element | null {
6068
const handleDrop: React.DragEventHandler<HTMLLabelElement> = e => {
6169
e.preventDefault()
6270
e.stopPropagation()
63-
Array.from(e.dataTransfer.files).forEach(f => props.onUpload(f))
71+
Array.from(e.dataTransfer.files).forEach(f => onUpload(f))
6472
setIsFileOverDropZone(false)
6573
}
6674
const handleDragEnter: React.DragEventHandler<HTMLLabelElement> = e => {
@@ -81,11 +89,11 @@ export function UploadInput(props: UploadInputProps): JSX.Element | null {
8189
}
8290

8391
const handleClick: React.MouseEventHandler<HTMLButtonElement> = _event => {
84-
props.onClick != null ? props.onClick() : fileInput.current?.click()
92+
onClick != null ? onClick() : fileInput.current?.click()
8593
}
8694

8795
const onChange: React.ChangeEventHandler<HTMLInputElement> = event => {
88-
;[...(event.target.files ?? [])].forEach(f => props.onUpload(f))
96+
;[...(event.target.files ?? [])].forEach(f => onUpload(f))
8997
if ('value' in event.currentTarget) event.currentTarget.value = ''
9098
}
9199

@@ -97,18 +105,20 @@ export function UploadInput(props: UploadInputProps): JSX.Element | null {
97105
alignItems={ALIGN_CENTER}
98106
gridGap={SPACING.spacing24}
99107
>
100-
<StyledText
101-
as="p"
102-
textAlign={TYPOGRAPHY.textAlignCenter}
103-
marginTop={SPACING.spacing16}
104-
>
105-
{props.uploadText}
106-
</StyledText>
108+
{uploadText != null ? (
109+
<StyledText
110+
as="p"
111+
textAlign={TYPOGRAPHY.textAlignCenter}
112+
marginTop={SPACING.spacing16}
113+
>
114+
{uploadText}
115+
</StyledText>
116+
) : null}
107117
<PrimaryButton
108118
onClick={handleClick}
109119
id="UploadInput_protocolUploadButton"
110120
>
111-
{t('upload')}
121+
{uploadButtonText ?? t('upload')}
112122
</PrimaryButton>
113123

114124
<StyledLabel
@@ -127,7 +137,7 @@ export function UploadInput(props: UploadInputProps): JSX.Element | null {
127137
name="upload"
128138
marginBottom={SPACING.spacing24}
129139
/>
130-
{props.dragAndDropText}
140+
{dragAndDropText}
131141
<StyledInput
132142
id="file_input"
133143
data-testid="file_input"

0 commit comments

Comments
 (0)