Skip to content

Commit dee9575

Browse files
authored
Add input source modal (#4615)
1 parent 027eca1 commit dee9575

File tree

10 files changed

+130
-122
lines changed

10 files changed

+130
-122
lines changed

ui/src/assets/icons/camera-off.svg

Lines changed: 6 additions & 0 deletions
Loading

ui/src/assets/icons/camera.svg

Lines changed: 5 additions & 0 deletions
Loading

ui/src/components/radio-disclosure-group/radio-disclosure-group.module.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
.disclosure {
77
border: var(--spectrum-global-dimension-size-40) solid var(--spectrum-global-color-gray-200);
88
border-radius: var(--spectrum-global-dimension-size-50);
9-
109
background-color: var(--spectrum-global-color-gray-75);
1110
}
1211

ui/src/components/radio-disclosure-group/radio-disclosure-group.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const RadioDisclosure = <ValueType extends string>({
3232
aria-label={ariaLabel}
3333
value={value}
3434
>
35-
<Flex direction='column' gap='size-200' minWidth={'size-6000'}>
35+
<Flex direction='column' gap='size-200'>
3636
{items.map((item) => {
3737
return (
3838
<Disclosure
@@ -42,7 +42,7 @@ export const RadioDisclosure = <ValueType extends string>({
4242
UNSAFE_className={classes.disclosure}
4343
>
4444
<DisclosureTitle UNSAFE_className={classes.disclosureTitle}>
45-
<View padding='size-200'>
45+
<View>
4646
<Radio value={item.value} UNSAFE_className={classes.radio}>
4747
<Flex alignItems='center' gap='size-200'>
4848
{item.label}
@@ -51,7 +51,7 @@ export const RadioDisclosure = <ValueType extends string>({
5151
</View>
5252
</DisclosureTitle>
5353
<DisclosurePanel UNSAFE_className={classes.disclosurePanel}>
54-
<View padding='size-200'>{item.content}</View>
54+
<View>{item.content}</View>
5555
</DisclosurePanel>
5656
</Disclosure>
5757
);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { Button, Content, Dialog, DialogTrigger, Heading, Text } from '@geti/ui';
5+
6+
import { ReactComponent as Camera } from '../../../assets/icons/camera.svg';
7+
import { ConnectionPreview, Source } from './source';
8+
9+
export const SourceModal = () => {
10+
return (
11+
<DialogTrigger>
12+
<Button width={'size-2000'} variant={'secondary'}>
13+
<Text>Input source</Text>
14+
<Camera fill='white' />
15+
</Button>
16+
{(_close) => (
17+
<Dialog>
18+
<Heading>
19+
<ConnectionPreview />
20+
</Heading>
21+
<Content marginTop={'size-200'}>
22+
<Source />
23+
</Content>
24+
</Dialog>
25+
)}
26+
</DialogTrigger>
27+
);
28+
};

ui/src/routes/project/source.tsx renamed to ui/src/features/inference/source/source.tsx

Lines changed: 81 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
11
// Copyright (C) 2025 Intel Corporation
22
// SPDX-License-Identifier: Apache-2.0
33

4-
import { FormEvent, useState } from 'react';
4+
import { useState } from 'react';
55

6-
import { Button, ButtonGroup, Divider, Flex, Form, Grid, Loading, NumberField, Text, TextField, View } from '@geti/ui';
7-
import { isEqual } from 'lodash-es';
6+
import { Button, ButtonGroup, Divider, Flex, Loading, NumberField, Text, TextField } from '@geti/ui';
87

9-
import { $api } from '../../api/client';
108
import {
119
SchemaDisconnectedSourceConfig,
1210
SchemaImagesFolderSourceConfig,
1311
SchemaIpCameraSourceConfig,
1412
SchemaVideoFileSourceConfig,
1513
SchemaWebcamSourceConfig,
16-
} from '../../api/openapi-spec';
17-
import { RadioDisclosure } from '../../components/radio-disclosure-group/radio-disclosure-group';
18-
import { Stream } from '../../features/inference/stream/stream';
19-
import { useWebRTCConnection } from '../../features/inference/stream/web-rtc-connection-provider';
20-
import { ReactComponent as Image } from './../../assets/icons/images-folder.svg';
21-
import { ReactComponent as IpCamera } from './../../assets/icons/ip-camera.svg';
22-
import { ReactComponent as Video } from './../../assets/icons/video-file.svg';
23-
import { ReactComponent as Webcam } from './../../assets/icons/webcam.svg';
14+
} from '../../../api/openapi-spec';
15+
import { ReactComponent as CameraOff } from '../../../assets/icons/camera-off.svg';
16+
import { ReactComponent as Image } from '../../../assets/icons/images-folder.svg';
17+
import { ReactComponent as IpCamera } from '../../../assets/icons/ip-camera.svg';
18+
import { ReactComponent as Video } from '../../../assets/icons/video-file.svg';
19+
import { ReactComponent as Webcam } from '../../../assets/icons/webcam.svg';
20+
import { RadioDisclosure } from '../../../components/radio-disclosure-group/radio-disclosure-group';
21+
import { Stream } from '../stream/stream';
22+
import { useWebRTCConnection } from '../stream/web-rtc-connection-provider';
2423

25-
// TODO: create a new module scss for this file
26-
const classes = { canvasContainer: '' };
24+
import classes from '../inference.module.scss';
2725

2826
type SourceConfig =
2927
| SchemaDisconnectedSourceConfig
@@ -68,35 +66,42 @@ const DEFAULT_SOURCE_FORMS: SourceFormRecord = {
6866
},
6967
};
7068

71-
const ConnectionPreview = () => {
69+
export const ConnectionPreview = () => {
7270
const [size, setSize] = useState({ height: 608, width: 892 });
7371
const { status } = useWebRTCConnection();
7472

7573
return (
76-
<>
74+
<Flex
75+
alignItems={'center'}
76+
justifyContent={'center'}
77+
width={'100%'}
78+
height={'size-3000'}
79+
UNSAFE_style={{
80+
backgroundColor: 'var(--spectrum-global-color-gray-200)',
81+
}}
82+
>
7783
{status === 'idle' && (
7884
<div className={classes.canvasContainer}>
79-
<View backgroundColor={'gray-200'} width='100%' height='100%'>
80-
<Flex alignItems={'center'} justifyContent={'center'} height='100%'>
81-
<Text
82-
UNSAFE_style={{
83-
color: 'var(--spectrum-global-color-gray-800)',
84-
}}
85-
>
86-
Save camera settings to establish a connection and view the preview.
87-
</Text>
88-
</Flex>
89-
</View>
85+
<Flex direction={'column'} justifyContent={'center'} alignItems={'center'} gap={'size-200'}>
86+
<CameraOff />
87+
<Text
88+
UNSAFE_style={{
89+
color: 'var(--spectrum-global-color-gray-700)',
90+
fontSize: 'var(--spectrum-global-dimension-font-size-75)',
91+
lineHeight: 'var(--spectrum-global-dimension-size-225)',
92+
}}
93+
>
94+
Configure your input source
95+
</Text>
96+
</Flex>
9097
</div>
9198
)}
9299

93100
{status === 'connecting' && (
94101
<div className={classes.canvasContainer}>
95-
<View backgroundColor={'gray-200'} width='100%' height='100%'>
96-
<Flex alignItems={'center'} justifyContent={'center'} height='100%'>
97-
<Loading mode='inline' />
98-
</Flex>
99-
</View>
102+
<Flex alignItems={'center'} justifyContent={'center'} height='100%'>
103+
<Loading mode='inline' />
104+
</Flex>
100105
</div>
101106
)}
102107

@@ -105,7 +110,7 @@ const ConnectionPreview = () => {
105110
<Stream size={size} setSize={setSize} />
106111
</div>
107112
)}
108-
</>
113+
</Flex>
109114
);
110115
};
111116

@@ -208,14 +213,21 @@ const ConfigureSource = ({
208213

209214
const Label = ({ item }: { item: { name: string; source_type: SourceType } }) => {
210215
return (
211-
<Flex alignItems='center' gap='size-200'>
216+
<Flex alignItems='center' gap='size-200' margin={0}>
212217
<Flex alignItems='center' justifyContent={'center'}>
213218
{item.source_type === 'video_file' && <Video width='32px' />}
214219
{item.source_type === 'webcam' && <Webcam width='32px' />}
215220
{item.source_type === 'ip_camera' && <IpCamera width='32px' />}
216221
{item.source_type === 'images_folder' && <Image width='32px' />}
217222
</Flex>
218-
{item.name}
223+
<Text
224+
UNSAFE_style={{
225+
fontSize: 'var(--spectrum-global-dimension-font-size-100)',
226+
lineHeight: 'var(--spectrum-global-dimension-size-300)',
227+
}}
228+
>
229+
{item.name}
230+
</Text>
219231
</Flex>
220232
);
221233
};
@@ -229,59 +241,29 @@ const DEFAULT_SOURCE_ITEMS = [
229241
] satisfies Array<{ source_type: SourceType; name: string }>;
230242

231243
export const Source = () => {
232-
const { status } = useWebRTCConnection();
233-
const sources = $api.useSuspenseQuery('get', '/api/sources');
234-
const sourceMutation = $api.useMutation('post', '/api/sources', {
235-
onSuccess: async () => {
236-
// TODO: Enable this once WebRTC connection works properly
237-
// if (status !== 'connected') {
238-
// await start();
239-
// }
240-
},
241-
});
242-
243-
const [selectedSourceType, setSelectedSourceType] = useState<SourceType>(
244-
sources.data[0]?.source_type ?? DEFAULT_SOURCE_ITEMS[0].source_type
245-
);
244+
const [selectedSourceType, setSelectedSourceType] = useState<SourceType>(DEFAULT_SOURCE_ITEMS[0].source_type);
246245
const [forms, setForms] = useState<SourceFormRecord>(() => {
247246
return DEFAULT_SOURCE_FORMS;
248247
});
249248

250-
const submitIsDisabled = isEqual(forms[selectedSourceType], sources.data);
251-
const onSubmit = (event: FormEvent<HTMLFormElement>) => {
252-
event.preventDefault();
253-
254-
sourceMutation.mutateAsync({ body: forms[selectedSourceType] });
249+
const handleSaveSource = (sourceType: SourceType) => {
250+
setSelectedSourceType(sourceType);
255251
};
256252

253+
const handleSubmit = () => {};
254+
257255
return (
258-
<Grid
259-
areas={['text text', 'form canvas', 'divider divider', 'buttons buttons']}
260-
columns={['auto', '1fr']}
261-
gap='size-200'
262-
>
263-
<View
264-
gridArea='text'
265-
UNSAFE_style={{
266-
color: 'var(--spectrum-global-color-gray-700)',
267-
textAlign: 'center',
268-
}}
269-
>
270-
<Text>
271-
Please configure the source for your system. Select the appropriate source type and provide the
272-
necessary connection details below.
273-
</Text>
274-
</View>
275-
<Form gridArea={'form'} onSubmit={onSubmit}>
276-
<RadioDisclosure
277-
ariaLabel={'Select your source'}
278-
value={selectedSourceType}
279-
setValue={setSelectedSourceType}
280-
items={DEFAULT_SOURCE_ITEMS.map((item) => {
281-
return {
282-
value: item.source_type,
283-
label: <Label item={item} />,
284-
content: (
256+
<Flex direction={'column'} gap={'size-200'}>
257+
<RadioDisclosure
258+
ariaLabel={'Select your source'}
259+
value={selectedSourceType}
260+
setValue={setSelectedSourceType}
261+
items={DEFAULT_SOURCE_ITEMS.map((item) => {
262+
return {
263+
value: item.source_type,
264+
label: <Label item={item} />,
265+
content: (
266+
<Flex direction={'column'} gap={'size-200'}>
285267
<ConfigureSource
286268
source={forms[item.source_type]}
287269
setSource={(newSource) => {
@@ -290,34 +272,24 @@ export const Source = () => {
290272
});
291273
}}
292274
/>
293-
),
294-
};
295-
})}
296-
/>
275+
<ButtonGroup>
276+
<Button variant={'accent'} onPress={() => handleSaveSource(item.source_type)}>
277+
Save & connect
278+
</Button>
279+
</ButtonGroup>
280+
</Flex>
281+
),
282+
};
283+
})}
284+
/>
297285

298-
<ButtonGroup marginTop={'size-400'} marginX='size-200'>
299-
<Button
300-
type='submit'
301-
variant='accent'
302-
isPending={sourceMutation.isPending}
303-
// TODO: disable only if there are no changes
304-
isDisabled={submitIsDisabled && status === 'connected'}
305-
>
306-
Save
307-
</Button>
308-
</ButtonGroup>
309-
</Form>
310-
<View gridArea={'canvas'} height={'100%'} UNSAFE_style={{ backgroundColor: 'rgba(0, 0, 0, 0.20)' }}>
311-
<ConnectionPreview />
312-
</View>
313-
<View gridArea={'divider'} paddingY='size-400'>
314-
<Divider size='S' orientation='horizontal' />
315-
</View>
316-
<View gridArea='buttons'>
317-
<ButtonGroup align={'end'} width={'100%'}>
318-
<Button variant='accent'>Next</Button>
319-
</ButtonGroup>
320-
</View>
321-
</Grid>
286+
<Divider size={'S'} />
287+
288+
<ButtonGroup alignSelf={'end'}>
289+
<Button variant={'primary'} onPress={handleSubmit}>
290+
Submit
291+
</Button>
292+
</ButtonGroup>
293+
</Flex>
322294
);
323295
};

ui/src/features/inference/stream-container.tsx renamed to ui/src/features/inference/stream/stream-container.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { useEffect, useState } from 'react';
66
import { Button, Flex, Loading, toast, View } from '@geti/ui';
77
import { Play } from '@geti/ui/icons';
88

9-
import { Stream } from './stream/stream';
10-
import { useWebRTCConnection } from './stream/web-rtc-connection-provider';
9+
import { Stream } from './stream';
10+
import { useWebRTCConnection } from './web-rtc-connection-provider';
1111

12-
import classes from './inference.module.css';
12+
import classes from '../inference.module.scss';
1313

1414
export const StreamContainer = () => {
1515
const [size, setSize] = useState({ height: 608, width: 892 });

ui/src/features/inference/toolbar.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { StatusLight } from '@adobe/react-spectrum';
77
import { Button, Divider, Flex, Text, View } from '@geti/ui';
88

99
import { $api } from '../../api/client';
10-
import { paths } from '../../router';
10+
import { SourceModal } from './source/source-modal';
1111
import { useWebRTCConnection } from './stream/web-rtc-connection-provider';
1212

1313
const ActiveModel = () => {
@@ -111,10 +111,8 @@ export const Toolbar = () => {
111111

112112
<Divider orientation='vertical' size='S' />
113113

114-
<Flex marginStart='auto' gap='size-100'>
115-
<Button href={paths.project.index({})} variant='secondary'>
116-
View project
117-
</Button>
114+
<Flex marginStart='auto'>
115+
<SourceModal />
118116
</Flex>
119117
</Flex>
120118
</View>

ui/src/routes/inference/inference.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { Grid } from '@geti/ui';
55

66
import { Aside } from '../../features/inference/aside';
7-
import { StreamContainer } from '../../features/inference/stream-container';
7+
import { StreamContainer } from '../../features/inference/stream/stream-container';
88
import { Toolbar } from '../../features/inference/toolbar';
99

1010
export const Inference = () => {

0 commit comments

Comments
 (0)