@wandelbots/wandelbots-js-react-components 2.37.0 → 2.38.0-pr.bugfix-add-bg-to-cycle-timer.380.f7d61ae

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 +9302 -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 +325 -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,386 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react"
2
+ import { useInterpolation } from "../utils/interpolation"
3
+ import type { TimerState } from "./types"
4
+ import { calculateExactProgress } from "./utils"
5
+
6
+ interface UseTimerLogicProps {
7
+ autoStart: boolean
8
+ onCycleEnd?: () => void
9
+ onMeasuringComplete?: () => void
10
+ hasError: boolean
11
+ onPauseAnimation: () => void
12
+ onErrorAnimation: () => void
13
+ onClearErrorAnimation: () => void
14
+ onStartPulsating: (onComplete?: () => void) => void
15
+ }
16
+
17
+ export const useTimerLogic = ({
18
+ autoStart,
19
+ onCycleEnd,
20
+ onMeasuringComplete,
21
+ hasError,
22
+ onPauseAnimation,
23
+ onErrorAnimation,
24
+ onClearErrorAnimation,
25
+ onStartPulsating,
26
+ }: UseTimerLogicProps) => {
27
+ const [timerState, setTimerState] = useState<TimerState>({
28
+ currentState: "idle",
29
+ remainingTime: 0,
30
+ maxTime: null,
31
+ isRunning: false,
32
+ isPausedState: false,
33
+ currentProgress: 0,
34
+ wasRunningBeforeError: false,
35
+ })
36
+
37
+ // Timer-related refs
38
+ const animationRef = useRef<number | null>(null)
39
+ const startTimeRef = useRef<number | null>(null)
40
+ const pausedTimeRef = useRef<number>(0)
41
+
42
+ // Spring-based interpolator for smooth gauge progress animations
43
+ const [progressInterpolator] = useInterpolation([0], {
44
+ tension: 80,
45
+ friction: 18,
46
+ onChange: ([progress]) => {
47
+ setTimerState((prev) => ({ ...prev, currentProgress: progress }))
48
+ },
49
+ })
50
+
51
+ const setIdle = useCallback(() => {
52
+ setTimerState((prev) => ({
53
+ ...prev,
54
+ currentState: "idle",
55
+ maxTime: null,
56
+ remainingTime: 0,
57
+ isRunning: false,
58
+ isPausedState: false,
59
+ currentProgress: 0, // Immediately reset progress to 0
60
+ }))
61
+ pausedTimeRef.current = 0
62
+ startTimeRef.current = null
63
+ progressInterpolator.setImmediate([0]) // Use setImmediate for instant reset
64
+ }, [progressInterpolator])
65
+
66
+ const startMeasuring = useCallback(
67
+ (elapsedSeconds: number = 0) => {
68
+ const initialProgress = ((elapsedSeconds / 60) % 1) * 100
69
+ setTimerState((prev) => ({
70
+ ...prev,
71
+ currentState: "measuring",
72
+ maxTime: null,
73
+ remainingTime: elapsedSeconds,
74
+ isPausedState: false,
75
+ currentProgress: initialProgress, // Immediately set progress
76
+ }))
77
+ pausedTimeRef.current = 0
78
+
79
+ progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
80
+
81
+ if (autoStart) {
82
+ startTimeRef.current = Date.now() - elapsedSeconds * 1000
83
+ setTimerState((prev) => ({ ...prev, isRunning: true }))
84
+ } else {
85
+ startTimeRef.current = null
86
+ }
87
+ },
88
+ [autoStart, progressInterpolator],
89
+ )
90
+
91
+ const startCountUp = useCallback(
92
+ (elapsedSeconds: number = 0) => {
93
+ const initialProgress = ((elapsedSeconds / 60) % 1) * 100
94
+ setTimerState((prev) => ({
95
+ ...prev,
96
+ currentState: "countup",
97
+ maxTime: null,
98
+ remainingTime: elapsedSeconds,
99
+ isPausedState: false,
100
+ currentProgress: initialProgress, // Immediately set progress
101
+ }))
102
+ pausedTimeRef.current = 0
103
+
104
+ progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
105
+
106
+ if (autoStart) {
107
+ startTimeRef.current = Date.now() - elapsedSeconds * 1000
108
+ setTimerState((prev) => ({ ...prev, isRunning: true }))
109
+ } else {
110
+ startTimeRef.current = null
111
+ }
112
+ },
113
+ [autoStart, progressInterpolator],
114
+ )
115
+
116
+ const startNewCycle = useCallback(
117
+ (maxTimeSeconds?: number, elapsedSeconds: number = 0) => {
118
+ // Stop any running timer first to prevent conflicts
119
+ setTimerState((prev) => ({ ...prev, isRunning: false }))
120
+ startTimeRef.current = null
121
+
122
+ const newState = maxTimeSeconds !== undefined ? "countdown" : "countup"
123
+ setTimerState((prev) => ({
124
+ ...prev,
125
+ currentState: newState,
126
+ maxTime: maxTimeSeconds ?? null,
127
+ isPausedState: false,
128
+ }))
129
+ pausedTimeRef.current = 0
130
+
131
+ if (maxTimeSeconds !== undefined) {
132
+ // Count-down mode
133
+ const remainingSeconds = Math.max(0, maxTimeSeconds - elapsedSeconds)
134
+ const initialProgress =
135
+ elapsedSeconds > 0 ? (elapsedSeconds / maxTimeSeconds) * 100 : 0
136
+
137
+ setTimerState((prev) => ({
138
+ ...prev,
139
+ remainingTime: remainingSeconds,
140
+ currentProgress: initialProgress, // Immediately set progress
141
+ }))
142
+
143
+ progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
144
+
145
+ if (remainingSeconds === 0) {
146
+ setTimerState((prev) => ({ ...prev, isRunning: false }))
147
+ startTimeRef.current = null
148
+ if (onCycleEnd) {
149
+ queueMicrotask(() => onCycleEnd())
150
+ }
151
+ } else if (autoStart) {
152
+ setTimeout(() => {
153
+ startTimeRef.current = Date.now() - elapsedSeconds * 1000
154
+ setTimerState((prev) => ({ ...prev, isRunning: true }))
155
+ }, 0)
156
+ } else {
157
+ startTimeRef.current = null
158
+ }
159
+ } else {
160
+ // Count-up mode
161
+ const initialProgress = ((elapsedSeconds / 60) % 1) * 100
162
+ setTimerState((prev) => ({
163
+ ...prev,
164
+ remainingTime: elapsedSeconds,
165
+ currentProgress: initialProgress, // Immediately set progress
166
+ }))
167
+
168
+ progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
169
+
170
+ if (autoStart) {
171
+ setTimeout(() => {
172
+ startTimeRef.current = Date.now() - elapsedSeconds * 1000
173
+ setTimerState((prev) => ({ ...prev, isRunning: true }))
174
+ }, 0)
175
+ } else {
176
+ startTimeRef.current = null
177
+ }
178
+ }
179
+ },
180
+ [autoStart, onCycleEnd, progressInterpolator],
181
+ )
182
+
183
+ const completeMeasuring = useCallback(() => {
184
+ if (timerState.currentState === "measuring") {
185
+ setTimerState((prev) => ({
186
+ ...prev,
187
+ isRunning: false,
188
+ currentState: "measured",
189
+ }))
190
+ startTimeRef.current = null
191
+
192
+ onStartPulsating(() => {
193
+ if (onMeasuringComplete) {
194
+ onMeasuringComplete()
195
+ }
196
+ })
197
+ }
198
+ }, [timerState.currentState, onStartPulsating, onMeasuringComplete])
199
+
200
+ const pause = useCallback(() => {
201
+ if (startTimeRef.current && timerState.isRunning) {
202
+ const now = Date.now()
203
+ const additionalElapsed = now - startTimeRef.current
204
+ pausedTimeRef.current += additionalElapsed
205
+
206
+ const totalElapsed = pausedTimeRef.current / 1000
207
+ const exactProgress = calculateExactProgress(
208
+ timerState.currentState,
209
+ totalElapsed,
210
+ timerState.maxTime,
211
+ )
212
+ progressInterpolator.setTarget([exactProgress])
213
+ }
214
+
215
+ setTimerState((prev) => ({
216
+ ...prev,
217
+ isRunning: false,
218
+ isPausedState: true,
219
+ }))
220
+ onPauseAnimation()
221
+ }, [
222
+ timerState.isRunning,
223
+ timerState.currentState,
224
+ timerState.maxTime,
225
+ progressInterpolator,
226
+ onPauseAnimation,
227
+ ])
228
+
229
+ const resume = useCallback(() => {
230
+ if (
231
+ timerState.isPausedState &&
232
+ (timerState.remainingTime > 0 || timerState.currentState !== "countdown")
233
+ ) {
234
+ startTimeRef.current = Date.now()
235
+ setTimerState((prev) => ({
236
+ ...prev,
237
+ isRunning: true,
238
+ isPausedState: false,
239
+ }))
240
+ }
241
+ }, [
242
+ timerState.isPausedState,
243
+ timerState.remainingTime,
244
+ timerState.currentState,
245
+ ])
246
+
247
+ const isPaused = useCallback(() => {
248
+ return timerState.isPausedState
249
+ }, [timerState.isPausedState])
250
+
251
+ // Handle error state changes
252
+ useEffect(() => {
253
+ if (hasError) {
254
+ if (timerState.isRunning && !timerState.isPausedState) {
255
+ setTimerState((prev) => ({ ...prev, wasRunningBeforeError: true }))
256
+ pause()
257
+ }
258
+ onErrorAnimation()
259
+ } else {
260
+ if (timerState.wasRunningBeforeError && timerState.isPausedState) {
261
+ setTimerState((prev) => ({ ...prev, wasRunningBeforeError: false }))
262
+ resume()
263
+ }
264
+ onClearErrorAnimation()
265
+ }
266
+ }, [
267
+ hasError,
268
+ timerState.isRunning,
269
+ timerState.isPausedState,
270
+ timerState.wasRunningBeforeError,
271
+ pause,
272
+ resume,
273
+ onErrorAnimation,
274
+ onClearErrorAnimation,
275
+ ])
276
+
277
+ // Main timer loop
278
+ useEffect(() => {
279
+ if (timerState.isRunning) {
280
+ const updateTimer = () => {
281
+ if (startTimeRef.current) {
282
+ const now = Date.now()
283
+ const elapsed =
284
+ (now - startTimeRef.current + pausedTimeRef.current) / 1000
285
+
286
+ if (
287
+ timerState.currentState === "countdown" &&
288
+ timerState.maxTime !== null
289
+ ) {
290
+ const remaining = Math.max(0, timerState.maxTime - elapsed)
291
+ setTimerState((prev) => ({
292
+ ...prev,
293
+ remainingTime: Math.ceil(remaining),
294
+ }))
295
+
296
+ const progress = Math.min(100, (elapsed / timerState.maxTime) * 100)
297
+ progressInterpolator.setTarget([progress])
298
+
299
+ if (remaining <= 0) {
300
+ setTimerState((prev) => ({
301
+ ...prev,
302
+ isRunning: false,
303
+ remainingTime: 0,
304
+ }))
305
+ startTimeRef.current = null
306
+ progressInterpolator.setTarget([100])
307
+ if (onCycleEnd) {
308
+ queueMicrotask(() => onCycleEnd())
309
+ }
310
+ return
311
+ }
312
+ } else if (timerState.currentState === "measuring") {
313
+ setTimerState((prev) => ({
314
+ ...prev,
315
+ remainingTime: Math.floor(elapsed),
316
+ }))
317
+ const progress = ((elapsed / 60) % 1) * 100
318
+ progressInterpolator.setTarget([progress])
319
+ } else if (timerState.currentState === "countup") {
320
+ setTimerState((prev) => ({
321
+ ...prev,
322
+ remainingTime: Math.floor(elapsed),
323
+ }))
324
+ const progress = ((elapsed / 60) % 1) * 100
325
+ progressInterpolator.setTarget([progress])
326
+ }
327
+
328
+ if (timerState.isRunning) {
329
+ animationRef.current = requestAnimationFrame(updateTimer)
330
+ }
331
+ }
332
+ }
333
+
334
+ animationRef.current = requestAnimationFrame(updateTimer)
335
+ } else {
336
+ if (animationRef.current) {
337
+ cancelAnimationFrame(animationRef.current)
338
+ animationRef.current = null
339
+ }
340
+ }
341
+
342
+ return () => {
343
+ if (animationRef.current) {
344
+ cancelAnimationFrame(animationRef.current)
345
+ }
346
+ }
347
+ }, [
348
+ timerState.isRunning,
349
+ onCycleEnd,
350
+ timerState.currentState,
351
+ timerState.maxTime,
352
+ progressInterpolator,
353
+ ])
354
+
355
+ // Interpolation animation loop
356
+ useEffect(() => {
357
+ let interpolationAnimationId: number | null = null
358
+
359
+ const animateInterpolation = () => {
360
+ progressInterpolator.update(1 / 60)
361
+ interpolationAnimationId = requestAnimationFrame(animateInterpolation)
362
+ }
363
+
364
+ interpolationAnimationId = requestAnimationFrame(animateInterpolation)
365
+
366
+ return () => {
367
+ if (interpolationAnimationId) {
368
+ cancelAnimationFrame(interpolationAnimationId)
369
+ }
370
+ }
371
+ }, [progressInterpolator])
372
+
373
+ return {
374
+ timerState,
375
+ controls: {
376
+ startNewCycle,
377
+ startMeasuring,
378
+ startCountUp,
379
+ setIdle,
380
+ completeMeasuring,
381
+ pause,
382
+ resume,
383
+ isPaused,
384
+ },
385
+ }
386
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Formats time in seconds to MM:SS format
3
+ */
4
+ export const formatTime = (seconds: number): string => {
5
+ const minutes = Math.floor(seconds / 60)
6
+ const remainingSeconds = seconds % 60
7
+ return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`
8
+ }
9
+
10
+ /**
11
+ * Calculates progress percentage for different timer states
12
+ */
13
+ export const calculateProgress = (
14
+ currentState: string,
15
+ remainingTime: number,
16
+ maxTime: number | null,
17
+ ): number => {
18
+ if (currentState === "idle") {
19
+ return 0
20
+ }
21
+
22
+ if (currentState === "countdown" && maxTime !== null) {
23
+ // Count-down mode: progress based on elapsed time
24
+ const elapsed = maxTime - remainingTime
25
+ return Math.min(100, (elapsed / maxTime) * 100)
26
+ }
27
+
28
+ if (currentState === "measuring" || currentState === "countup") {
29
+ // Count-up modes: progress based on minute steps (0-100% per minute)
30
+ return ((remainingTime / 60) % 1) * 100
31
+ }
32
+
33
+ return 0
34
+ }
35
+
36
+ /**
37
+ * Calculates exact progress position based on elapsed time
38
+ */
39
+ export const calculateExactProgress = (
40
+ currentState: string,
41
+ totalElapsed: number,
42
+ maxTime: number | null,
43
+ ): number => {
44
+ if (currentState === "countdown" && maxTime !== null) {
45
+ return Math.min(100, (totalElapsed / maxTime) * 100)
46
+ }
47
+
48
+ if (currentState === "measuring" || currentState === "countup") {
49
+ return ((totalElapsed / 60) % 1) * 100
50
+ }
51
+
52
+ return 0
53
+ }