@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/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-program-control-component.367.cfb26c6",
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
- "ProgramControl.Start.bt": "Start",
48
- "ProgramControl.Resume.bt": "Weiter",
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
- "ProgramControl.Start.bt": "Start",
49
- "ProgramControl.Resume.bt": "Resume",
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
- )