@wandelbots/wandelbots-js-react-components 2.37.0 → 2.38.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.
Files changed (40) hide show
  1. package/dist/components/CycleTimer/CycleTimer.d.ts +3 -0
  2. package/dist/components/CycleTimer/CycleTimer.d.ts.map +1 -0
  3. package/dist/components/CycleTimer/DefaultVariant.d.ts +10 -0
  4. package/dist/components/CycleTimer/DefaultVariant.d.ts.map +1 -0
  5. package/dist/components/CycleTimer/SmallVariant.d.ts +11 -0
  6. package/dist/components/CycleTimer/SmallVariant.d.ts.map +1 -0
  7. package/dist/components/CycleTimer/index.d.ts +28 -0
  8. package/dist/components/CycleTimer/index.d.ts.map +1 -0
  9. package/dist/components/CycleTimer/types.d.ts +51 -0
  10. package/dist/components/CycleTimer/types.d.ts.map +1 -0
  11. package/dist/components/CycleTimer/useAnimations.d.ts +15 -0
  12. package/dist/components/CycleTimer/useAnimations.d.ts.map +1 -0
  13. package/dist/components/CycleTimer/useTimerLogic.d.ts +26 -0
  14. package/dist/components/CycleTimer/useTimerLogic.d.ts.map +1 -0
  15. package/dist/components/CycleTimer/utils.d.ts +13 -0
  16. package/dist/components/CycleTimer/utils.d.ts.map +1 -0
  17. package/dist/components/CycleTimer.d.ts +2 -96
  18. package/dist/components/CycleTimer.d.ts.map +1 -1
  19. package/dist/components/jogging/PoseCartesianValues.d.ts +8 -4
  20. package/dist/components/jogging/PoseCartesianValues.d.ts.map +1 -1
  21. package/dist/components/jogging/PoseJointValues.d.ts +8 -4
  22. package/dist/components/jogging/PoseJointValues.d.ts.map +1 -1
  23. package/dist/index.cjs +50 -50
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.js +9299 -8800
  26. package/dist/index.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/components/CycleTimer/CycleTimer.ts +6 -0
  29. package/src/components/CycleTimer/DefaultVariant.tsx +327 -0
  30. package/src/components/CycleTimer/SmallVariant.tsx +230 -0
  31. package/src/components/CycleTimer/index.tsx +157 -0
  32. package/src/components/CycleTimer/types.ts +60 -0
  33. package/src/components/CycleTimer/useAnimations.ts +202 -0
  34. package/src/components/CycleTimer/useTimerLogic.ts +386 -0
  35. package/src/components/CycleTimer/utils.ts +53 -0
  36. package/src/components/CycleTimer.tsx +6 -715
  37. package/src/components/jogging/PoseCartesianValues.tsx +85 -7
  38. package/src/components/jogging/PoseJointValues.tsx +86 -8
  39. package/src/i18n/locales/de/translations.json +4 -0
  40. package/src/i18n/locales/en/translations.json +4 -0
