Skip to content

Commit 42e5f69

Browse files
authored
react-viewer: collapsible stream modules + motion enabled by default (realsenseai#15404)
2 parents 29e5ac3 + bfc90fd commit 42e5f69

3 files changed

Lines changed: 70 additions & 36 deletions

File tree

wrappers/rest-api/tools/react-viewer/src/components/DevicePanel.tsx

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -666,8 +666,16 @@ function SensorStreamControls({
666666
onStartSensorStreaming,
667667
onStopSensorStreaming,
668668
}: SensorStreamControlsProps) {
669+
// Each sensor module is collapsed by default; the play button stays visible.
670+
const [expandedSensors, setExpandedSensors] = useState<Set<string>>(new Set())
671+
const toggleSensor = (sensorId: string) => setExpandedSensors(prev => {
672+
const next = new Set(prev)
673+
next.has(sensorId) ? next.delete(sensorId) : next.add(sensorId)
674+
return next
675+
})
676+
669677
return (
670-
<div className="space-y-3">
678+
<div className="space-y-1.5">
671679
{sensors.map((sensor) => {
672680
const sensorStreamConfigs = streamsBySensor[sensor.sensor_id] || []
673681
if (sensorStreamConfigs.length === 0) return null
@@ -680,6 +688,7 @@ function SensorStreamControls({
680688
const sensorConfig = sensorConfigs[sensor.sensor_id]
681689

682690
const canStartSensor = hasEnabledSensorStreams && streamingMode !== 'pipeline'
691+
const isExpanded = expandedSensors.has(sensor.sensor_id)
683692

684693
const computeCommonOptions = () => {
685694
const profiles = sensor.supported_stream_profiles
@@ -706,15 +715,25 @@ function SensorStreamControls({
706715
const { resolutions: availableResolutions, fps: availableFps } = computeCommonOptions()
707716

708717
return (
709-
<div key={sensor.sensor_id} className="bg-gray-800/50 rounded-lg p-2">
710-
{/* Sensor header with per-sensor start button */}
711-
<div className="flex items-center justify-between mb-2">
712-
<div className="flex items-center gap-2">
713-
<span className="text-sm font-medium text-gray-300">{sensor.name}</span>
718+
<div key={sensor.sensor_id} className="bg-gray-800/50 rounded-lg px-2 py-1">
719+
{/* Sensor header: collapse toggle (name) on the left, start button always visible on the right */}
720+
<div className={`flex items-center justify-between ${isExpanded ? 'mb-2' : ''}`}>
721+
<button
722+
onClick={() => toggleSensor(sensor.sensor_id)}
723+
className="flex items-center gap-2 flex-1 min-w-0 text-left"
724+
aria-expanded={isExpanded}
725+
>
726+
<svg
727+
className={`w-3 h-3 shrink-0 text-gray-400 transition-transform ${isExpanded ? 'rotate-90' : ''}`}
728+
fill="none" stroke="currentColor" viewBox="0 0 24 24"
729+
>
730+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
731+
</svg>
732+
<span className="text-sm font-medium text-gray-300 truncate">{sensor.name}</span>
714733
{isSensorStreaming && (
715-
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
734+
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse shrink-0" />
716735
)}
717-
</div>
736+
</button>
718737
<button
719738
onClick={() => isSensorStreaming
720739
? onStopSensorStreaming(sensor.sensor_id)
@@ -744,6 +763,7 @@ function SensorStreamControls({
744763
</div>
745764
)}
746765

766+
{isExpanded && (<>
747767
{sensorConfig && !sensorConfig.isMotionSensor && (
748768
<div className="mb-2 flex items-center gap-2 text-xs">
749769
<div className="flex items-center gap-1">
@@ -794,6 +814,7 @@ function SensorStreamControls({
794814
/>
795815
))}
796816
</div>
817+
</>)}
797818
</div>
798819
)
799820
})}
@@ -987,22 +1008,20 @@ function SensorOptionsPanel({ sensor, options, searchQuery, isExpanded, onToggle
9871008
onClick={onToggle}
9881009
className="w-full flex items-center justify-between p-1.5 bg-gray-800/50 rounded hover:bg-gray-700 transition-colors text-xs"
9891010
>
990-
<span className="font-medium">{sensor.name}</span>
991-
<div className="flex items-center gap-1">
992-
{modifiedCount > 0 && (
993-
<span className="px-1.5 py-0.5 bg-rs-blue/20 text-rs-blue rounded text-[10px]">
994-
{modifiedCount} modified
995-
</span>
996-
)}
1011+
<span className="flex items-center gap-1.5 font-medium min-w-0">
9971012
<svg
998-
className={`w-4 h-4 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
999-
fill="none"
1000-
stroke="currentColor"
1001-
viewBox="0 0 24 24"
1013+
className={`w-3.5 h-3.5 shrink-0 text-gray-400 transition-transform ${isExpanded ? 'rotate-90' : ''}`}
1014+
fill="none" stroke="currentColor" viewBox="0 0 24 24"
10021015
>
1003-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
1016+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
10041017
</svg>
1005-
</div>
1018+
<span className="truncate">{sensor.name}</span>
1019+
</span>
1020+
{modifiedCount > 0 && (
1021+
<span className="px-1.5 py-0.5 bg-rs-blue/20 text-rs-blue rounded text-[10px]">
1022+
{modifiedCount} modified
1023+
</span>
1024+
)}
10061025
</button>
10071026

10081027
{isExpanded && (
@@ -1086,17 +1105,15 @@ function CategorySection({ category, options, searchQuery, isExpanded, onToggle,
10861105
<div className="flex items-center bg-gray-750 hover:bg-gray-700 transition-colors">
10871106
<button
10881107
onClick={onToggle}
1089-
className="flex-1 flex items-center justify-between p-1.5"
1108+
className="flex-1 flex items-center gap-1.5 p-1.5"
10901109
>
1091-
<span className="text-xs font-medium text-gray-300">{category}</span>
10921110
<svg
1093-
className={`w-3 h-3 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
1094-
fill="none"
1095-
stroke="currentColor"
1096-
viewBox="0 0 24 24"
1111+
className={`w-3 h-3 shrink-0 text-gray-400 transition-transform ${isExpanded ? 'rotate-90' : ''}`}
1112+
fill="none" stroke="currentColor" viewBox="0 0 24 24"
10971113
>
1098-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
1114+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
10991115
</svg>
1116+
<span className="text-xs font-medium text-gray-300">{category}</span>
11001117
</button>
11011118
{hasModifiedOptions && (
11021119
<button
@@ -1177,17 +1194,15 @@ function PostProcessingSection({ options, searchQuery, isExpanded, onToggle, onR
11771194
<div className="flex items-center bg-gray-750 hover:bg-gray-700 transition-colors">
11781195
<button
11791196
onClick={onToggle}
1180-
className="flex-1 flex items-center justify-between p-1.5"
1197+
className="flex-1 flex items-center gap-1.5 p-1.5"
11811198
>
1182-
<span className="text-xs font-medium text-gray-300">Post-Processing</span>
11831199
<svg
1184-
className={`w-3 h-3 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
1185-
fill="none"
1186-
stroke="currentColor"
1187-
viewBox="0 0 24 24"
1200+
className={`w-3 h-3 shrink-0 text-gray-400 transition-transform ${isExpanded ? 'rotate-90' : ''}`}
1201+
fill="none" stroke="currentColor" viewBox="0 0 24 24"
11881202
>
1189-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
1203+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
11901204
</svg>
1205+
<span className="text-xs font-medium text-gray-300">Post-Processing</span>
11911206
</button>
11921207
{hasModifiedOptions && (
11931208
<button

wrappers/rest-api/tools/react-viewer/src/store/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ function buildStreamConfigs(sensors: SensorInfo[]): StreamConfig[] {
4040
)
4141
for (const profile of profiles) {
4242
const streamTypeLower = profile.stream_type.toLowerCase()
43-
const enableByDefault = streamTypeLower === 'depth' || streamTypeLower === 'color'
43+
const enableByDefault =
44+
streamTypeLower === 'depth' || streamTypeLower === 'color' ||
45+
streamTypeLower === 'gyro' || streamTypeLower === 'accel'
4446
configs.push({
4547
sensor_id: sensor.sensor_id,
4648
stream_type: profile.stream_type,

wrappers/rest-api/tools/react-viewer/tests/e2e/real-device.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@
1414
*/
1515

1616
import { test, expect, getTestMode, getApiUrl, dismissWhatsNewModal } from './fixtures'
17+
import type { Locator } from '@playwright/test'
18+
19+
// Per-stream toggles only render inside an expanded sensor module
20+
async function expandSensorModules(deviceCard: Locator) {
21+
const headers = deviceCard.locator('button[aria-expanded]')
22+
for (let i = 0; i < await headers.count(); i++) {
23+
const header = headers.nth(i)
24+
if (await header.getAttribute('aria-expanded') === 'false') await header.click()
25+
}
26+
}
1727

1828
// Skip entire file in mock mode
1929
test.beforeEach(async ({ testMode, page }) => {
@@ -72,6 +82,9 @@ test.describe('@real-device Real Device Tests', () => {
7282
// Wait for device to finish loading sensors
7383
await expect(page.locator('[title="Loading..."]')).not.toBeVisible({ timeout: 10000 })
7484

85+
// Sensor modules are collapsed by default; expand them to reveal the stream toggles
86+
await expandSensorModules(deviceCard)
87+
7588
// Enable depth stream
7689
const depthToggle = page.locator('[data-testid="toggle-stream-depth"]').first()
7790
await depthToggle.check()
@@ -102,6 +115,8 @@ test.describe('@real-device Real Device Tests', () => {
102115
// Wait for device to finish loading sensors
103116
await expect(page.locator('[title="Loading..."]')).not.toBeVisible({ timeout: 10000 })
104117

118+
await expandSensorModules(deviceCard)
119+
105120
const depthToggle = page.locator('[data-testid="toggle-stream-depth"]').first()
106121
await depthToggle.check()
107122

@@ -196,6 +211,8 @@ test.describe('@real-device Performance Tests', () => {
196211
// Wait for device to finish loading sensors
197212
await expect(page.locator('[title="Loading..."]')).not.toBeVisible({ timeout: 10000 })
198213

214+
await expandSensorModules(deviceCard)
215+
199216
const depthToggle = page.locator('[data-testid="toggle-stream-depth"]').first()
200217
await depthToggle.check()
201218

0 commit comments

Comments
 (0)