@wandelbots/wandelbots-js-react-components 2.27.1-pr.feature-add-cycle-timer.368.3779de1 → 2.27.1-pr.feature-add-program-control-component.367.8887fdc

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-cycle-timer.368.3779de1",
3
+ "version": "2.27.1-pr.feature-add-program-control-component.367.8887fdc",
4
4
  "description": "React UI toolkit for building applications on top of the Wandelbots platform",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -140,7 +140,6 @@
140
140
  },
141
141
  "dependencies": {
142
142
  "@monaco-editor/react": "^4.7.0",
143
- "@mui/x-charts": "^8.9.0",
144
143
  "@shikijs/monaco": "^3.1.0",
145
144
  "i18next-browser-languagedetector": "^8.0.4",
146
145
  "lodash-es": "^4.17.21",
@@ -0,0 +1,217 @@
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
+ )
@@ -44,7 +44,8 @@
44
44
  "Jogging.Cartesian.bt": "Kartesisch",
45
45
  "Jogging.Joints.bt": "Gelenke",
46
46
  "Jogging.Velocity.bt": "Geschwindigkeit",
47
- "CycleTimer.RemainingTime.lb": "Verbleibende Zeit",
48
- "CycleTimer.Of.lb": "von",
49
- "CycleTimer.Min.lb": "min."
47
+ "ProgramControl.Start.bt": "Starten",
48
+ "ProgramControl.Resume.bt": "Fortsetzen",
49
+ "ProgramControl.Pause.bt": "Pausieren",
50
+ "ProgramControl.Stop.bt": "Stoppen"
50
51
  }
@@ -45,7 +45,8 @@
45
45
  "Jogging.Cartesian.bt": "Cartesian",
46
46
  "Jogging.Joints.bt": "Joints",
47
47
  "Jogging.Velocity.bt": "Velocity",
48
- "CycleTimer.RemainingTime.lb": "remaining time",
49
- "CycleTimer.Of.lb": "of",
50
- "CycleTimer.Min.lb": "min."
48
+ "ProgramControl.Start.bt": "Start",
49
+ "ProgramControl.Resume.bt": "Resume",
50
+ "ProgramControl.Pause.bt": "Pause",
51
+ "ProgramControl.Stop.bt": "Stop"
51
52
  }
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
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"
5
4
  export * from "./components/jogging/JoggingCartesianAxisControl"
6
5
  export * from "./components/jogging/JoggingJointRotationControl"
7
6
  export * from "./components/jogging/JoggingPanel"
@@ -10,6 +9,7 @@ export * from "./components/jogging/PoseCartesianValues"
10
9
  export * from "./components/jogging/PoseJointValues"
11
10
  export * from "./components/LoadingCover"
12
11
  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,30 +0,0 @@