@@ -0,0 +1,157 @@
1
+ import { observer } from "mobx-react-lite"
2
+ import { useEffect, useRef } from "react"
3
+ import { externalizeComponent } from "../../externalizeComponent"
4
+ import { DefaultVariant } from "./DefaultVariant"
5
+ import { SmallVariant } from "./SmallVariant"
6
+ import type { CycleTimerProps } from "./types"
7
+ import { useAnimations } from "./useAnimations"
8
+ import { useTimerLogic } from "./useTimerLogic"
9
+
10
+ /**
11
+ * A circular gauge timer component that shows the remaining time of a cycle or counts up
12
+ *
13
+ * Features:
14
+ * - Custom SVG circular gauge with 264px diameter and 40px thickness
15
+ * - Multiple states: idle, measuring, measured, countdown, countup, success
16
+ * - Idle state: shows "Waiting for program cycle" with transparent inner circle
17
+ * - Measuring state: counts up with "Cycle Time" / "measuring..." labels
18
+ * - Measured state: shows final time with "Cycle Time" / "determined" labels in pulsating green
19
+ * - Countdown mode: shows remaining time prominently, counts down to zero
20
+ * - Count-up mode: shows elapsed time without special labels
21
+ * - Success state: brief green flash after cycle completion
22
+ * - Displays appropriate labels based on state
23
+ * - Automatically counts down/up and triggers callback when reaching zero (countdown only)
24
+ * - Full timer control: start, pause, resume functionality
25
+ * - Support for starting with elapsed time (resume mid-cycle)
26
+ * - Error state support: pauses timer and shows error styling (red color)
27
+ * - Smooth fade transitions between different text states
28
+ * - Pulsating text animation for completed measuring state
29
+ * - Fully localized with i18next
30
+ * - Material-UI theming integration
31
+ * - Small variant with animated progress icon (gauge border only) next to text or simple text-only mode
32
+ */
33
+ export const CycleTimer = externalizeComponent(
34
+ observer(
35
+ ({
36
+ onCycleComplete,
37
+ onCycleEnd,
38
+ onMeasuringComplete,
39
+ autoStart = true,
40
+ variant = "default",
41
+ compact = false,
42
+ className,
43
+ hasError = false,
44
+ }: CycleTimerProps) => {
45
+ const prevStateRef = useRef<string | undefined>(undefined)
46
+
47
+ // Initialize animation hooks
48
+ const {
49
+ animationState,
50
+ triggerPauseAnimation,
51
+ triggerErrorAnimation,
52
+ clearErrorAnimation,
53
+ startPulsatingAnimation,
54
+ stopPulsatingAnimation,
55
+ startIdleAnimations,
56
+ stopIdleAnimations,
57
+ triggerFadeTransition,
58
+ setInitialAnimationState,
59
+ cleanup,
60
+ } = useAnimations()
61
+
62
+ // Initialize timer logic
63
+ const { timerState, controls } = useTimerLogic({
64
+ autoStart,
65
+ onCycleEnd,
66
+ onMeasuringComplete,
67
+ hasError,
68
+ onPauseAnimation: triggerPauseAnimation,
69
+ onErrorAnimation: triggerErrorAnimation,
70
+ onClearErrorAnimation: clearErrorAnimation,
71
+ onStartPulsating: startPulsatingAnimation,
72
+ })
73
+
74
+ // Handle state changes with fade transitions
75
+ useEffect(() => {
76
+ const prevState = prevStateRef.current
77
+
78
+ if (
79
+ prevStateRef.current !== undefined &&
80
+ prevState !== timerState.currentState
81
+ ) {
82
+ // Stop pulsating animation if leaving measured state
83
+ if (prevState === "measured") {
84
+ stopPulsatingAnimation()
85
+ }
86
+
87
+ // Stop idle animations if leaving idle state
88
+ if (prevState === "idle") {
89
+ stopIdleAnimations()
90
+ }
91
+
92
+ // Trigger fade transition
93
+ triggerFadeTransition()
94
+ } else {
95
+ // No state change or first time - set initial state
96
+ setInitialAnimationState()
97
+ }
98
+
99
+ // Start idle animations if entering idle state
100
+ if (timerState.currentState === "idle") {
101
+ startIdleAnimations()
102
+ }
103
+
104
+ prevStateRef.current = timerState.currentState
105
+ }, [
106
+ timerState.currentState,
107
+ stopPulsatingAnimation,
108
+ stopIdleAnimations,
109
+ startIdleAnimations,
110
+ triggerFadeTransition,
111
+ setInitialAnimationState,
112
+ ])
113
+
114
+ // Provide controls to parent component
115
+ useEffect(() => {
116
+ let isMounted = true
117
+ const timeoutId = setTimeout(() => {
118
+ if (isMounted) {
119
+ onCycleComplete(controls)
120
+ }
121
+ }, 0)
122
+
123
+ return () => {
124
+ isMounted = false
125
+ clearTimeout(timeoutId)
126
+ }
127
+ }, [onCycleComplete, controls])
128
+
129
+ // Cleanup on unmount
130
+ useEffect(() => {
131
+ return cleanup
132
+ }, [cleanup])
133
+
134
+ // Render appropriate variant
135
+ if (variant === "small") {
136
+ return (
137
+ <SmallVariant
138
+ timerState={timerState}
139
+ animationState={animationState}
140
+ hasError={hasError}
141
+ compact={compact}
142
+ className={className}
143
+ />
144
+ )
145
+ }
146
+
147
+ return (
148
+ <DefaultVariant
149
+ timerState={timerState}
150
+ animationState={animationState}
151
+ hasError={hasError}
152
+ className={className}
153
+ />
154
+ )
155
+ },
156
+ ),
157
+ )
@@ -0,0 +1,60 @@
1
+ export type CycleTimerState =
2
+ | "idle" // Initial state showing "Waiting for program cycle"
3
+ | "measuring" // Counting up without max time, showing "Cycle Time" / "measuring..."
4
+ | "measured" // Completed measuring state showing "Cycle Time" / "determined" with pulsating green text
5
+ | "countdown" // Counting down with max time
6
+ | "countup" // Simple count up without special text
7
+ | "success" // Brief success state after cycle completion
8
+
9
+ export interface CycleTimerControls {
10
+ startNewCycle: (maxTimeSeconds?: number, elapsedSeconds?: number) => void
11
+ startMeasuring: (elapsedSeconds?: number) => void
12
+ startCountUp: (elapsedSeconds?: number) => void
13
+ setIdle: () => void
14
+ completeMeasuring: () => void
15
+ pause: () => void
16
+ resume: () => void
17
+ isPaused: () => boolean
18
+ }
19
+
20
+ export interface CycleTimerProps {
21
+ /**
22
+ * Callback that receives the timer control functions
23
+ */
24
+ onCycleComplete: (controls: CycleTimerControls) => void
25
+ /** Callback fired when a cycle actually completes (reaches zero) */
26
+ onCycleEnd?: () => void
27
+ /** Callback fired when measuring cycle completes */
28
+ onMeasuringComplete?: () => void
29
+ /** Whether the timer should start automatically when maxTime is set */
30
+ autoStart?: boolean
31
+ /** Visual variant of the timer */
32
+ variant?: "default" | "small"
33
+ /** For small variant: whether to show remaining time details (compact hides them) */
34
+ compact?: boolean
35
+ /** Additional CSS classes */
36
+ className?: string
37
+ /** Whether the timer is in an error state (pauses timer and shows error styling) */
38
+ hasError?: boolean
39
+ }
40
+
41
+ export interface TimerState {
42
+ currentState: CycleTimerState
43
+ remainingTime: number
44
+ maxTime: number | null
45
+ isRunning: boolean
46
+ isPausedState: boolean
47
+ currentProgress: number
48
+ wasRunningBeforeError: boolean
49
+ }
50
+
51
+ export interface AnimationState {
52
+ showPauseAnimation: boolean
53
+ showErrorAnimation: boolean
54
+ showPulsatingText: boolean
55
+ pulsatingFinished: boolean
56
+ showLabels: boolean
57
+ showMainText: boolean
58
+ showIdlePulsating: boolean
59
+ idleDotsCount: number
60
+ }
@@ -0,0 +1,202 @@
1
+ import { useCallback, useRef, useState } from "react"
2
+ import type { AnimationState } from "./types"
3
+
4
+ export const useAnimations = () => {
5
+ const [animationState, setAnimationState] = useState<AnimationState>({
6
+ showPauseAnimation: false,
7
+ showErrorAnimation: false,
8
+ showPulsatingText: false,
9
+ pulsatingFinished: false,
10
+ showLabels: true,
11
+ showMainText: true,
12
+ showIdlePulsating: false,
13
+ idleDotsCount: 0,
14
+ })
15
+
16
+ // Refs for managing timeouts and intervals
17
+ const pauseAnimationTimeoutRef = useRef<NodeJS.Timeout | null>(null)
18
+ const errorAnimationTimeoutRef = useRef<NodeJS.Timeout | null>(null)
19
+ const pulsatingIntervalRef = useRef<NodeJS.Timeout | null>(null)
20
+ const fadeTimeoutRef = useRef<NodeJS.Timeout | null>(null)
21
+ const pulseCountRef = useRef<number>(0)
22
+ const idlePulsatingIntervalRef = useRef<NodeJS.Timeout | null>(null)
23
+ const idleDotsIntervalRef = useRef<NodeJS.Timeout | null>(null)
24
+
25
+ const triggerPauseAnimation = useCallback(() => {
26
+ setAnimationState((prev) => ({ ...prev, showPauseAnimation: true }))
27
+
28
+ if (pauseAnimationTimeoutRef.current) {
29
+ clearTimeout(pauseAnimationTimeoutRef.current)
30
+ }
31
+
32
+ pauseAnimationTimeoutRef.current = setTimeout(() => {
33
+ setAnimationState((prev) => ({ ...prev, showPauseAnimation: false }))
34
+ }, 800)
35
+ }, [])
36
+
37
+ const triggerErrorAnimation = useCallback(() => {
38
+ setAnimationState((prev) => ({ ...prev, showErrorAnimation: true }))
39
+
40
+ if (errorAnimationTimeoutRef.current) {
41
+ clearTimeout(errorAnimationTimeoutRef.current)
42
+ }
43
+
44
+ errorAnimationTimeoutRef.current = setTimeout(() => {
45
+ setAnimationState((prev) => ({ ...prev, showErrorAnimation: false }))
46
+ }, 600)
47
+ }, [])
48
+
49
+ const clearErrorAnimation = useCallback(() => {
50
+ setAnimationState((prev) => ({ ...prev, showErrorAnimation: false }))
51
+ if (errorAnimationTimeoutRef.current) {
52
+ clearTimeout(errorAnimationTimeoutRef.current)
53
+ }
54
+ }, [])
55
+
56
+ const startPulsatingAnimation = useCallback((onComplete?: () => void) => {
57
+ pulseCountRef.current = 0
58
+ // Start with fade to success color
59
+ setAnimationState((prev) => ({
60
+ ...prev,
61
+ showPulsatingText: true,
62
+ pulsatingFinished: false,
63
+ }))
64
+
65
+ // After initial success color fade, start slow pulsating like idle
66
+ setTimeout(() => {
67
+ setAnimationState((prev) => ({
68
+ ...prev,
69
+ pulsatingFinished: true, // This will keep the success color and start slow pulsating
70
+ }))
71
+
72
+ // Start slow pulsating animation similar to idle
73
+ pulsatingIntervalRef.current = setInterval(() => {
74
+ setAnimationState((prev) => ({
75
+ ...prev,
76
+ showPulsatingText: !prev.showPulsatingText,
77
+ }))
78
+ }, 2000) // Same slow timing as idle pulsating
79
+
80
+ if (onComplete) {
81
+ onComplete()
82
+ }
83
+ }, 800) // Initial success color display duration
84
+ }, [])
85
+
86
+ const stopPulsatingAnimation = useCallback(() => {
87
+ if (pulsatingIntervalRef.current) {
88
+ clearInterval(pulsatingIntervalRef.current)
89
+ pulsatingIntervalRef.current = null
90
+ }
91
+ // Reset all pulsating states to ensure colors are reset
92
+ setAnimationState((prev) => ({
93
+ ...prev,
94
+ showPulsatingText: false,
95
+ pulsatingFinished: false,
96
+ }))
97
+ pulseCountRef.current = 0
98
+ }, [])
99
+
100
+ const startIdleAnimations = useCallback(() => {
101
+ // Start pulsating animation for the text
102
+ setAnimationState((prev) => ({
103
+ ...prev,
104
+ showIdlePulsating: true,
105
+ }))
106
+
107
+ idlePulsatingIntervalRef.current = setInterval(() => {
108
+ setAnimationState((prev) => ({
109
+ ...prev,
110
+ showIdlePulsating: !prev.showIdlePulsating,
111
+ }))
112
+ }, 2000) // Slower pulsate every 2 seconds
113
+
114
+ // Start animated dots
115
+ idleDotsIntervalRef.current = setInterval(() => {
116
+ setAnimationState((prev) => ({
117
+ ...prev,
118
+ idleDotsCount: (prev.idleDotsCount + 1) % 4, // Cycle through 0, 1, 2, 3
119
+ }))
120
+ }, 800) // Change dots every 800ms
121
+ }, [])
122
+
123
+ const stopIdleAnimations = useCallback(() => {
124
+ if (idlePulsatingIntervalRef.current) {
125
+ clearInterval(idlePulsatingIntervalRef.current)
126
+ idlePulsatingIntervalRef.current = null
127
+ }
128
+ if (idleDotsIntervalRef.current) {
129
+ clearInterval(idleDotsIntervalRef.current)
130
+ idleDotsIntervalRef.current = null
131
+ }
132
+ setAnimationState((prev) => ({
133
+ ...prev,
134
+ showIdlePulsating: false,
135
+ idleDotsCount: 0,
136
+ }))
137
+ }, [])
138
+
139
+ const triggerFadeTransition = useCallback(() => {
140
+ setAnimationState((prev) => ({
141
+ ...prev,
142
+ showLabels: false,
143
+ showMainText: false,
144
+ }))
145
+
146
+ if (fadeTimeoutRef.current) {
147
+ clearTimeout(fadeTimeoutRef.current)
148
+ }
149
+
150
+ fadeTimeoutRef.current = setTimeout(() => {
151
+ setAnimationState((prev) => ({
152
+ ...prev,
153
+ showLabels: true,
154
+ showMainText: true,
155
+ }))
156
+ }, 200)
157
+ }, [])
158
+
159
+ const setInitialAnimationState = useCallback(() => {
160
+ setAnimationState((prev) => ({
161
+ ...prev,
162
+ showLabels: true,
163
+ showMainText: true,
164
+ }))
165
+ }, [])
166
+
167
+ // Cleanup function
168
+ const cleanup = useCallback(() => {
169
+ if (pauseAnimationTimeoutRef.current) {
170
+ clearTimeout(pauseAnimationTimeoutRef.current)
171
+ }
172
+ if (errorAnimationTimeoutRef.current) {
173
+ clearTimeout(errorAnimationTimeoutRef.current)
174
+ }
175
+ if (fadeTimeoutRef.current) {
176
+ clearTimeout(fadeTimeoutRef.current)
177
+ }
178
+ if (pulsatingIntervalRef.current) {
179
+ clearInterval(pulsatingIntervalRef.current)
180
+ }
181
+ if (idlePulsatingIntervalRef.current) {
182
+ clearInterval(idlePulsatingIntervalRef.current)
183
+ }
184
+ if (idleDotsIntervalRef.current) {
185
+ clearInterval(idleDotsIntervalRef.current)
186
+ }
187
+ }, [])
188
+
189
+ return {
190
+ animationState,
191
+ triggerPauseAnimation,
192
+ triggerErrorAnimation,
193
+ clearErrorAnimation,
194
+ startPulsatingAnimation,
195
+ stopPulsatingAnimation,
196
+ startIdleAnimations,
197
+ stopIdleAnimations,
198
+ triggerFadeTransition,
199
+ setInitialAnimationState,
200
+ cleanup,
201
+ }
202
+ }