@wandelbots/wandelbots-js-react-components 2.34.2 → 2.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AppHeader.d.ts +34 -0
- package/dist/components/AppHeader.d.ts.map +1 -0
- package/dist/components/CycleTimer.d.ts +33 -16
- package/dist/components/CycleTimer.d.ts.map +1 -1
- package/dist/components/DataGrid.d.ts +66 -0
- package/dist/components/DataGrid.d.ts.map +1 -0
- package/dist/components/LogPanel.d.ts +38 -0
- package/dist/components/LogPanel.d.ts.map +1 -0
- package/dist/components/LogStore.d.ts +12 -0
- package/dist/components/LogStore.d.ts.map +1 -0
- package/dist/components/LogViewer.d.ts +46 -0
- package/dist/components/LogViewer.d.ts.map +1 -0
- package/dist/components/ProgramControl.d.ts +8 -2
- package/dist/components/ProgramControl.d.ts.map +1 -1
- package/dist/components/ProgramStateIndicator.d.ts +1 -1
- package/dist/components/ProgramStateIndicator.d.ts.map +1 -1
- package/dist/components/RobotCard.d.ts +103 -0
- package/dist/components/RobotCard.d.ts.map +1 -0
- package/dist/components/RobotListItem.d.ts +34 -0
- package/dist/components/RobotListItem.d.ts.map +1 -0
- package/dist/components/RobotSetupReadinessIndicator.d.ts +31 -0
- package/dist/components/RobotSetupReadinessIndicator.d.ts.map +1 -0
- package/dist/components/RobotSetupReadinessIndicator.test.d.ts +2 -0
- package/dist/components/RobotSetupReadinessIndicator.test.d.ts.map +1 -0
- package/dist/components/TabBar.d.ts +30 -0
- package/dist/components/TabBar.d.ts.map +1 -0
- package/dist/components/robots/Robot.d.ts +3 -2
- package/dist/components/robots/Robot.d.ts.map +1 -1
- package/dist/components/robots/manufacturerHomePositions.d.ts +21 -0
- package/dist/components/robots/manufacturerHomePositions.d.ts.map +1 -0
- package/dist/icons/DropdownArrowIcon.d.ts +3 -0
- package/dist/icons/DropdownArrowIcon.d.ts.map +1 -0
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.d.ts.map +1 -1
- package/dist/index.cjs +50 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11332 -9473
- package/dist/index.js.map +1 -1
- package/dist/themes/createDarkTheme.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/components/AppHeader.md +84 -0
- package/src/components/AppHeader.tsx +199 -0
- package/src/components/CycleTimer.tsx +384 -159
- package/src/components/DataGrid.tsx +659 -0
- package/src/components/LogPanel.tsx +69 -0
- package/src/components/LogStore.ts +44 -0
- package/src/components/LogViewer.tsx +370 -0
- package/src/components/ProgramControl.tsx +27 -12
- package/src/components/ProgramStateIndicator.tsx +25 -8
- package/src/components/RobotCard.tsx +568 -0
- package/src/components/RobotListItem.tsx +150 -0
- package/src/components/RobotSetupReadinessIndicator.test.tsx +60 -0
- package/src/components/RobotSetupReadinessIndicator.tsx +124 -0
- package/src/components/TabBar.tsx +144 -0
- package/src/components/robots/Robot.tsx +5 -2
- package/src/components/robots/manufacturerHomePositions.ts +76 -0
- package/src/i18n/locales/de/translations.json +8 -1
- package/src/i18n/locales/en/translations.json +8 -1
- package/src/icons/DropdownArrowIcon.tsx +13 -0
- package/src/icons/chevronDown.svg +3 -0
- package/src/icons/index.ts +1 -0
- package/src/index.ts +14 -0
- package/src/themes/createDarkTheme.ts +75 -1
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import { Box, Button, Card, Divider, Typography, useTheme } from "@mui/material"
|
|
2
|
+
import { Bounds } from "@react-three/drei"
|
|
3
|
+
import { Canvas } from "@react-three/fiber"
|
|
4
|
+
import type {
|
|
5
|
+
ConnectedMotionGroup,
|
|
6
|
+
RobotControllerStateOperationModeEnum,
|
|
7
|
+
RobotControllerStateSafetyStateEnum,
|
|
8
|
+
} from "@wandelbots/nova-js/v1"
|
|
9
|
+
import { observer } from "mobx-react-lite"
|
|
10
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
11
|
+
import { useTranslation } from "react-i18next"
|
|
12
|
+
import type { Group } from "three"
|
|
13
|
+
import { externalizeComponent } from "../externalizeComponent"
|
|
14
|
+
import { PresetEnvironment } from "./3d-viewport/PresetEnvironment"
|
|
15
|
+
import { CycleTimer } from "./CycleTimer"
|
|
16
|
+
import type { ProgramState } from "./ProgramControl"
|
|
17
|
+
import { ProgramStateIndicator } from "./ProgramStateIndicator"
|
|
18
|
+
import { Robot } from "./robots/Robot"
|
|
19
|
+
|
|
20
|
+
export interface RobotCardProps {
|
|
21
|
+
/** Name of the robot displayed at the top */
|
|
22
|
+
robotName: string
|
|
23
|
+
/** Current program state */
|
|
24
|
+
programState: ProgramState
|
|
25
|
+
/** Current safety state of the robot controller */
|
|
26
|
+
safetyState: RobotControllerStateSafetyStateEnum
|
|
27
|
+
/** Current operation mode of the robot controller */
|
|
28
|
+
operationMode: RobotControllerStateOperationModeEnum
|
|
29
|
+
/** Whether the "Drive to Home" button should be enabled */
|
|
30
|
+
driveToHomeEnabled?: boolean
|
|
31
|
+
/** Callback fired when "Drive to Home" button is pressed */
|
|
32
|
+
onDriveToHomePress?: () => void
|
|
33
|
+
/** Callback fired when "Drive to Home" button is released */
|
|
34
|
+
onDriveToHomeRelease?: () => void
|
|
35
|
+
/**
|
|
36
|
+
* Callback fired when "Drive to Home" button is pressed, with the default home position.
|
|
37
|
+
* If provided, this will be called instead of onDriveToHomePress, providing the recommended
|
|
38
|
+
* home position joint configuration based on the robot manufacturer.
|
|
39
|
+
*/
|
|
40
|
+
onDriveToHomePressWithConfig?: (homePosition: number[]) => void
|
|
41
|
+
/**
|
|
42
|
+
* Callback fired when "Drive to Home" button is released after using onDriveToHomePressWithConfig.
|
|
43
|
+
* If provided, this will be called instead of onDriveToHomeRelease.
|
|
44
|
+
*/
|
|
45
|
+
onDriveToHomeReleaseWithConfig?: () => void
|
|
46
|
+
/**
|
|
47
|
+
* Custom default joint configuration to use if manufacturer-based defaults are not available.
|
|
48
|
+
* Joint values should be in radians.
|
|
49
|
+
*/
|
|
50
|
+
defaultJointConfig?: number[]
|
|
51
|
+
/** Connected motion group for the robot */
|
|
52
|
+
connectedMotionGroup: ConnectedMotionGroup
|
|
53
|
+
/** Custom robot component to render (optional, defaults to Robot) */
|
|
54
|
+
robotComponent?: React.ComponentType<{
|
|
55
|
+
connectedMotionGroup: ConnectedMotionGroup
|
|
56
|
+
flangeRef?: React.Ref<Group>
|
|
57
|
+
postModelRender?: () => void
|
|
58
|
+
transparentColor?: string
|
|
59
|
+
getModel?: (modelFromController: string) => string
|
|
60
|
+
}>
|
|
61
|
+
/** Custom cycle timer component (optional, defaults to CycleTimer) */
|
|
62
|
+
cycleTimerComponent?: React.ComponentType<{
|
|
63
|
+
variant?: "default" | "small"
|
|
64
|
+
compact?: boolean
|
|
65
|
+
onCycleComplete: (controls: {
|
|
66
|
+
startNewCycle: (maxTimeSeconds?: number, elapsedSeconds?: number) => void
|
|
67
|
+
pause: () => void
|
|
68
|
+
resume: () => void
|
|
69
|
+
isPaused: () => boolean
|
|
70
|
+
}) => void
|
|
71
|
+
onCycleEnd?: () => void
|
|
72
|
+
autoStart?: boolean
|
|
73
|
+
hasError?: boolean
|
|
74
|
+
className?: string
|
|
75
|
+
}>
|
|
76
|
+
/** Callback to receive cycle timer controls for external timer management */
|
|
77
|
+
onCycleTimerReady?: (controls: {
|
|
78
|
+
startNewCycle: (maxTimeSeconds?: number, elapsedSeconds?: number) => void
|
|
79
|
+
pause: () => void
|
|
80
|
+
resume: () => void
|
|
81
|
+
isPaused: () => boolean
|
|
82
|
+
}) => void
|
|
83
|
+
/** Callback fired when a cycle completes (reaches zero) */
|
|
84
|
+
onCycleEnd?: () => void
|
|
85
|
+
/** Whether the cycle timer should auto-start when a new cycle is set */
|
|
86
|
+
cycleTimerAutoStart?: boolean
|
|
87
|
+
/** Whether the cycle timer is in an error state (pauses timer and shows error styling) */
|
|
88
|
+
cycleTimerHasError?: boolean
|
|
89
|
+
/** Additional CSS class name */
|
|
90
|
+
className?: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A responsive card component that displays a 3D robot with states and controls.
|
|
95
|
+
* The card automatically adapts to its container's size and aspect ratio.
|
|
96
|
+
*
|
|
97
|
+
* Features:
|
|
98
|
+
* - Fully responsive Material-UI Card that adapts to container dimensions
|
|
99
|
+
* - Automatic layout switching based on aspect ratio:
|
|
100
|
+
* - Portrait mode: Vertical layout with robot in center
|
|
101
|
+
* - Landscape mode: Horizontal layout with robot on left, content on right (left-aligned)
|
|
102
|
+
* - Responsive 3D robot rendering:
|
|
103
|
+
* - Scales dynamically with container size
|
|
104
|
+
* - Hides at very small sizes to preserve usability
|
|
105
|
+
* - Adaptive margin based on available space
|
|
106
|
+
* - Smart spacing and padding that reduces at smaller sizes
|
|
107
|
+
* - Minimum size constraints for usability while maximizing content density
|
|
108
|
+
* - Robot name displayed in Typography h6 at top-left
|
|
109
|
+
* - Program state indicator below the name
|
|
110
|
+
* - Auto-fitting 3D robot model that scales with container size
|
|
111
|
+
* - Compact cycle time component with small variant, error state, and count-up/count-down mode support
|
|
112
|
+
* - Transparent gray divider line
|
|
113
|
+
* - "Drive to Home" button with press-and-hold functionality
|
|
114
|
+
* - Localization support via react-i18next
|
|
115
|
+
* - Material-UI theming integration
|
|
116
|
+
*/
|
|
117
|
+
export const RobotCard = externalizeComponent(
|
|
118
|
+
observer(
|
|
119
|
+
({
|
|
120
|
+
robotName,
|
|
121
|
+
programState,
|
|
122
|
+
safetyState,
|
|
123
|
+
operationMode,
|
|
124
|
+
driveToHomeEnabled = false,
|
|
125
|
+
onDriveToHomePress,
|
|
126
|
+
onDriveToHomeRelease,
|
|
127
|
+
connectedMotionGroup,
|
|
128
|
+
robotComponent: RobotComponent = Robot,
|
|
129
|
+
cycleTimerComponent: CycleTimerComponent = CycleTimer,
|
|
130
|
+
onCycleTimerReady,
|
|
131
|
+
onCycleEnd,
|
|
132
|
+
cycleTimerAutoStart = true,
|
|
133
|
+
cycleTimerHasError = false,
|
|
134
|
+
className,
|
|
135
|
+
}: RobotCardProps) => {
|
|
136
|
+
const theme = useTheme()
|
|
137
|
+
const { t } = useTranslation()
|
|
138
|
+
const [isDriveToHomePressed, setIsDriveToHomePressed] = useState(false)
|
|
139
|
+
const driveButtonRef = useRef<HTMLButtonElement>(null)
|
|
140
|
+
const cardRef = useRef<HTMLDivElement>(null)
|
|
141
|
+
const [isLandscape, setIsLandscape] = useState(false)
|
|
142
|
+
const [cardSize, setCardSize] = useState<{
|
|
143
|
+
width: number
|
|
144
|
+
height: number
|
|
145
|
+
}>({ width: 400, height: 600 })
|
|
146
|
+
const [modelRenderTrigger, setModelRenderTrigger] = useState(0)
|
|
147
|
+
|
|
148
|
+
// Store cycle timer controls for external control
|
|
149
|
+
const cycleControlsRef = useRef<{
|
|
150
|
+
startNewCycle: (
|
|
151
|
+
maxTimeSeconds?: number,
|
|
152
|
+
elapsedSeconds?: number,
|
|
153
|
+
) => void
|
|
154
|
+
pause: () => void
|
|
155
|
+
resume: () => void
|
|
156
|
+
isPaused: () => boolean
|
|
157
|
+
} | null>(null)
|
|
158
|
+
|
|
159
|
+
// Hook to detect aspect ratio and size changes
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
const checkDimensions = () => {
|
|
162
|
+
if (cardRef.current) {
|
|
163
|
+
const { offsetWidth, offsetHeight } = cardRef.current
|
|
164
|
+
setIsLandscape(offsetWidth > offsetHeight)
|
|
165
|
+
setCardSize({ width: offsetWidth, height: offsetHeight })
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Initial check
|
|
170
|
+
checkDimensions()
|
|
171
|
+
|
|
172
|
+
// Set up ResizeObserver to watch for size changes
|
|
173
|
+
const resizeObserver = new ResizeObserver(checkDimensions)
|
|
174
|
+
if (cardRef.current) {
|
|
175
|
+
resizeObserver.observe(cardRef.current)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return () => {
|
|
179
|
+
resizeObserver.disconnect()
|
|
180
|
+
}
|
|
181
|
+
}, [])
|
|
182
|
+
|
|
183
|
+
const handleModelRender = useCallback(() => {
|
|
184
|
+
// Trigger bounds refresh when model renders
|
|
185
|
+
setModelRenderTrigger((prev) => prev + 1)
|
|
186
|
+
}, [])
|
|
187
|
+
|
|
188
|
+
const handleDriveToHomeMouseDown = useCallback(() => {
|
|
189
|
+
if (!driveToHomeEnabled || !onDriveToHomePress) return
|
|
190
|
+
setIsDriveToHomePressed(true)
|
|
191
|
+
onDriveToHomePress()
|
|
192
|
+
}, [driveToHomeEnabled, onDriveToHomePress])
|
|
193
|
+
|
|
194
|
+
const handleDriveToHomeMouseUp = useCallback(() => {
|
|
195
|
+
if (!driveToHomeEnabled || !onDriveToHomeRelease) return
|
|
196
|
+
setIsDriveToHomePressed(false)
|
|
197
|
+
onDriveToHomeRelease()
|
|
198
|
+
}, [driveToHomeEnabled, onDriveToHomeRelease])
|
|
199
|
+
|
|
200
|
+
const handleDriveToHomeMouseLeave = useCallback(() => {
|
|
201
|
+
if (isDriveToHomePressed && onDriveToHomeRelease) {
|
|
202
|
+
setIsDriveToHomePressed(false)
|
|
203
|
+
onDriveToHomeRelease()
|
|
204
|
+
}
|
|
205
|
+
}, [isDriveToHomePressed, onDriveToHomeRelease])
|
|
206
|
+
|
|
207
|
+
// Store and provide cycle timer controls for external use
|
|
208
|
+
const handleCycleComplete = useCallback(
|
|
209
|
+
(controls: {
|
|
210
|
+
startNewCycle: (
|
|
211
|
+
maxTimeSeconds?: number,
|
|
212
|
+
elapsedSeconds?: number,
|
|
213
|
+
) => void
|
|
214
|
+
pause: () => void
|
|
215
|
+
resume: () => void
|
|
216
|
+
isPaused: () => boolean
|
|
217
|
+
}) => {
|
|
218
|
+
// Store the controls for potential future use
|
|
219
|
+
cycleControlsRef.current = controls
|
|
220
|
+
|
|
221
|
+
// Notify parent component that timer controls are ready
|
|
222
|
+
if (onCycleTimerReady) {
|
|
223
|
+
onCycleTimerReady(controls)
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
[onCycleTimerReady],
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
// Determine if robot should be hidden at small sizes to save space
|
|
230
|
+
const shouldHideRobot = isLandscape
|
|
231
|
+
? cardSize.width < 350
|
|
232
|
+
: cardSize.height < 200 // Hide robot at height < 200px in portrait
|
|
233
|
+
|
|
234
|
+
// Determine if runtime view should be hidden when height is too low
|
|
235
|
+
// Runtime should be hidden BEFORE the robot (at higher threshold)
|
|
236
|
+
const shouldHideRuntime = isLandscape
|
|
237
|
+
? cardSize.height < 310 // Landscape: hide runtime at height < 350px
|
|
238
|
+
: cardSize.height < 450 // Portrait: hide runtime at height < 450px
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<Card
|
|
242
|
+
ref={cardRef}
|
|
243
|
+
className={className}
|
|
244
|
+
sx={{
|
|
245
|
+
width: "100%",
|
|
246
|
+
height: "100%",
|
|
247
|
+
display: "flex",
|
|
248
|
+
flexDirection: isLandscape ? "row" : "column",
|
|
249
|
+
position: "relative",
|
|
250
|
+
overflow: "hidden",
|
|
251
|
+
minWidth: { xs: 180, sm: 220, md: 250 },
|
|
252
|
+
minHeight: isLandscape
|
|
253
|
+
? { xs: 200, sm: 240, md: 260 } // Allow runtime hiding at < 283px
|
|
254
|
+
: { xs: 150, sm: 180, md: 220 }, // Allow progressive hiding in portrait mode
|
|
255
|
+
border: `1px solid ${theme.palette.divider}`,
|
|
256
|
+
borderRadius: "18px",
|
|
257
|
+
boxShadow: "none",
|
|
258
|
+
backgroundColor:
|
|
259
|
+
theme.palette.backgroundPaperElevation?.[8] || "#2A2A3F",
|
|
260
|
+
backgroundImage: "none", // Override any gradient from elevation
|
|
261
|
+
}}
|
|
262
|
+
>
|
|
263
|
+
{isLandscape ? (
|
|
264
|
+
<>
|
|
265
|
+
{/* Landscape Layout: Robot on left, content on right */}
|
|
266
|
+
<Box
|
|
267
|
+
sx={{
|
|
268
|
+
flex: "0 0 50%",
|
|
269
|
+
position: "relative",
|
|
270
|
+
height: "100%",
|
|
271
|
+
minHeight: "100%",
|
|
272
|
+
maxHeight: "100%",
|
|
273
|
+
borderRadius: 1,
|
|
274
|
+
m: { xs: 1.5, sm: 2, md: 3 },
|
|
275
|
+
mr: { xs: 0.75, sm: 1, md: 1.5 },
|
|
276
|
+
overflow: "hidden", // Prevent content from affecting container size
|
|
277
|
+
display: shouldHideRobot ? "none" : "block",
|
|
278
|
+
}}
|
|
279
|
+
>
|
|
280
|
+
{!shouldHideRobot && (
|
|
281
|
+
<Canvas
|
|
282
|
+
orthographic
|
|
283
|
+
camera={{
|
|
284
|
+
position: [3, 2, 3],
|
|
285
|
+
zoom: 1,
|
|
286
|
+
}}
|
|
287
|
+
shadows
|
|
288
|
+
frameloop="demand"
|
|
289
|
+
style={{
|
|
290
|
+
borderRadius: theme.shape.borderRadius,
|
|
291
|
+
width: "100%",
|
|
292
|
+
height: "100%",
|
|
293
|
+
background: "transparent",
|
|
294
|
+
position: "absolute",
|
|
295
|
+
top: 0,
|
|
296
|
+
left: 0,
|
|
297
|
+
}}
|
|
298
|
+
dpr={[1, 2]}
|
|
299
|
+
gl={{ alpha: true, antialias: true }}
|
|
300
|
+
>
|
|
301
|
+
<PresetEnvironment />
|
|
302
|
+
<Bounds fit observe margin={1} maxDuration={1}>
|
|
303
|
+
<RobotComponent
|
|
304
|
+
connectedMotionGroup={connectedMotionGroup}
|
|
305
|
+
postModelRender={handleModelRender}
|
|
306
|
+
/>
|
|
307
|
+
</Bounds>
|
|
308
|
+
</Canvas>
|
|
309
|
+
)}
|
|
310
|
+
</Box>
|
|
311
|
+
|
|
312
|
+
{/* Content container on right */}
|
|
313
|
+
<Box
|
|
314
|
+
sx={{
|
|
315
|
+
flex: shouldHideRobot ? "1" : "1",
|
|
316
|
+
display: "flex",
|
|
317
|
+
flexDirection: "column",
|
|
318
|
+
justifyContent: "flex-start",
|
|
319
|
+
width: shouldHideRobot ? "100%" : "50%",
|
|
320
|
+
}}
|
|
321
|
+
>
|
|
322
|
+
{/* Header section with robot name and program state */}
|
|
323
|
+
<Box
|
|
324
|
+
sx={{
|
|
325
|
+
p: { xs: 1.5, sm: 2, md: 3 },
|
|
326
|
+
pb: { xs: 1, sm: 1.5, md: 2 },
|
|
327
|
+
textAlign: "left",
|
|
328
|
+
}}
|
|
329
|
+
>
|
|
330
|
+
<Typography variant="h6" component="h2" sx={{ mb: 1 }}>
|
|
331
|
+
{robotName}
|
|
332
|
+
</Typography>
|
|
333
|
+
<ProgramStateIndicator
|
|
334
|
+
programState={programState}
|
|
335
|
+
safetyState={safetyState}
|
|
336
|
+
operationMode={operationMode}
|
|
337
|
+
/>
|
|
338
|
+
</Box>
|
|
339
|
+
|
|
340
|
+
{/* Bottom section with runtime, cycle time, and button */}
|
|
341
|
+
<Box
|
|
342
|
+
sx={{
|
|
343
|
+
p: { xs: 1.5, sm: 2, md: 3 },
|
|
344
|
+
pt: 0,
|
|
345
|
+
flex: "1",
|
|
346
|
+
display: "flex",
|
|
347
|
+
flexDirection: "column",
|
|
348
|
+
justifyContent: "space-between",
|
|
349
|
+
}}
|
|
350
|
+
>
|
|
351
|
+
{/* Runtime view - hidden if height is too low in landscape mode */}
|
|
352
|
+
{!shouldHideRuntime && (
|
|
353
|
+
<Box>
|
|
354
|
+
{/* Runtime display */}
|
|
355
|
+
<Typography
|
|
356
|
+
variant="body1"
|
|
357
|
+
sx={{
|
|
358
|
+
mb: 0,
|
|
359
|
+
color: theme.palette.text.secondary,
|
|
360
|
+
textAlign: "left",
|
|
361
|
+
}}
|
|
362
|
+
>
|
|
363
|
+
{t("RobotCard.Runtime.lb")}
|
|
364
|
+
</Typography>
|
|
365
|
+
|
|
366
|
+
{/* Compact cycle time component directly below runtime */}
|
|
367
|
+
<Box sx={{ textAlign: "left" }}>
|
|
368
|
+
<CycleTimerComponent
|
|
369
|
+
variant="small"
|
|
370
|
+
compact
|
|
371
|
+
onCycleComplete={handleCycleComplete}
|
|
372
|
+
onCycleEnd={onCycleEnd}
|
|
373
|
+
autoStart={cycleTimerAutoStart}
|
|
374
|
+
hasError={cycleTimerHasError}
|
|
375
|
+
/>
|
|
376
|
+
</Box>
|
|
377
|
+
|
|
378
|
+
{/* Divider */}
|
|
379
|
+
<Divider
|
|
380
|
+
sx={{
|
|
381
|
+
mt: 1,
|
|
382
|
+
mb: 0,
|
|
383
|
+
borderColor: theme.palette.divider,
|
|
384
|
+
opacity: 0.5,
|
|
385
|
+
}}
|
|
386
|
+
/>
|
|
387
|
+
</Box>
|
|
388
|
+
)}
|
|
389
|
+
|
|
390
|
+
<Box sx={{ mt: !shouldHideRuntime ? "auto" : 0 }}>
|
|
391
|
+
{/* Drive to Home button with some space */}
|
|
392
|
+
<Box
|
|
393
|
+
sx={{
|
|
394
|
+
display: "flex",
|
|
395
|
+
justifyContent: "flex-start",
|
|
396
|
+
mt: { xs: 1, sm: 1.5, md: 2 },
|
|
397
|
+
mb: { xs: 0.5, sm: 0.75, md: 1 },
|
|
398
|
+
}}
|
|
399
|
+
>
|
|
400
|
+
<Button
|
|
401
|
+
ref={driveButtonRef}
|
|
402
|
+
variant="contained"
|
|
403
|
+
color="secondary"
|
|
404
|
+
size="small"
|
|
405
|
+
disabled={!driveToHomeEnabled}
|
|
406
|
+
onMouseDown={handleDriveToHomeMouseDown}
|
|
407
|
+
onMouseUp={handleDriveToHomeMouseUp}
|
|
408
|
+
onMouseLeave={handleDriveToHomeMouseLeave}
|
|
409
|
+
onTouchStart={handleDriveToHomeMouseDown}
|
|
410
|
+
onTouchEnd={handleDriveToHomeMouseUp}
|
|
411
|
+
sx={{
|
|
412
|
+
textTransform: "none",
|
|
413
|
+
px: 1.5,
|
|
414
|
+
py: 0.5,
|
|
415
|
+
}}
|
|
416
|
+
>
|
|
417
|
+
{t("RobotCard.DriveToHome.bt")}
|
|
418
|
+
</Button>
|
|
419
|
+
</Box>
|
|
420
|
+
</Box>
|
|
421
|
+
</Box>
|
|
422
|
+
</Box>
|
|
423
|
+
</>
|
|
424
|
+
) : (
|
|
425
|
+
<>
|
|
426
|
+
{/* Portrait Layout: Header, Robot, Footer */}
|
|
427
|
+
<Box
|
|
428
|
+
sx={{
|
|
429
|
+
p: 3,
|
|
430
|
+
height: "100%",
|
|
431
|
+
display: "flex",
|
|
432
|
+
flexDirection: "column",
|
|
433
|
+
}}
|
|
434
|
+
>
|
|
435
|
+
{/* Header section with robot name and program state */}
|
|
436
|
+
<Box>
|
|
437
|
+
<Typography variant="h6" component="h2" sx={{ mb: 1 }}>
|
|
438
|
+
{robotName}
|
|
439
|
+
</Typography>
|
|
440
|
+
<ProgramStateIndicator
|
|
441
|
+
programState={programState}
|
|
442
|
+
safetyState={safetyState}
|
|
443
|
+
operationMode={operationMode}
|
|
444
|
+
/>
|
|
445
|
+
</Box>
|
|
446
|
+
|
|
447
|
+
{/* 3D Robot viewport in center */}
|
|
448
|
+
<Box
|
|
449
|
+
sx={{
|
|
450
|
+
flex: shouldHideRobot ? 0 : 1,
|
|
451
|
+
position: "relative",
|
|
452
|
+
minHeight: shouldHideRobot
|
|
453
|
+
? 0
|
|
454
|
+
: { xs: 120, sm: 150, md: 200 },
|
|
455
|
+
height: shouldHideRobot ? 0 : "auto",
|
|
456
|
+
borderRadius: 1,
|
|
457
|
+
overflow: "hidden",
|
|
458
|
+
display: shouldHideRobot ? "none" : "block",
|
|
459
|
+
}}
|
|
460
|
+
>
|
|
461
|
+
{!shouldHideRobot && (
|
|
462
|
+
<Canvas
|
|
463
|
+
orthographic
|
|
464
|
+
camera={{
|
|
465
|
+
position: [3, 2, 3],
|
|
466
|
+
zoom: 1,
|
|
467
|
+
}}
|
|
468
|
+
shadows
|
|
469
|
+
frameloop="demand"
|
|
470
|
+
style={{
|
|
471
|
+
borderRadius: theme.shape.borderRadius,
|
|
472
|
+
width: "100%",
|
|
473
|
+
height: "100%",
|
|
474
|
+
background: "transparent",
|
|
475
|
+
position: "absolute",
|
|
476
|
+
}}
|
|
477
|
+
dpr={[1, 2]}
|
|
478
|
+
gl={{ alpha: true, antialias: true }}
|
|
479
|
+
>
|
|
480
|
+
<PresetEnvironment />
|
|
481
|
+
<Bounds fit clip observe margin={1} maxDuration={1}>
|
|
482
|
+
<RobotComponent
|
|
483
|
+
connectedMotionGroup={connectedMotionGroup}
|
|
484
|
+
postModelRender={handleModelRender}
|
|
485
|
+
/>
|
|
486
|
+
</Bounds>
|
|
487
|
+
</Canvas>
|
|
488
|
+
)}
|
|
489
|
+
</Box>
|
|
490
|
+
|
|
491
|
+
{/* Bottom section with runtime, cycle time, and button */}
|
|
492
|
+
<Box>
|
|
493
|
+
{/* Runtime view - hidden if height is too low */}
|
|
494
|
+
{!shouldHideRuntime && (
|
|
495
|
+
<>
|
|
496
|
+
{/* Runtime display */}
|
|
497
|
+
<Typography
|
|
498
|
+
variant="body1"
|
|
499
|
+
sx={{
|
|
500
|
+
mb: 0,
|
|
501
|
+
color: theme.palette.text.secondary,
|
|
502
|
+
}}
|
|
503
|
+
>
|
|
504
|
+
{t("RobotCard.Runtime.lb")}
|
|
505
|
+
</Typography>
|
|
506
|
+
|
|
507
|
+
{/* Compact cycle time component directly below runtime */}
|
|
508
|
+
<CycleTimerComponent
|
|
509
|
+
variant="small"
|
|
510
|
+
compact
|
|
511
|
+
onCycleComplete={handleCycleComplete}
|
|
512
|
+
onCycleEnd={onCycleEnd}
|
|
513
|
+
autoStart={cycleTimerAutoStart}
|
|
514
|
+
hasError={cycleTimerHasError}
|
|
515
|
+
/>
|
|
516
|
+
|
|
517
|
+
{/* Divider */}
|
|
518
|
+
<Divider
|
|
519
|
+
sx={{
|
|
520
|
+
mt: 1,
|
|
521
|
+
mb: 0,
|
|
522
|
+
borderColor: theme.palette.divider,
|
|
523
|
+
opacity: 0.5,
|
|
524
|
+
}}
|
|
525
|
+
/>
|
|
526
|
+
</>
|
|
527
|
+
)}
|
|
528
|
+
|
|
529
|
+
{/* Drive to Home button with some space */}
|
|
530
|
+
<Box
|
|
531
|
+
sx={{
|
|
532
|
+
display: "flex",
|
|
533
|
+
justifyContent: "flex-start",
|
|
534
|
+
mt: !shouldHideRuntime
|
|
535
|
+
? { xs: 1, sm: 2, md: 5 }
|
|
536
|
+
: { xs: 0.5, sm: 1, md: 2 },
|
|
537
|
+
mb: { xs: 0.5, sm: 0.75, md: 1 },
|
|
538
|
+
}}
|
|
539
|
+
>
|
|
540
|
+
<Button
|
|
541
|
+
ref={driveButtonRef}
|
|
542
|
+
variant="contained"
|
|
543
|
+
color="secondary"
|
|
544
|
+
size="small"
|
|
545
|
+
disabled={!driveToHomeEnabled}
|
|
546
|
+
onMouseDown={handleDriveToHomeMouseDown}
|
|
547
|
+
onMouseUp={handleDriveToHomeMouseUp}
|
|
548
|
+
onMouseLeave={handleDriveToHomeMouseLeave}
|
|
549
|
+
onTouchStart={handleDriveToHomeMouseDown}
|
|
550
|
+
onTouchEnd={handleDriveToHomeMouseUp}
|
|
551
|
+
sx={{
|
|
552
|
+
textTransform: "none",
|
|
553
|
+
px: 1.5,
|
|
554
|
+
py: 0.5,
|
|
555
|
+
}}
|
|
556
|
+
>
|
|
557
|
+
{t("RobotCard.DriveToHome.bt")}
|
|
558
|
+
</Button>
|
|
559
|
+
</Box>
|
|
560
|
+
</Box>
|
|
561
|
+
</Box>
|
|
562
|
+
</>
|
|
563
|
+
)}
|
|
564
|
+
</Card>
|
|
565
|
+
)
|
|
566
|
+
},
|
|
567
|
+
),
|
|
568
|
+
)
|