@wandelbots/wandelbots-js-react-components 2.33.0-pr.feature-robot-precondition-list.372.cb78a22 → 2.33.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/CycleTimer.d.ts.map +1 -1
- package/dist/components/ProgramControl.d.ts +1 -6
- 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/robots/Robot.d.ts +2 -3
- package/dist/components/robots/Robot.d.ts.map +1 -1
- package/dist/index.cjs +48 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7196 -8357
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/src/components/CycleTimer.tsx +64 -43
- package/src/components/ProgramControl.tsx +10 -20
- package/src/components/ProgramStateIndicator.tsx +8 -20
- package/src/components/robots/Robot.tsx +2 -5
- package/src/i18n/locales/de/translations.json +1 -6
- package/src/i18n/locales/en/translations.json +1 -6
- package/src/index.ts +0 -7
- package/dist/components/DataGrid.d.ts +0 -61
- package/dist/components/DataGrid.d.ts.map +0 -1
- package/dist/components/LogPanel.d.ts +0 -54
- package/dist/components/LogPanel.d.ts.map +0 -1
- package/dist/components/LogStore.d.ts +0 -11
- package/dist/components/LogStore.d.ts.map +0 -1
- package/dist/components/LogViewer.d.ts +0 -26
- package/dist/components/LogViewer.d.ts.map +0 -1
- package/dist/components/RobotCard.d.ts +0 -84
- package/dist/components/RobotCard.d.ts.map +0 -1
- package/dist/components/RobotListItem.d.ts +0 -34
- package/dist/components/RobotListItem.d.ts.map +0 -1
- package/dist/components/RobotSetupReadinessIndicator.d.ts +0 -31
- package/dist/components/RobotSetupReadinessIndicator.d.ts.map +0 -1
- package/dist/components/RobotSetupReadinessIndicator.test.d.ts +0 -2
- package/dist/components/RobotSetupReadinessIndicator.test.d.ts.map +0 -1
- package/src/components/DataGrid.tsx +0 -454
- package/src/components/LogPanel.tsx +0 -85
- package/src/components/LogStore.ts +0 -40
- package/src/components/LogViewer.tsx +0 -297
- package/src/components/RobotCard.tsx +0 -576
- package/src/components/RobotListItem.tsx +0 -152
- package/src/components/RobotSetupReadinessIndicator.test.tsx +0 -60
- package/src/components/RobotSetupReadinessIndicator.tsx +0 -124
|
@@ -1,576 +0,0 @@
|
|
|
1
|
-
import { Box, Button, Card, Divider, Typography, useTheme } from "@mui/material"
|
|
2
|
-
import { Bounds, useBounds } 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
|
-
// Component to refresh bounds when model renders
|
|
21
|
-
function BoundsRefresher({
|
|
22
|
-
modelRenderTrigger,
|
|
23
|
-
children,
|
|
24
|
-
}: {
|
|
25
|
-
modelRenderTrigger: number
|
|
26
|
-
children: React.ReactNode
|
|
27
|
-
}) {
|
|
28
|
-
const bounds = useBounds()
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (modelRenderTrigger > 0) {
|
|
32
|
-
bounds.refresh().clip().fit()
|
|
33
|
-
}
|
|
34
|
-
}, [modelRenderTrigger, bounds])
|
|
35
|
-
|
|
36
|
-
return <>{children}</>
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface RobotCardProps {
|
|
40
|
-
/** Name of the robot displayed at the top */
|
|
41
|
-
robotName: string
|
|
42
|
-
/** Current program state */
|
|
43
|
-
programState: ProgramState
|
|
44
|
-
/** Current safety state of the robot controller */
|
|
45
|
-
safetyState: RobotControllerStateSafetyStateEnum
|
|
46
|
-
/** Current operation mode of the robot controller */
|
|
47
|
-
operationMode: RobotControllerStateOperationModeEnum
|
|
48
|
-
/** Whether the "Drive to Home" button should be enabled */
|
|
49
|
-
driveToHomeEnabled?: boolean
|
|
50
|
-
/** Callback fired when "Drive to Home" button is pressed */
|
|
51
|
-
onDriveToHomePress?: () => void
|
|
52
|
-
/** Callback fired when "Drive to Home" button is released */
|
|
53
|
-
onDriveToHomeRelease?: () => void
|
|
54
|
-
/** Connected motion group for the robot */
|
|
55
|
-
connectedMotionGroup: ConnectedMotionGroup
|
|
56
|
-
/** Custom robot component to render (optional, defaults to Robot) */
|
|
57
|
-
robotComponent?: React.ComponentType<{
|
|
58
|
-
connectedMotionGroup: ConnectedMotionGroup
|
|
59
|
-
flangeRef?: React.Ref<Group>
|
|
60
|
-
postModelRender?: () => void
|
|
61
|
-
transparentColor?: string
|
|
62
|
-
getModel?: (modelFromController: string) => string
|
|
63
|
-
}>
|
|
64
|
-
/** Custom cycle timer component (optional, defaults to CycleTimer) */
|
|
65
|
-
cycleTimerComponent?: React.ComponentType<{
|
|
66
|
-
variant?: "default" | "small"
|
|
67
|
-
compact?: boolean
|
|
68
|
-
onCycleComplete: (controls: {
|
|
69
|
-
startNewCycle: (maxTimeSeconds: number, elapsedSeconds?: number) => void
|
|
70
|
-
pause: () => void
|
|
71
|
-
resume: () => void
|
|
72
|
-
isPaused: () => boolean
|
|
73
|
-
}) => void
|
|
74
|
-
onCycleEnd?: () => void
|
|
75
|
-
autoStart?: boolean
|
|
76
|
-
className?: string
|
|
77
|
-
}>
|
|
78
|
-
/** Callback to receive cycle timer controls for external timer management */
|
|
79
|
-
onCycleTimerReady?: (controls: {
|
|
80
|
-
startNewCycle: (maxTimeSeconds: number, elapsedSeconds?: number) => void
|
|
81
|
-
pause: () => void
|
|
82
|
-
resume: () => void
|
|
83
|
-
isPaused: () => boolean
|
|
84
|
-
}) => void
|
|
85
|
-
/** Callback fired when a cycle completes (reaches zero) */
|
|
86
|
-
onCycleEnd?: () => void
|
|
87
|
-
/** Whether the cycle timer should auto-start when a new cycle is set */
|
|
88
|
-
cycleTimerAutoStart?: 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
|
|
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
|
-
className,
|
|
134
|
-
}: RobotCardProps) => {
|
|
135
|
-
const theme = useTheme()
|
|
136
|
-
const { t } = useTranslation()
|
|
137
|
-
const [isDriveToHomePressed, setIsDriveToHomePressed] = useState(false)
|
|
138
|
-
const driveButtonRef = useRef<HTMLButtonElement>(null)
|
|
139
|
-
const robotRef = useRef<Group>(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: (maxTimeSeconds: number, elapsedSeconds?: number) => void
|
|
151
|
-
pause: () => void
|
|
152
|
-
resume: () => void
|
|
153
|
-
isPaused: () => boolean
|
|
154
|
-
} | null>(null)
|
|
155
|
-
|
|
156
|
-
// Hook to detect aspect ratio and size changes
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
const checkDimensions = () => {
|
|
159
|
-
if (cardRef.current) {
|
|
160
|
-
const { offsetWidth, offsetHeight } = cardRef.current
|
|
161
|
-
setIsLandscape(offsetWidth > offsetHeight)
|
|
162
|
-
setCardSize({ width: offsetWidth, height: offsetHeight })
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Initial check
|
|
167
|
-
checkDimensions()
|
|
168
|
-
|
|
169
|
-
// Set up ResizeObserver to watch for size changes
|
|
170
|
-
const resizeObserver = new ResizeObserver(checkDimensions)
|
|
171
|
-
if (cardRef.current) {
|
|
172
|
-
resizeObserver.observe(cardRef.current)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return () => {
|
|
176
|
-
resizeObserver.disconnect()
|
|
177
|
-
}
|
|
178
|
-
}, [])
|
|
179
|
-
|
|
180
|
-
const handleModelRender = useCallback(() => {
|
|
181
|
-
// Trigger bounds refresh when model renders
|
|
182
|
-
setModelRenderTrigger((prev) => prev + 1)
|
|
183
|
-
}, [])
|
|
184
|
-
|
|
185
|
-
const handleDriveToHomeMouseDown = useCallback(() => {
|
|
186
|
-
if (!driveToHomeEnabled || !onDriveToHomePress) return
|
|
187
|
-
setIsDriveToHomePressed(true)
|
|
188
|
-
onDriveToHomePress()
|
|
189
|
-
}, [driveToHomeEnabled, onDriveToHomePress])
|
|
190
|
-
|
|
191
|
-
const handleDriveToHomeMouseUp = useCallback(() => {
|
|
192
|
-
if (!driveToHomeEnabled || !onDriveToHomeRelease) return
|
|
193
|
-
setIsDriveToHomePressed(false)
|
|
194
|
-
onDriveToHomeRelease()
|
|
195
|
-
}, [driveToHomeEnabled, onDriveToHomeRelease])
|
|
196
|
-
|
|
197
|
-
const handleDriveToHomeMouseLeave = useCallback(() => {
|
|
198
|
-
if (isDriveToHomePressed && onDriveToHomeRelease) {
|
|
199
|
-
setIsDriveToHomePressed(false)
|
|
200
|
-
onDriveToHomeRelease()
|
|
201
|
-
}
|
|
202
|
-
}, [isDriveToHomePressed, onDriveToHomeRelease])
|
|
203
|
-
|
|
204
|
-
// Store and provide cycle timer controls for external use
|
|
205
|
-
const handleCycleComplete = useCallback(
|
|
206
|
-
(controls: {
|
|
207
|
-
startNewCycle: (
|
|
208
|
-
maxTimeSeconds: number,
|
|
209
|
-
elapsedSeconds?: number,
|
|
210
|
-
) => void
|
|
211
|
-
pause: () => void
|
|
212
|
-
resume: () => void
|
|
213
|
-
isPaused: () => boolean
|
|
214
|
-
}) => {
|
|
215
|
-
// Store the controls for potential future use
|
|
216
|
-
cycleControlsRef.current = controls
|
|
217
|
-
|
|
218
|
-
// Notify parent component that timer controls are ready
|
|
219
|
-
if (onCycleTimerReady) {
|
|
220
|
-
onCycleTimerReady(controls)
|
|
221
|
-
}
|
|
222
|
-
},
|
|
223
|
-
[onCycleTimerReady],
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
// Determine if robot should be hidden at small sizes to save space
|
|
227
|
-
// Less aggressive hiding for portrait mode since robot is in center
|
|
228
|
-
const shouldHideRobot = isLandscape
|
|
229
|
-
? cardSize.width < 350 || cardSize.height < 250 // Aggressive for landscape
|
|
230
|
-
: cardSize.width < 250 || cardSize.height < 180 // Less aggressive for portrait
|
|
231
|
-
|
|
232
|
-
// Calculate responsive robot scale based on card size and orientation
|
|
233
|
-
const getRobotScale = () => {
|
|
234
|
-
if (shouldHideRobot) return 0
|
|
235
|
-
|
|
236
|
-
if (isLandscape) {
|
|
237
|
-
// More aggressive scaling for landscape since robot is on side
|
|
238
|
-
if (cardSize.width < 450) return 0.8
|
|
239
|
-
if (cardSize.width < 550) return 0.9
|
|
240
|
-
return 1
|
|
241
|
-
} else {
|
|
242
|
-
// Less aggressive scaling for portrait since robot is central
|
|
243
|
-
if (cardSize.width < 300) return 0.8
|
|
244
|
-
if (cardSize.width < 400) return 0.9
|
|
245
|
-
return 1
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const robotScale = getRobotScale()
|
|
250
|
-
|
|
251
|
-
return (
|
|
252
|
-
<Card
|
|
253
|
-
ref={cardRef}
|
|
254
|
-
className={className}
|
|
255
|
-
elevation={5}
|
|
256
|
-
sx={{
|
|
257
|
-
width: "100%",
|
|
258
|
-
height: "100%",
|
|
259
|
-
display: "flex",
|
|
260
|
-
flexDirection: isLandscape ? "row" : "column",
|
|
261
|
-
position: "relative",
|
|
262
|
-
overflow: "hidden",
|
|
263
|
-
minWidth: { xs: 180, sm: 220, md: 250 },
|
|
264
|
-
minHeight: isLandscape
|
|
265
|
-
? { xs: 160, sm: 200, md: 250 }
|
|
266
|
-
: { xs: 200, sm: 280, md: 350 },
|
|
267
|
-
border:
|
|
268
|
-
"1px solid var(--secondary-_states-outlinedBorder, #FFFFFF1F)",
|
|
269
|
-
borderRadius: "18px",
|
|
270
|
-
boxShadow: "none",
|
|
271
|
-
}}
|
|
272
|
-
>
|
|
273
|
-
{isLandscape ? (
|
|
274
|
-
<>
|
|
275
|
-
{/* Landscape Layout: Robot on left, content on right */}
|
|
276
|
-
<Box
|
|
277
|
-
sx={{
|
|
278
|
-
flex: "0 0 50%",
|
|
279
|
-
position: "relative",
|
|
280
|
-
height: "100%",
|
|
281
|
-
minHeight: "100%",
|
|
282
|
-
maxHeight: "100%",
|
|
283
|
-
borderRadius: 1,
|
|
284
|
-
m: { xs: 1.5, sm: 2, md: 3 },
|
|
285
|
-
mr: { xs: 0.75, sm: 1, md: 1.5 },
|
|
286
|
-
overflow: "hidden", // Prevent content from affecting container size
|
|
287
|
-
display: shouldHideRobot ? "none" : "block",
|
|
288
|
-
}}
|
|
289
|
-
>
|
|
290
|
-
{!shouldHideRobot && (
|
|
291
|
-
<Canvas
|
|
292
|
-
camera={{
|
|
293
|
-
position: [4, 4, 4], // Move camera further back for orthographic view
|
|
294
|
-
fov: 15, // Low FOV for near-orthographic projection
|
|
295
|
-
}}
|
|
296
|
-
shadows
|
|
297
|
-
frameloop="demand"
|
|
298
|
-
style={{
|
|
299
|
-
borderRadius: theme.shape.borderRadius,
|
|
300
|
-
width: "100%",
|
|
301
|
-
height: "100%",
|
|
302
|
-
background: "transparent",
|
|
303
|
-
position: "absolute",
|
|
304
|
-
top: 0,
|
|
305
|
-
left: 0,
|
|
306
|
-
}}
|
|
307
|
-
dpr={[1, 2]}
|
|
308
|
-
gl={{ alpha: true, antialias: true }}
|
|
309
|
-
>
|
|
310
|
-
<PresetEnvironment />
|
|
311
|
-
<Bounds fit clip observe margin={1}>
|
|
312
|
-
<BoundsRefresher modelRenderTrigger={modelRenderTrigger}>
|
|
313
|
-
<group ref={robotRef} scale={robotScale}>
|
|
314
|
-
<RobotComponent
|
|
315
|
-
connectedMotionGroup={connectedMotionGroup}
|
|
316
|
-
postModelRender={handleModelRender}
|
|
317
|
-
/>
|
|
318
|
-
</group>
|
|
319
|
-
</BoundsRefresher>
|
|
320
|
-
</Bounds>
|
|
321
|
-
</Canvas>
|
|
322
|
-
)}
|
|
323
|
-
</Box>
|
|
324
|
-
|
|
325
|
-
{/* Content container on right */}
|
|
326
|
-
<Box
|
|
327
|
-
sx={{
|
|
328
|
-
flex: shouldHideRobot ? "1" : "1",
|
|
329
|
-
display: "flex",
|
|
330
|
-
flexDirection: "column",
|
|
331
|
-
justifyContent: "flex-start",
|
|
332
|
-
width: shouldHideRobot ? "100%" : "50%",
|
|
333
|
-
}}
|
|
334
|
-
>
|
|
335
|
-
{/* Header section with robot name and program state */}
|
|
336
|
-
<Box
|
|
337
|
-
sx={{
|
|
338
|
-
p: { xs: 1.5, sm: 2, md: 3 },
|
|
339
|
-
pb: { xs: 1, sm: 1.5, md: 2 },
|
|
340
|
-
textAlign: "left",
|
|
341
|
-
}}
|
|
342
|
-
>
|
|
343
|
-
<Typography variant="h6" component="h2" sx={{ mb: 1 }}>
|
|
344
|
-
{robotName}
|
|
345
|
-
</Typography>
|
|
346
|
-
<ProgramStateIndicator
|
|
347
|
-
programState={programState}
|
|
348
|
-
safetyState={safetyState}
|
|
349
|
-
operationMode={operationMode}
|
|
350
|
-
/>
|
|
351
|
-
</Box>
|
|
352
|
-
|
|
353
|
-
{/* Bottom section with runtime, cycle time, and button */}
|
|
354
|
-
<Box
|
|
355
|
-
sx={{
|
|
356
|
-
p: { xs: 1.5, sm: 2, md: 3 },
|
|
357
|
-
pt: 0,
|
|
358
|
-
flex: "1",
|
|
359
|
-
display: "flex",
|
|
360
|
-
flexDirection: "column",
|
|
361
|
-
justifyContent: "space-between",
|
|
362
|
-
}}
|
|
363
|
-
>
|
|
364
|
-
<Box>
|
|
365
|
-
{/* Runtime display */}
|
|
366
|
-
<Typography
|
|
367
|
-
variant="body1"
|
|
368
|
-
sx={{
|
|
369
|
-
mb: 0,
|
|
370
|
-
color: "var(--text-secondary, #FFFFFFB2)",
|
|
371
|
-
textAlign: "left",
|
|
372
|
-
}}
|
|
373
|
-
>
|
|
374
|
-
{t("RobotCard.Runtime.lb")}
|
|
375
|
-
</Typography>
|
|
376
|
-
|
|
377
|
-
{/* Compact cycle time component directly below runtime */}
|
|
378
|
-
<Box sx={{ textAlign: "left" }}>
|
|
379
|
-
<CycleTimerComponent
|
|
380
|
-
variant="small"
|
|
381
|
-
compact
|
|
382
|
-
onCycleComplete={handleCycleComplete}
|
|
383
|
-
onCycleEnd={onCycleEnd}
|
|
384
|
-
autoStart={cycleTimerAutoStart}
|
|
385
|
-
/>
|
|
386
|
-
</Box>
|
|
387
|
-
|
|
388
|
-
{/* Divider */}
|
|
389
|
-
<Divider
|
|
390
|
-
sx={{
|
|
391
|
-
mt: 1,
|
|
392
|
-
mb: 0,
|
|
393
|
-
borderColor: theme.palette.divider,
|
|
394
|
-
opacity: 0.5,
|
|
395
|
-
}}
|
|
396
|
-
/>
|
|
397
|
-
</Box>
|
|
398
|
-
|
|
399
|
-
<Box sx={{ mt: "auto" }}>
|
|
400
|
-
{/* Drive to Home button with some space */}
|
|
401
|
-
<Box
|
|
402
|
-
sx={{
|
|
403
|
-
display: "flex",
|
|
404
|
-
justifyContent: "flex-start",
|
|
405
|
-
mt: { xs: 1, sm: 1.5, md: 2 },
|
|
406
|
-
mb: { xs: 0.5, sm: 0.75, md: 1 },
|
|
407
|
-
}}
|
|
408
|
-
>
|
|
409
|
-
<Button
|
|
410
|
-
ref={driveButtonRef}
|
|
411
|
-
variant="contained"
|
|
412
|
-
color="secondary"
|
|
413
|
-
size="small"
|
|
414
|
-
disabled={!driveToHomeEnabled}
|
|
415
|
-
onMouseDown={handleDriveToHomeMouseDown}
|
|
416
|
-
onMouseUp={handleDriveToHomeMouseUp}
|
|
417
|
-
onMouseLeave={handleDriveToHomeMouseLeave}
|
|
418
|
-
onTouchStart={handleDriveToHomeMouseDown}
|
|
419
|
-
onTouchEnd={handleDriveToHomeMouseUp}
|
|
420
|
-
sx={{
|
|
421
|
-
textTransform: "none",
|
|
422
|
-
px: 1.5,
|
|
423
|
-
py: 0.5,
|
|
424
|
-
}}
|
|
425
|
-
>
|
|
426
|
-
{t("RobotCard.DriveToHome.bt")}
|
|
427
|
-
</Button>
|
|
428
|
-
</Box>
|
|
429
|
-
</Box>
|
|
430
|
-
</Box>
|
|
431
|
-
</Box>
|
|
432
|
-
</>
|
|
433
|
-
) : (
|
|
434
|
-
<>
|
|
435
|
-
{/* Portrait Layout: Header, Robot, Footer */}
|
|
436
|
-
<Box
|
|
437
|
-
sx={{
|
|
438
|
-
p: 3,
|
|
439
|
-
height: "100%",
|
|
440
|
-
display: "flex",
|
|
441
|
-
flexDirection: "column",
|
|
442
|
-
}}
|
|
443
|
-
>
|
|
444
|
-
{/* Header section with robot name and program state */}
|
|
445
|
-
<Box>
|
|
446
|
-
<Typography variant="h6" component="h2" sx={{ mb: 1 }}>
|
|
447
|
-
{robotName}
|
|
448
|
-
</Typography>
|
|
449
|
-
<ProgramStateIndicator
|
|
450
|
-
programState={programState}
|
|
451
|
-
safetyState={safetyState}
|
|
452
|
-
operationMode={operationMode}
|
|
453
|
-
/>
|
|
454
|
-
</Box>
|
|
455
|
-
|
|
456
|
-
{/* 3D Robot viewport in center */}
|
|
457
|
-
<Box
|
|
458
|
-
sx={{
|
|
459
|
-
flex: shouldHideRobot ? 0 : 1,
|
|
460
|
-
position: "relative",
|
|
461
|
-
minHeight: shouldHideRobot
|
|
462
|
-
? 0
|
|
463
|
-
: { xs: 120, sm: 150, md: 200 },
|
|
464
|
-
height: shouldHideRobot ? 0 : "auto",
|
|
465
|
-
borderRadius: 1,
|
|
466
|
-
overflow: "hidden",
|
|
467
|
-
display: shouldHideRobot ? "none" : "block",
|
|
468
|
-
}}
|
|
469
|
-
>
|
|
470
|
-
{!shouldHideRobot && (
|
|
471
|
-
<Canvas
|
|
472
|
-
camera={{
|
|
473
|
-
position: [3, 3, 3], // Closer camera position for portrait to make robot larger
|
|
474
|
-
fov: 20, // Slightly higher FOV for portrait to fill better
|
|
475
|
-
}}
|
|
476
|
-
shadows
|
|
477
|
-
frameloop="demand"
|
|
478
|
-
style={{
|
|
479
|
-
borderRadius: theme.shape.borderRadius,
|
|
480
|
-
width: "100%",
|
|
481
|
-
height: "100%",
|
|
482
|
-
background: "transparent",
|
|
483
|
-
position: "absolute",
|
|
484
|
-
top: 0,
|
|
485
|
-
left: 0,
|
|
486
|
-
}}
|
|
487
|
-
dpr={[1, 2]}
|
|
488
|
-
gl={{ alpha: true, antialias: true }}
|
|
489
|
-
>
|
|
490
|
-
<PresetEnvironment />
|
|
491
|
-
<Bounds fit observe margin={1}>
|
|
492
|
-
<BoundsRefresher
|
|
493
|
-
modelRenderTrigger={modelRenderTrigger}
|
|
494
|
-
>
|
|
495
|
-
<group ref={robotRef} scale={robotScale}>
|
|
496
|
-
<RobotComponent
|
|
497
|
-
connectedMotionGroup={connectedMotionGroup}
|
|
498
|
-
postModelRender={handleModelRender}
|
|
499
|
-
/>
|
|
500
|
-
</group>
|
|
501
|
-
</BoundsRefresher>
|
|
502
|
-
</Bounds>
|
|
503
|
-
</Canvas>
|
|
504
|
-
)}
|
|
505
|
-
</Box>
|
|
506
|
-
|
|
507
|
-
{/* Bottom section with runtime, cycle time, and button */}
|
|
508
|
-
<Box>
|
|
509
|
-
{/* Runtime display */}
|
|
510
|
-
<Typography
|
|
511
|
-
variant="body1"
|
|
512
|
-
sx={{
|
|
513
|
-
mb: 0,
|
|
514
|
-
color: "var(--text-secondary, #FFFFFFB2)",
|
|
515
|
-
}}
|
|
516
|
-
>
|
|
517
|
-
{t("RobotCard.Runtime.lb")}
|
|
518
|
-
</Typography>
|
|
519
|
-
|
|
520
|
-
{/* Compact cycle time component directly below runtime */}
|
|
521
|
-
<CycleTimerComponent
|
|
522
|
-
variant="small"
|
|
523
|
-
compact
|
|
524
|
-
onCycleComplete={handleCycleComplete}
|
|
525
|
-
onCycleEnd={onCycleEnd}
|
|
526
|
-
autoStart={cycleTimerAutoStart}
|
|
527
|
-
/>
|
|
528
|
-
|
|
529
|
-
{/* Divider */}
|
|
530
|
-
<Divider
|
|
531
|
-
sx={{
|
|
532
|
-
mt: 1,
|
|
533
|
-
mb: 0,
|
|
534
|
-
borderColor: theme.palette.divider,
|
|
535
|
-
opacity: 0.5,
|
|
536
|
-
}}
|
|
537
|
-
/>
|
|
538
|
-
|
|
539
|
-
{/* Drive to Home button with some space */}
|
|
540
|
-
<Box
|
|
541
|
-
sx={{
|
|
542
|
-
display: "flex",
|
|
543
|
-
justifyContent: "flex-start",
|
|
544
|
-
mt: { xs: 1, sm: 2, md: 5 },
|
|
545
|
-
mb: { xs: 0.5, sm: 0.75, md: 1 },
|
|
546
|
-
}}
|
|
547
|
-
>
|
|
548
|
-
<Button
|
|
549
|
-
ref={driveButtonRef}
|
|
550
|
-
variant="contained"
|
|
551
|
-
color="secondary"
|
|
552
|
-
size="small"
|
|
553
|
-
disabled={!driveToHomeEnabled}
|
|
554
|
-
onMouseDown={handleDriveToHomeMouseDown}
|
|
555
|
-
onMouseUp={handleDriveToHomeMouseUp}
|
|
556
|
-
onMouseLeave={handleDriveToHomeMouseLeave}
|
|
557
|
-
onTouchStart={handleDriveToHomeMouseDown}
|
|
558
|
-
onTouchEnd={handleDriveToHomeMouseUp}
|
|
559
|
-
sx={{
|
|
560
|
-
textTransform: "none",
|
|
561
|
-
px: 1.5,
|
|
562
|
-
py: 0.5,
|
|
563
|
-
}}
|
|
564
|
-
>
|
|
565
|
-
{t("RobotCard.DriveToHome.bt")}
|
|
566
|
-
</Button>
|
|
567
|
-
</Box>
|
|
568
|
-
</Box>
|
|
569
|
-
</Box>
|
|
570
|
-
</>
|
|
571
|
-
)}
|
|
572
|
-
</Card>
|
|
573
|
-
)
|
|
574
|
-
},
|
|
575
|
-
),
|
|
576
|
-
)
|