1
- export interface CycleTimerProps {
2
- /** Callback that receives the startNewCycle function for controlling the timer */
3
- onCycleComplete: (startNewCycle: (maxTimeSeconds: number) => void) => void;
4
- /** Callback fired when a cycle actually completes (reaches zero) */
5
- onCycleEnd?: () => void;
6
- /** Whether the timer should start automatically when maxTime is set */
7
- autoStart?: boolean;
8
- /** Additional CSS classes */
9
- className?: string;
10
- }
11
- /**
12
- * A circular gauge timer component that shows the remaining time of a cycle
13
- *
14
- * Features:
15
- * - Circular gauge with 264px diameter and 40px thickness
16
- * - Shows remaining time prominently in the center (60px font)
17
- * - Displays "remaining time" label at top and total time at bottom
18
- * - Automatically counts down and triggers callback when reaching zero
19
- * - Fully localized with i18next
20
- * - Material-UI theming integration
21
- *
22
- * @param onCycleComplete - Callback that receives the startNewCycle function for controlling the timer
23
- * @param onCycleEnd - Optional callback fired when a cycle actually completes (reaches zero)
24
- * @param autoStart - Whether to start timer automatically (default: true)
25
- * @param className - Additional CSS classes
26
- */
27
- export declare const CycleTimer: (({ onCycleComplete, onCycleEnd, autoStart, className, }: CycleTimerProps) => import("react/jsx-runtime").JSX.Element) & {
28
- displayName: string;
29
- };
30
- //# sourceMappingURL=CycleTimer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CycleTimer.d.ts","sourceRoot":"","sources":["../../src/components/CycleTimer.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,eAAe,EAAE,CAAC,aAAa,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,KAAK,IAAI,CAAA;IAC1E,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAA;IACvB,uEAAuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,4DAOhB,eAAe;;CAyKrB,CAAA"}
@@ -1,211 +0,0 @@
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
- /** Callback that receives the startNewCycle function for controlling the timer */
10
- onCycleComplete: (startNewCycle: (maxTimeSeconds: number) => void) => void
11
- /** Callback fired when a cycle actually completes (reaches zero) */
12
- onCycleEnd?: () => void
13
- /** Whether the timer should start automatically when maxTime is set */
14
- autoStart?: boolean
15
- /** Additional CSS classes */
16
- className?: string
17
- }
18
-
19
- /**
20
- * A circular gauge timer component that shows the remaining time of a cycle
21
- *
22
- * Features:
23
- * - Circular gauge with 264px diameter and 40px thickness
24
- * - Shows remaining time prominently in the center (60px font)
25
- * - Displays "remaining time" label at top and total time at bottom
26
- * - Automatically counts down and triggers callback when reaching zero
27
- * - Fully localized with i18next
28
- * - Material-UI theming integration
29
- *
30
- * @param onCycleComplete - Callback that receives the startNewCycle function for controlling the timer
31
- * @param onCycleEnd - Optional callback fired when a cycle actually completes (reaches zero)
32
- * @param autoStart - Whether to start timer automatically (default: true)
33
- * @param className - Additional CSS classes
34
- */
35
- export const CycleTimer = externalizeComponent(
36
- observer(
37
- ({
38
- onCycleComplete,
39
- onCycleEnd,
40
- autoStart = true,
41
- className,
42
- }: CycleTimerProps) => {
43
- const theme = useTheme()
44
- const { t } = useTranslation()
45
- const [remainingTime, setRemainingTime] = useState(0)
46
- const [maxTime, setMaxTime] = useState(0)
47
- const [isRunning, setIsRunning] = useState(false)
48
- const intervalRef = useRef<NodeJS.Timeout | null>(null)
49
-
50
- const startNewCycle = useCallback(
51
- (maxTimeSeconds: number) => {
52
- console.log(`Starting new cycle with ${maxTimeSeconds} seconds`)
53
- setMaxTime(maxTimeSeconds)
54
- setRemainingTime(maxTimeSeconds)
55
- if (autoStart) {
56
- console.log("Auto-start enabled, starting timer")
57
- setIsRunning(true)
58
- } else {
59
- console.log("Auto-start disabled, timer set but not started")
60
- }
61
- },
62
- [autoStart],
63
- )
64
-
65
- // Call onCycleComplete immediately to provide the startNewCycle function
66
- useEffect(() => {
67
- let isMounted = true
68
- const timeoutId = setTimeout(() => {
69
- if (isMounted) {
70
- onCycleComplete(startNewCycle)
71
- }
72
- }, 0)
73
-
74
- return () => {
75
- isMounted = false
76
- clearTimeout(timeoutId)
77
- }
78
- }, [onCycleComplete, startNewCycle])
79
-
80
- useEffect(() => {
81
- if (isRunning && remainingTime > 0) {
82
- intervalRef.current = setInterval(() => {
83
- setRemainingTime((prev) => {
84
- if (prev <= 1) {
85
- setIsRunning(false)
86
- // Call onCycleEnd when timer reaches zero to notify about completion
87
- if (onCycleEnd) {
88
- setTimeout(() => onCycleEnd(), 0)
89
- }
90
- console.log("Cycle completed! Timer reached zero.")
91
- return 0
92
- }
93
- return prev - 1
94
- })
95
- }, 1000)
96
- } else {
97
- if (intervalRef.current) {
98
- clearInterval(intervalRef.current)
99
- intervalRef.current = null
100
- }
101
- }
102
-
103
- return () => {
104
- if (intervalRef.current) {
105
- clearInterval(intervalRef.current)
106
- }
107
- }
108
- }, [isRunning, remainingTime, onCycleEnd])
109
-
110
- const formatTime = (seconds: number): string => {
111
- const minutes = Math.floor(seconds / 60)
112
- const remainingSeconds = seconds % 60
113
- return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`
114
- }
115
-
116
- const progressValue =
117
- maxTime > 0 ? ((maxTime - remainingTime) / maxTime) * 100 : 0
118
-
119
- return (
120
- <Box
121
- className={className}
122
- sx={{
123
- position: "relative",
124
- width: 264,
125
- height: 264,
126
- display: "flex",
127
- alignItems: "center",
128
- justifyContent: "center",
129
- }}
130
- >
131
- <Gauge
132
- width={264}
133
- height={264}
134
- value={progressValue}
135
- valueMin={0}
136
- valueMax={100}
137
- innerRadius="71%"
138
- outerRadius="90%"
139
- sx={{
140
- [`& .MuiGauge-valueArc`]: {
141
- fill: theme.palette.success.main,
142
- },
143
- [`& .MuiGauge-referenceArc`]: {
144
- fill: "white",
145
- stroke: "transparent",
146
- },
147
- }}
148
- />
149
-
150
- {/* Center content overlay */}
151
- <Box
152
- sx={{
153
- position: "absolute",
154
- top: "50%",
155
- left: "50%",
156
- transform: "translate(-50%, -50%)",
157
- width: 187, // 71% of 264 = ~187px inner radius
158
- height: 187,
159
- borderRadius: "50%",
160
- backgroundColor: theme.palette.backgroundPaperElevation?.[8], // background/paper-elevation-8
161
- display: "flex",
162
- flexDirection: "column",
163
- alignItems: "center",
164
- justifyContent: "center",
165
- textAlign: "center",
166
- gap: 1,
167
- }}
168
- >
169
- {/* "remaining time" label */}
170
- <Typography
171
- variant="body2"
172
- sx={{
173
- fontSize: "12px",
174
- color: theme.palette.text.secondary,
175
- marginBottom: 0.5,
176
- }}
177
- >
178
- {t("CycleTimer.RemainingTime.lb")}
179
- </Typography>
180
-
181
- {/* Main timer display */}
182
- <Typography
183
- variant="h1"
184
- sx={{
185
- fontSize: "48px",
186
- fontWeight: 500,
187
- color: theme.palette.text.primary,
188
- lineHeight: 1,
189
- marginBottom: 0.5,
190
- }}
191
- >
192
- {formatTime(remainingTime)}
193
- </Typography>
194
-
195
- {/* Total time at bottom */}
196
- <Typography
197
- variant="body2"
198
- sx={{
199
- fontSize: "12px",
200
- color: theme.palette.text.secondary,
201
- }}
202
- >
203
- {t("CycleTimer.Of.lb")} {formatTime(maxTime)}{" "}
204
- {t("CycleTimer.Min.lb")}
205
- </Typography>
206
- </Box>
207
- </Box>
208
- )
209
- },
210
- ),
211
- )