@wandelbots/wandelbots-js-react-components 2.27.1-pr.feature-add-program-control-component.367.cfb26c6 → 2.27.1-pr.feature-add-cycle-timer.368.7535dcc
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 +79 -0
- package/dist/components/CycleTimer.d.ts.map +1 -0
- package/dist/index.cjs +89 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17207 -9385
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/CycleTimer.tsx +369 -0
- package/src/i18n/locales/de/translations.json +2 -4
- package/src/i18n/locales/en/translations.json +2 -4
- package/src/index.ts +1 -1
- package/dist/components/ProgramControl.d.ts +0 -43
- package/dist/components/ProgramControl.d.ts.map +0 -1
- package/src/components/ProgramControl.tsx +0 -217
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wandelbots/wandelbots-js-react-components",
|
|
3
|
-
"version": "2.27.1-pr.feature-add-
|
|
3
|
+
"version": "2.27.1-pr.feature-add-cycle-timer.368.7535dcc",
|
|
4
4
|
"description": "React UI toolkit for building applications on top of the Wandelbots platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -140,6 +140,7 @@
|
|
|
140
140
|
},
|
|
141
141
|
"dependencies": {
|
|
142
142
|
"@monaco-editor/react": "^4.7.0",
|
|
143
|
+
"@mui/x-charts": "^8.9.0",
|
|
143
144
|
"@shikijs/monaco": "^3.1.0",
|
|
144
145
|
"i18next-browser-languagedetector": "^8.0.4",
|
|
145
146
|
"lodash-es": "^4.17.21",
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { Box, Typography, useTheme } from "@mui/material"
|
|
2
|
+
import { Gauge } from "@mui/x-charts/Gauge"
|
|
3
|
+
import { observer } from "mobx-react-lite"
|
|
4
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
5
|
+
import { useTranslation } from "react-i18next"
|
|
6
|
+
import { externalizeComponent } from "../externalizeComponent"
|
|
7
|
+
|
|
8
|
+
export interface CycleTimerProps {
|
|
9
|
+
/**
|
|
10
|
+
* Callback that receives the timer control functions:
|
|
11
|
+
* - `startNewCycle(maxTimeSeconds, elapsedSeconds?)` - Start a new timer cycle
|
|
12
|
+
* - `pause()` - Pause the countdown while preserving remaining time
|
|
13
|
+
* - `resume()` - Resume countdown from where it was paused
|
|
14
|
+
* - `isPaused()` - Check current pause state
|
|
15
|
+
*/
|
|
16
|
+
onCycleComplete: (controls: {
|
|
17
|
+
startNewCycle: (maxTimeSeconds: number, elapsedSeconds?: number) => void
|
|
18
|
+
pause: () => void
|
|
19
|
+
resume: () => void
|
|
20
|
+
isPaused: () => boolean
|
|
21
|
+
}) => void
|
|
22
|
+
/** Callback fired when a cycle actually completes (reaches zero) */
|
|
23
|
+
onCycleEnd?: () => void
|
|
24
|
+
/** Whether the timer should start automatically when maxTime is set */
|
|
25
|
+
autoStart?: boolean
|
|
26
|
+
/** Visual variant of the timer */
|
|
27
|
+
variant?: "default" | "small"
|
|
28
|
+
/** For small variant: whether to show remaining time details (compact hides them) */
|
|
29
|
+
compact?: boolean
|
|
30
|
+
/** Additional CSS classes */
|
|
31
|
+
className?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A circular gauge timer component that shows the remaining time of a cycle
|
|
36
|
+
*
|
|
37
|
+
* Features:
|
|
38
|
+
* - Circular gauge with 264px diameter and 40px thickness
|
|
39
|
+
* - Shows remaining time prominently in the center (60px font)
|
|
40
|
+
* - Displays "remaining time" label at top and total time at bottom
|
|
41
|
+
* - Automatically counts down and triggers callback when reaching zero
|
|
42
|
+
* - Full timer control: start, pause, resume functionality
|
|
43
|
+
* - Support for starting with elapsed time (resume mid-cycle)
|
|
44
|
+
* - Fully localized with i18next
|
|
45
|
+
* - Material-UI theming integration
|
|
46
|
+
* - Small variant with animated progress icon (gauge border only) next to text
|
|
47
|
+
*
|
|
48
|
+
* @param onCycleComplete - Callback that receives timer control functions
|
|
49
|
+
* @param onCycleEnd - Optional callback fired when a cycle actually completes (reaches zero)
|
|
50
|
+
* @param autoStart - Whether to start timer automatically (default: true)
|
|
51
|
+
* @param variant - Visual variant: "default" (large gauge) or "small" (animated icon with text)
|
|
52
|
+
* @param compact - For small variant: whether to hide remaining time details
|
|
53
|
+
* @param className - Additional CSS classes
|
|
54
|
+
*
|
|
55
|
+
* Usage:
|
|
56
|
+
* ```tsx
|
|
57
|
+
* <CycleTimer
|
|
58
|
+
* onCycleComplete={(controls) => {
|
|
59
|
+
* // Start a 5-minute cycle
|
|
60
|
+
* controls.startNewCycle(300)
|
|
61
|
+
*
|
|
62
|
+
* // Or start a 5-minute cycle with 2 minutes already elapsed
|
|
63
|
+
* controls.startNewCycle(300, 120)
|
|
64
|
+
*
|
|
65
|
+
* // Pause the timer
|
|
66
|
+
* controls.pause()
|
|
67
|
+
*
|
|
68
|
+
* // Resume the timer
|
|
69
|
+
* controls.resume()
|
|
70
|
+
*
|
|
71
|
+
* // Check if paused
|
|
72
|
+
* const paused = controls.isPaused()
|
|
73
|
+
* }}
|
|
74
|
+
* onCycleEnd={() => console.log('Cycle completed!')}
|
|
75
|
+
* />
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* Control Functions:
|
|
79
|
+
* - `startNewCycle(maxTimeSeconds, elapsedSeconds?)` - Start a new timer cycle
|
|
80
|
+
* - `pause()` - Pause the countdown while preserving remaining time
|
|
81
|
+
* - `resume()` - Resume countdown from where it was paused
|
|
82
|
+
* - `isPaused()` - Check current pause state
|
|
83
|
+
*/
|
|
84
|
+
export const CycleTimer = externalizeComponent(
|
|
85
|
+
observer(
|
|
86
|
+
({
|
|
87
|
+
onCycleComplete,
|
|
88
|
+
onCycleEnd,
|
|
89
|
+
autoStart = true,
|
|
90
|
+
variant = "default",
|
|
91
|
+
compact = false,
|
|
92
|
+
className,
|
|
93
|
+
}: CycleTimerProps) => {
|
|
94
|
+
const theme = useTheme()
|
|
95
|
+
const { t } = useTranslation()
|
|
96
|
+
const [remainingTime, setRemainingTime] = useState(0)
|
|
97
|
+
const [maxTime, setMaxTime] = useState(0)
|
|
98
|
+
const [isRunning, setIsRunning] = useState(false)
|
|
99
|
+
const [isPausedState, setIsPausedState] = useState(false)
|
|
100
|
+
const intervalRef = useRef<NodeJS.Timeout | null>(null)
|
|
101
|
+
|
|
102
|
+
const startNewCycle = useCallback(
|
|
103
|
+
(maxTimeSeconds: number, elapsedSeconds: number = 0) => {
|
|
104
|
+
console.log(
|
|
105
|
+
`Starting new cycle with ${maxTimeSeconds} seconds (${elapsedSeconds} seconds already elapsed)`,
|
|
106
|
+
)
|
|
107
|
+
setMaxTime(maxTimeSeconds)
|
|
108
|
+
const remainingSeconds = Math.max(0, maxTimeSeconds - elapsedSeconds)
|
|
109
|
+
setRemainingTime(remainingSeconds)
|
|
110
|
+
setIsPausedState(false)
|
|
111
|
+
|
|
112
|
+
if (remainingSeconds === 0) {
|
|
113
|
+
console.log("Cycle already completed (elapsed time >= max time)")
|
|
114
|
+
setIsRunning(false)
|
|
115
|
+
// Trigger completion callback immediately if time is already up
|
|
116
|
+
if (onCycleEnd) {
|
|
117
|
+
setTimeout(() => onCycleEnd(), 0)
|
|
118
|
+
}
|
|
119
|
+
} else if (autoStart) {
|
|
120
|
+
console.log("Auto-start enabled, starting timer")
|
|
121
|
+
setIsRunning(true)
|
|
122
|
+
} else {
|
|
123
|
+
console.log("Auto-start disabled, timer set but not started")
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
[autoStart, onCycleEnd],
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
const pause = useCallback(() => {
|
|
130
|
+
console.log("Pausing timer")
|
|
131
|
+
setIsRunning(false)
|
|
132
|
+
setIsPausedState(true)
|
|
133
|
+
}, [])
|
|
134
|
+
|
|
135
|
+
const resume = useCallback(() => {
|
|
136
|
+
if (isPausedState && remainingTime > 0) {
|
|
137
|
+
console.log("Resuming timer")
|
|
138
|
+
setIsRunning(true)
|
|
139
|
+
setIsPausedState(false)
|
|
140
|
+
}
|
|
141
|
+
}, [isPausedState, remainingTime])
|
|
142
|
+
|
|
143
|
+
const isPaused = useCallback(() => {
|
|
144
|
+
return isPausedState
|
|
145
|
+
}, [isPausedState])
|
|
146
|
+
|
|
147
|
+
// Call onCycleComplete immediately to provide the timer control functions
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
let isMounted = true
|
|
150
|
+
const timeoutId = setTimeout(() => {
|
|
151
|
+
if (isMounted) {
|
|
152
|
+
onCycleComplete({
|
|
153
|
+
startNewCycle,
|
|
154
|
+
pause,
|
|
155
|
+
resume,
|
|
156
|
+
isPaused,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
}, 0)
|
|
160
|
+
|
|
161
|
+
return () => {
|
|
162
|
+
isMounted = false
|
|
163
|
+
clearTimeout(timeoutId)
|
|
164
|
+
}
|
|
165
|
+
}, [onCycleComplete, startNewCycle, pause, resume, isPaused])
|
|
166
|
+
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (isRunning && remainingTime > 0) {
|
|
169
|
+
intervalRef.current = setInterval(() => {
|
|
170
|
+
setRemainingTime((prev) => {
|
|
171
|
+
if (prev <= 1) {
|
|
172
|
+
setIsRunning(false)
|
|
173
|
+
// Call onCycleEnd when timer reaches zero to notify about completion
|
|
174
|
+
if (onCycleEnd) {
|
|
175
|
+
setTimeout(() => onCycleEnd(), 0)
|
|
176
|
+
}
|
|
177
|
+
console.log("Cycle completed! Timer reached zero.")
|
|
178
|
+
return 0
|
|
179
|
+
}
|
|
180
|
+
return prev - 1
|
|
181
|
+
})
|
|
182
|
+
}, 1000)
|
|
183
|
+
} else {
|
|
184
|
+
if (intervalRef.current) {
|
|
185
|
+
clearInterval(intervalRef.current)
|
|
186
|
+
intervalRef.current = null
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return () => {
|
|
191
|
+
if (intervalRef.current) {
|
|
192
|
+
clearInterval(intervalRef.current)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}, [isRunning, remainingTime, onCycleEnd])
|
|
196
|
+
|
|
197
|
+
const formatTime = (seconds: number): string => {
|
|
198
|
+
const minutes = Math.floor(seconds / 60)
|
|
199
|
+
const remainingSeconds = seconds % 60
|
|
200
|
+
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const progressValue =
|
|
204
|
+
maxTime > 0 ? ((maxTime - remainingTime) / maxTime) * 100 : 0
|
|
205
|
+
|
|
206
|
+
// Small variant: horizontal layout with gauge icon and text
|
|
207
|
+
if (variant === "small") {
|
|
208
|
+
return (
|
|
209
|
+
<Box
|
|
210
|
+
className={className}
|
|
211
|
+
sx={{
|
|
212
|
+
display: "flex",
|
|
213
|
+
alignItems: "center",
|
|
214
|
+
gap: 0.125, // Minimal gap - 1px
|
|
215
|
+
}}
|
|
216
|
+
>
|
|
217
|
+
{/* Animated progress gauge icon */}
|
|
218
|
+
<Box
|
|
219
|
+
sx={{
|
|
220
|
+
position: "relative",
|
|
221
|
+
width: 40,
|
|
222
|
+
height: 40,
|
|
223
|
+
display: "flex",
|
|
224
|
+
alignItems: "center",
|
|
225
|
+
justifyContent: "center",
|
|
226
|
+
borderRadius: "50%",
|
|
227
|
+
overflow: "visible",
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
<Gauge
|
|
231
|
+
width={40}
|
|
232
|
+
height={40}
|
|
233
|
+
value={progressValue}
|
|
234
|
+
valueMin={0}
|
|
235
|
+
valueMax={100}
|
|
236
|
+
innerRadius="70%"
|
|
237
|
+
outerRadius="95%"
|
|
238
|
+
sx={{
|
|
239
|
+
[`& .MuiGauge-valueArc`]: {
|
|
240
|
+
fill: theme.palette.success.main,
|
|
241
|
+
},
|
|
242
|
+
[`& .MuiGauge-referenceArc`]: {
|
|
243
|
+
fill: theme.palette.success.main,
|
|
244
|
+
opacity: 0.3,
|
|
245
|
+
},
|
|
246
|
+
[`& .MuiGauge-valueText`]: {
|
|
247
|
+
display: "none",
|
|
248
|
+
},
|
|
249
|
+
[`& .MuiGauge-text`]: {
|
|
250
|
+
display: "none",
|
|
251
|
+
},
|
|
252
|
+
[`& text`]: {
|
|
253
|
+
display: "none",
|
|
254
|
+
},
|
|
255
|
+
}}
|
|
256
|
+
/>
|
|
257
|
+
</Box>
|
|
258
|
+
|
|
259
|
+
{/* Timer text display */}
|
|
260
|
+
<Typography
|
|
261
|
+
variant="body2"
|
|
262
|
+
sx={{
|
|
263
|
+
color: theme.palette.text.primary,
|
|
264
|
+
fontSize: "14px",
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
{compact
|
|
268
|
+
? // Compact mode: show only remaining time
|
|
269
|
+
formatTime(remainingTime)
|
|
270
|
+
: // Full mode: show "remaining / of total min." format
|
|
271
|
+
`${formatTime(remainingTime)} / ${t("CycleTimer.OfTime.lb", { time: formatTime(maxTime) })}`}
|
|
272
|
+
</Typography>
|
|
273
|
+
</Box>
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Default variant: large circular gauge with centered content
|
|
278
|
+
return (
|
|
279
|
+
<Box
|
|
280
|
+
className={className}
|
|
281
|
+
sx={{
|
|
282
|
+
position: "relative",
|
|
283
|
+
width: 264,
|
|
284
|
+
height: 264,
|
|
285
|
+
display: "flex",
|
|
286
|
+
alignItems: "center",
|
|
287
|
+
justifyContent: "center",
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
<Gauge
|
|
291
|
+
width={264}
|
|
292
|
+
height={264}
|
|
293
|
+
value={progressValue}
|
|
294
|
+
valueMin={0}
|
|
295
|
+
valueMax={100}
|
|
296
|
+
innerRadius="71%"
|
|
297
|
+
outerRadius="90%"
|
|
298
|
+
sx={{
|
|
299
|
+
[`& .MuiGauge-valueArc`]: {
|
|
300
|
+
fill: theme.palette.success.main,
|
|
301
|
+
},
|
|
302
|
+
[`& .MuiGauge-referenceArc`]: {
|
|
303
|
+
fill: "white",
|
|
304
|
+
stroke: "transparent",
|
|
305
|
+
},
|
|
306
|
+
}}
|
|
307
|
+
/>
|
|
308
|
+
|
|
309
|
+
{/* Center content overlay with timer information */}
|
|
310
|
+
<Box
|
|
311
|
+
sx={{
|
|
312
|
+
position: "absolute",
|
|
313
|
+
top: "50%",
|
|
314
|
+
left: "50%",
|
|
315
|
+
transform: "translate(-50%, -50%)",
|
|
316
|
+
width: 187, // 71% of 264 = ~187px inner radius
|
|
317
|
+
height: 187,
|
|
318
|
+
borderRadius: "50%",
|
|
319
|
+
backgroundColor: theme.palette.backgroundPaperElevation?.[8],
|
|
320
|
+
display: "flex",
|
|
321
|
+
flexDirection: "column",
|
|
322
|
+
alignItems: "center",
|
|
323
|
+
justifyContent: "center",
|
|
324
|
+
textAlign: "center",
|
|
325
|
+
gap: 1,
|
|
326
|
+
}}
|
|
327
|
+
>
|
|
328
|
+
{/* "remaining time" label */}
|
|
329
|
+
<Typography
|
|
330
|
+
variant="body2"
|
|
331
|
+
sx={{
|
|
332
|
+
fontSize: "12px",
|
|
333
|
+
color: theme.palette.text.secondary,
|
|
334
|
+
marginBottom: 0.5,
|
|
335
|
+
}}
|
|
336
|
+
>
|
|
337
|
+
{t("CycleTimer.RemainingTime.lb")}
|
|
338
|
+
</Typography>
|
|
339
|
+
|
|
340
|
+
{/* Main timer display */}
|
|
341
|
+
<Typography
|
|
342
|
+
variant="h1"
|
|
343
|
+
sx={{
|
|
344
|
+
fontSize: "48px",
|
|
345
|
+
fontWeight: 500,
|
|
346
|
+
color: theme.palette.text.primary,
|
|
347
|
+
lineHeight: 1,
|
|
348
|
+
marginBottom: 0.5,
|
|
349
|
+
}}
|
|
350
|
+
>
|
|
351
|
+
{formatTime(remainingTime)}
|
|
352
|
+
</Typography>
|
|
353
|
+
|
|
354
|
+
{/* Total time display */}
|
|
355
|
+
<Typography
|
|
356
|
+
variant="body2"
|
|
357
|
+
sx={{
|
|
358
|
+
fontSize: "12px",
|
|
359
|
+
color: theme.palette.text.secondary,
|
|
360
|
+
}}
|
|
361
|
+
>
|
|
362
|
+
{t("CycleTimer.OfTime.lb", { time: formatTime(maxTime) })}
|
|
363
|
+
</Typography>
|
|
364
|
+
</Box>
|
|
365
|
+
</Box>
|
|
366
|
+
)
|
|
367
|
+
},
|
|
368
|
+
),
|
|
369
|
+
)
|
|
@@ -44,8 +44,6 @@
|
|
|
44
44
|
"Jogging.Cartesian.bt": "Kartesisch",
|
|
45
45
|
"Jogging.Joints.bt": "Gelenke",
|
|
46
46
|
"Jogging.Velocity.bt": "Geschwindigkeit",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"ProgramControl.Pause.bt": "Pause",
|
|
50
|
-
"ProgramControl.Stop.bt": "Stopp"
|
|
47
|
+
"CycleTimer.RemainingTime.lb": "Verbleibende Zeit",
|
|
48
|
+
"CycleTimer.OfTime.lb": "von {{time}} min."
|
|
51
49
|
}
|
|
@@ -45,8 +45,6 @@
|
|
|
45
45
|
"Jogging.Cartesian.bt": "Cartesian",
|
|
46
46
|
"Jogging.Joints.bt": "Joints",
|
|
47
47
|
"Jogging.Velocity.bt": "Velocity",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"ProgramControl.Pause.bt": "Pause",
|
|
51
|
-
"ProgramControl.Stop.bt": "Stop"
|
|
48
|
+
"CycleTimer.RemainingTime.lb": "Time remaining",
|
|
49
|
+
"CycleTimer.OfTime.lb": "of {{time}} min."
|
|
52
50
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./components/3d-viewport/PresetEnvironment"
|
|
2
2
|
export * from "./components/3d-viewport/SafetyZonesRenderer"
|
|
3
3
|
export * from "./components/3d-viewport/TrajectoryRenderer"
|
|
4
|
+
export * from "./components/CycleTimer"
|
|
4
5
|
export * from "./components/jogging/JoggingCartesianAxisControl"
|
|
5
6
|
export * from "./components/jogging/JoggingJointRotationControl"
|
|
6
7
|
export * from "./components/jogging/JoggingPanel"
|
|
@@ -9,7 +10,6 @@ export * from "./components/jogging/PoseCartesianValues"
|
|
|
9
10
|
export * from "./components/jogging/PoseJointValues"
|
|
10
11
|
export * from "./components/LoadingCover"
|
|
11
12
|
export * from "./components/modal/NoMotionGroupModal"
|
|
12
|
-
export * from "./components/ProgramControl"
|
|
13
13
|
export * from "./components/robots/AxisConfig"
|
|
14
14
|
export * from "./components/robots/Robot"
|
|
15
15
|
export { defaultGetModel } from "./components/robots/robotModelLogic"
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export type ProgramState = "idle" | "running" | "paused" | "stopping";
|
|
2
|
-
export interface ProgramControlProps {
|
|
3
|
-
/** The current state of the program control */
|
|
4
|
-
state: ProgramState;
|
|
5
|
-
/** Callback fired when the run/resume button is clicked */
|
|
6
|
-
onRun: () => void;
|
|
7
|
-
/** Callback fired when the pause button is clicked (only available in 'with_pause' variant) */
|
|
8
|
-
onPause?: () => void;
|
|
9
|
-
/** Callback fired when the stop button is clicked */
|
|
10
|
-
onStop: () => void;
|
|
11
|
-
/**
|
|
12
|
-
* Function to reset the component from 'stopping' state back to 'idle'.
|
|
13
|
-
* This must be called manually by the user when requiresManualReset is true.
|
|
14
|
-
*/
|
|
15
|
-
onReset?: () => void;
|
|
16
|
-
/**
|
|
17
|
-
* When true, the component will stay in 'stopping' state until onReset is called manually.
|
|
18
|
-
* When false (default), auto-resets to 'idle' after 2 seconds.
|
|
19
|
-
*/
|
|
20
|
-
requiresManualReset?: boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Variant of the component:
|
|
23
|
-
* - 'with_pause': Shows run/pause/stop buttons (default)
|
|
24
|
-
* - 'without_pause': Shows only run/stop buttons
|
|
25
|
-
*/
|
|
26
|
-
variant?: "with_pause" | "without_pause";
|
|
27
|
-
/** Additional CSS class name */
|
|
28
|
-
className?: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* A control component for program execution with run, pause, and stop functionality.
|
|
32
|
-
*
|
|
33
|
-
* Features:
|
|
34
|
-
* - State machine with idle, running, paused, and stopping states
|
|
35
|
-
* - Two variants: with_pause (3 buttons) and without_pause (2 buttons)
|
|
36
|
-
* - Optional manual reset functionality
|
|
37
|
-
* - Responsive design with 110px circular buttons
|
|
38
|
-
* - Material-UI theming integration
|
|
39
|
-
*/
|
|
40
|
-
export declare const ProgramControl: (({ state, onRun, onPause, onStop, onReset, requiresManualReset, variant, className, }: ProgramControlProps) => import("react/jsx-runtime").JSX.Element) & {
|
|
41
|
-
displayName: string;
|
|
42
|
-
};
|
|
43
|
-
//# sourceMappingURL=ProgramControl.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProgramControl.d.ts","sourceRoot":"","sources":["../../src/components/ProgramControl.tsx"],"names":[],"mappings":"AAMA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAA;AAErE,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,KAAK,EAAE,YAAY,CAAA;IACnB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,+FAA+F;IAC/F,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,qDAAqD;IACrD,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,YAAY,GAAG,eAAe,CAAA;IACxC,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AASD;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,0FAWpB,mBAAmB;;CAuJzB,CAAA"}
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { Pause, PlayArrow, Stop } from "@mui/icons-material"
|
|
2
|
-
import { Box, Button, Typography, useTheme } from "@mui/material"
|
|
3
|
-
import { observer } from "mobx-react-lite"
|
|
4
|
-
import { useTranslation } from "react-i18next"
|
|
5
|
-
import { externalizeComponent } from "../externalizeComponent"
|
|
6
|
-
|
|
7
|
-
export type ProgramState = "idle" | "running" | "paused" | "stopping"
|
|
8
|
-
|
|
9
|
-
export interface ProgramControlProps {
|
|
10
|
-
/** The current state of the program control */
|
|
11
|
-
state: ProgramState
|
|
12
|
-
/** Callback fired when the run/resume button is clicked */
|
|
13
|
-
onRun: () => void
|
|
14
|
-
/** Callback fired when the pause button is clicked (only available in 'with_pause' variant) */
|
|
15
|
-
onPause?: () => void
|
|
16
|
-
/** Callback fired when the stop button is clicked */
|
|
17
|
-
onStop: () => void
|
|
18
|
-
/**
|
|
19
|
-
* Function to reset the component from 'stopping' state back to 'idle'.
|
|
20
|
-
* This must be called manually by the user when requiresManualReset is true.
|
|
21
|
-
*/
|
|
22
|
-
onReset?: () => void
|
|
23
|
-
/**
|
|
24
|
-
* When true, the component will stay in 'stopping' state until onReset is called manually.
|
|
25
|
-
* When false (default), auto-resets to 'idle' after 2 seconds.
|
|
26
|
-
*/
|
|
27
|
-
requiresManualReset?: boolean
|
|
28
|
-
/**
|
|
29
|
-
* Variant of the component:
|
|
30
|
-
* - 'with_pause': Shows run/pause/stop buttons (default)
|
|
31
|
-
* - 'without_pause': Shows only run/stop buttons
|
|
32
|
-
*/
|
|
33
|
-
variant?: "with_pause" | "without_pause"
|
|
34
|
-
/** Additional CSS class name */
|
|
35
|
-
className?: string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface ButtonConfig {
|
|
39
|
-
enabled: boolean
|
|
40
|
-
label: string
|
|
41
|
-
color: string
|
|
42
|
-
onClick: () => void
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* A control component for program execution with run, pause, and stop functionality.
|
|
47
|
-
*
|
|
48
|
-
* Features:
|
|
49
|
-
* - State machine with idle, running, paused, and stopping states
|
|
50
|
-
* - Two variants: with_pause (3 buttons) and without_pause (2 buttons)
|
|
51
|
-
* - Optional manual reset functionality
|
|
52
|
-
* - Responsive design with 110px circular buttons
|
|
53
|
-
* - Material-UI theming integration
|
|
54
|
-
*/
|
|
55
|
-
export const ProgramControl = externalizeComponent(
|
|
56
|
-
observer(
|
|
57
|
-
({
|
|
58
|
-
state,
|
|
59
|
-
onRun,
|
|
60
|
-
onPause,
|
|
61
|
-
onStop,
|
|
62
|
-
onReset,
|
|
63
|
-
requiresManualReset = false,
|
|
64
|
-
variant = "with_pause",
|
|
65
|
-
className,
|
|
66
|
-
}: ProgramControlProps) => {
|
|
67
|
-
const theme = useTheme()
|
|
68
|
-
const { t } = useTranslation()
|
|
69
|
-
|
|
70
|
-
const getButtonConfigs = (): ButtonConfig[] => {
|
|
71
|
-
const baseConfigs: Record<string, ButtonConfig> = {
|
|
72
|
-
run: {
|
|
73
|
-
enabled: state === "idle" || state === "paused",
|
|
74
|
-
label:
|
|
75
|
-
state === "paused"
|
|
76
|
-
? t("ProgramControl.Resume.bt")
|
|
77
|
-
: t("ProgramControl.Start.bt"),
|
|
78
|
-
color: theme.palette.success.main,
|
|
79
|
-
onClick: onRun,
|
|
80
|
-
},
|
|
81
|
-
pause: {
|
|
82
|
-
enabled: state === "running",
|
|
83
|
-
label: t("ProgramControl.Pause.bt"),
|
|
84
|
-
color: "#FFFFFF33",
|
|
85
|
-
onClick: onPause || (() => {}),
|
|
86
|
-
},
|
|
87
|
-
stop: {
|
|
88
|
-
enabled: state === "running" || state === "paused",
|
|
89
|
-
label: t("ProgramControl.Stop.bt"),
|
|
90
|
-
color: theme.palette.error.main,
|
|
91
|
-
onClick: onStop,
|
|
92
|
-
},
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (variant === "without_pause") {
|
|
96
|
-
return [baseConfigs.run, baseConfigs.stop]
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return [baseConfigs.run, baseConfigs.pause, baseConfigs.stop]
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const getButtonIcon = (index: number) => {
|
|
103
|
-
const iconProps = { sx: { fontSize: "55px" } }
|
|
104
|
-
|
|
105
|
-
if (variant === "without_pause") {
|
|
106
|
-
return index === 0 ? (
|
|
107
|
-
<PlayArrow {...iconProps} />
|
|
108
|
-
) : (
|
|
109
|
-
<Stop {...iconProps} />
|
|
110
|
-
)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
switch (index) {
|
|
114
|
-
case 0:
|
|
115
|
-
return <PlayArrow {...iconProps} />
|
|
116
|
-
case 1:
|
|
117
|
-
return <Pause {...iconProps} />
|
|
118
|
-
case 2:
|
|
119
|
-
return <Stop {...iconProps} />
|
|
120
|
-
default:
|
|
121
|
-
return null
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const buttonConfigs = getButtonConfigs()
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<Box
|
|
129
|
-
className={className}
|
|
130
|
-
sx={{
|
|
131
|
-
display: "flex",
|
|
132
|
-
flexDirection: "column",
|
|
133
|
-
alignItems: "center",
|
|
134
|
-
gap: 2,
|
|
135
|
-
}}
|
|
136
|
-
>
|
|
137
|
-
<Box
|
|
138
|
-
sx={{
|
|
139
|
-
display: "flex",
|
|
140
|
-
gap: "40px",
|
|
141
|
-
flexWrap: "wrap",
|
|
142
|
-
justifyContent: "center",
|
|
143
|
-
alignItems: "center",
|
|
144
|
-
}}
|
|
145
|
-
>
|
|
146
|
-
{buttonConfigs.map((config, index) => (
|
|
147
|
-
<Box
|
|
148
|
-
key={config.label}
|
|
149
|
-
sx={{
|
|
150
|
-
display: "flex",
|
|
151
|
-
flexDirection: "column",
|
|
152
|
-
alignItems: "center",
|
|
153
|
-
gap: 1,
|
|
154
|
-
}}
|
|
155
|
-
>
|
|
156
|
-
<Button
|
|
157
|
-
variant="contained"
|
|
158
|
-
disabled={
|
|
159
|
-
!config.enabled ||
|
|
160
|
-
(state === "stopping" && !requiresManualReset)
|
|
161
|
-
}
|
|
162
|
-
onClick={config.onClick}
|
|
163
|
-
sx={{
|
|
164
|
-
width: "110px",
|
|
165
|
-
height: "110px",
|
|
166
|
-
borderRadius: "110px",
|
|
167
|
-
backgroundColor: config.color,
|
|
168
|
-
opacity:
|
|
169
|
-
config.enabled &&
|
|
170
|
-
!(state === "stopping" && !requiresManualReset)
|
|
171
|
-
? 1
|
|
172
|
-
: 0.3,
|
|
173
|
-
"&:hover": {
|
|
174
|
-
backgroundColor: config.color,
|
|
175
|
-
opacity:
|
|
176
|
-
config.enabled &&
|
|
177
|
-
!(state === "stopping" && !requiresManualReset)
|
|
178
|
-
? 0.8
|
|
179
|
-
: 0.3,
|
|
180
|
-
},
|
|
181
|
-
"&:disabled": {
|
|
182
|
-
backgroundColor: config.color,
|
|
183
|
-
opacity: 0.3,
|
|
184
|
-
},
|
|
185
|
-
minWidth: "110px",
|
|
186
|
-
flexShrink: 0,
|
|
187
|
-
}}
|
|
188
|
-
>
|
|
189
|
-
{getButtonIcon(index)}
|
|
190
|
-
</Button>
|
|
191
|
-
|
|
192
|
-
<Typography
|
|
193
|
-
variant="body1"
|
|
194
|
-
sx={{
|
|
195
|
-
color:
|
|
196
|
-
config.enabled &&
|
|
197
|
-
!(state === "stopping" && !requiresManualReset)
|
|
198
|
-
? config.color
|
|
199
|
-
: theme.palette.text.disabled,
|
|
200
|
-
textAlign: "center",
|
|
201
|
-
opacity:
|
|
202
|
-
config.enabled &&
|
|
203
|
-
!(state === "stopping" && !requiresManualReset)
|
|
204
|
-
? 1
|
|
205
|
-
: 0.3,
|
|
206
|
-
}}
|
|
207
|
-
>
|
|
208
|
-
{config.label}
|
|
209
|
-
</Typography>
|
|
210
|
-
</Box>
|
|
211
|
-
))}
|
|
212
|
-
</Box>
|
|
213
|
-
</Box>
|
|
214
|
-
)
|
|
215
|
-
},
|
|
216
|
-
),
|
|
217
|
-
)
|