@wandelbots/wandelbots-js-react-components 2.34.2-pr.feature-robot-precondition-list.372.9dfd57e → 2.34.2
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 +16 -33
- package/dist/components/CycleTimer.d.ts.map +1 -1
- package/dist/components/ProgramControl.d.ts +2 -8
- package/dist/components/ProgramControl.d.ts.map +1 -1
- package/dist/components/ProgramStateIndicator.d.ts +1 -1
- package/dist/components/ProgramStateIndicator.d.ts.map +1 -1
- package/dist/components/robots/Robot.d.ts +2 -3
- package/dist/components/robots/Robot.d.ts.map +1 -1
- package/dist/icons/index.d.ts +0 -1
- package/dist/icons/index.d.ts.map +1 -1
- package/dist/index.cjs +50 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9576 -11435
- package/dist/index.js.map +1 -1
- package/dist/themes/createDarkTheme.d.ts.map +1 -1
- package/package.json +1 -2
- package/src/components/CycleTimer.tsx +159 -384
- package/src/components/ProgramControl.tsx +12 -27
- package/src/components/ProgramStateIndicator.tsx +8 -25
- package/src/components/robots/Robot.tsx +2 -5
- package/src/i18n/locales/de/translations.json +1 -8
- package/src/i18n/locales/en/translations.json +1 -8
- package/src/icons/index.ts +0 -1
- package/src/index.ts +0 -14
- package/src/themes/createDarkTheme.ts +1 -75
- package/dist/components/AppHeader.d.ts +0 -34
- package/dist/components/AppHeader.d.ts.map +0 -1
- package/dist/components/DataGrid.d.ts +0 -66
- package/dist/components/DataGrid.d.ts.map +0 -1
- package/dist/components/LogPanel.d.ts +0 -38
- package/dist/components/LogPanel.d.ts.map +0 -1
- package/dist/components/LogStore.d.ts +0 -12
- package/dist/components/LogStore.d.ts.map +0 -1
- package/dist/components/LogViewer.d.ts +0 -46
- package/dist/components/LogViewer.d.ts.map +0 -1
- package/dist/components/RobotCard.d.ts +0 -103
- package/dist/components/RobotCard.d.ts.map +0 -1
- package/dist/components/RobotListItem.d.ts +0 -34
- package/dist/components/RobotListItem.d.ts.map +0 -1
- package/dist/components/RobotSetupReadinessIndicator.d.ts +0 -31
- package/dist/components/RobotSetupReadinessIndicator.d.ts.map +0 -1
- package/dist/components/RobotSetupReadinessIndicator.test.d.ts +0 -2
- package/dist/components/RobotSetupReadinessIndicator.test.d.ts.map +0 -1
- package/dist/components/TabBar.d.ts +0 -30
- package/dist/components/TabBar.d.ts.map +0 -1
- package/dist/components/robots/manufacturerHomePositions.d.ts +0 -21
- package/dist/components/robots/manufacturerHomePositions.d.ts.map +0 -1
- package/dist/icons/DropdownArrowIcon.d.ts +0 -3
- package/dist/icons/DropdownArrowIcon.d.ts.map +0 -1
- package/src/components/AppHeader.md +0 -84
- package/src/components/AppHeader.tsx +0 -199
- package/src/components/DataGrid.tsx +0 -659
- package/src/components/LogPanel.tsx +0 -69
- package/src/components/LogStore.ts +0 -44
- package/src/components/LogViewer.tsx +0 -370
- package/src/components/RobotCard.tsx +0 -568
- package/src/components/RobotListItem.tsx +0 -150
- package/src/components/RobotSetupReadinessIndicator.test.tsx +0 -60
- package/src/components/RobotSetupReadinessIndicator.tsx +0 -124
- package/src/components/TabBar.tsx +0 -144
- package/src/components/robots/manufacturerHomePositions.ts +0 -76
- package/src/icons/DropdownArrowIcon.tsx +0 -13
- package/src/icons/chevronDown.svg +0 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box,
|
|
1
|
+
import { Box, Typography, useTheme } from "@mui/material"
|
|
2
2
|
import { Gauge } from "@mui/x-charts/Gauge"
|
|
3
3
|
import { observer } from "mobx-react-lite"
|
|
4
4
|
import { useCallback, useEffect, useRef, useState } from "react"
|
|
@@ -9,13 +9,13 @@ import { useInterpolation } from "./utils/interpolation"
|
|
|
9
9
|
export interface CycleTimerProps {
|
|
10
10
|
/**
|
|
11
11
|
* Callback that receives the timer control functions:
|
|
12
|
-
* - `startNewCycle(maxTimeSeconds
|
|
12
|
+
* - `startNewCycle(maxTimeSeconds, elapsedSeconds?)` - Start a new timer cycle
|
|
13
13
|
* - `pause()` - Pause the countdown while preserving remaining time
|
|
14
14
|
* - `resume()` - Resume countdown from where it was paused
|
|
15
15
|
* - `isPaused()` - Check current pause state
|
|
16
16
|
*/
|
|
17
17
|
onCycleComplete: (controls: {
|
|
18
|
-
startNewCycle: (maxTimeSeconds
|
|
18
|
+
startNewCycle: (maxTimeSeconds: number, elapsedSeconds?: number) => void
|
|
19
19
|
pause: () => void
|
|
20
20
|
resume: () => void
|
|
21
21
|
isPaused: () => boolean
|
|
@@ -30,23 +30,18 @@ export interface CycleTimerProps {
|
|
|
30
30
|
compact?: boolean
|
|
31
31
|
/** Additional CSS classes */
|
|
32
32
|
className?: string
|
|
33
|
-
/** Whether the timer is in an error state (pauses timer and shows error styling) */
|
|
34
|
-
hasError?: boolean
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
|
-
* A circular gauge timer component that shows the remaining time of a cycle
|
|
36
|
+
* A circular gauge timer component that shows the remaining time of a cycle
|
|
39
37
|
*
|
|
40
38
|
* Features:
|
|
41
39
|
* - Circular gauge with 264px diameter and 40px thickness
|
|
42
|
-
* -
|
|
43
|
-
* -
|
|
44
|
-
* -
|
|
45
|
-
* - Displays appropriate labels based on mode
|
|
46
|
-
* - Automatically counts down/up and triggers callback when reaching zero (count-down only)
|
|
40
|
+
* - Shows remaining time prominently in the center (60px font)
|
|
41
|
+
* - Displays "remaining time" label at top and total time at bottom
|
|
42
|
+
* - Automatically counts down and triggers callback when reaching zero
|
|
47
43
|
* - Full timer control: start, pause, resume functionality
|
|
48
44
|
* - Support for starting with elapsed time (resume mid-cycle)
|
|
49
|
-
* - Error state support: pauses timer and shows error styling (red color)
|
|
50
45
|
* - Smooth spring-based progress animations for all state transitions
|
|
51
46
|
* - Fully localized with i18next
|
|
52
47
|
* - Material-UI theming integration
|
|
@@ -58,44 +53,32 @@ export interface CycleTimerProps {
|
|
|
58
53
|
* @param variant - Visual variant: "default" (large gauge) or "small" (animated icon with text)
|
|
59
54
|
* @param compact - For small variant: whether to hide remaining time details
|
|
60
55
|
* @param className - Additional CSS classes
|
|
61
|
-
* @param hasError - Whether the timer is in an error state (pauses timer and shows error styling)
|
|
62
56
|
*
|
|
63
57
|
* Usage:
|
|
64
58
|
* ```tsx
|
|
65
|
-
* // Count-down timer (with max time)
|
|
66
59
|
* <CycleTimer
|
|
67
60
|
* onCycleComplete={(controls) => {
|
|
68
|
-
* // Start a 5-minute
|
|
61
|
+
* // Start a 5-minute cycle
|
|
69
62
|
* controls.startNewCycle(300)
|
|
70
63
|
*
|
|
71
64
|
* // Or start a 5-minute cycle with 2 minutes already elapsed
|
|
72
65
|
* controls.startNewCycle(300, 120)
|
|
73
|
-
* }}
|
|
74
|
-
* onCycleEnd={() => console.log('Cycle completed!')}
|
|
75
|
-
* />
|
|
76
66
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* onCycleComplete={(controls) => {
|
|
80
|
-
* // Start count-up timer
|
|
81
|
-
* controls.startNewCycle()
|
|
67
|
+
* // Pause the timer
|
|
68
|
+
* controls.pause()
|
|
82
69
|
*
|
|
83
|
-
* //
|
|
84
|
-
* controls.
|
|
85
|
-
* }}
|
|
86
|
-
* />
|
|
70
|
+
* // Resume the timer
|
|
71
|
+
* controls.resume()
|
|
87
72
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* hasError={errorCondition}
|
|
91
|
-
* onCycleComplete={(controls) => {
|
|
92
|
-
* controls.startNewCycle(300)
|
|
73
|
+
* // Check if paused
|
|
74
|
+
* const paused = controls.isPaused()
|
|
93
75
|
* }}
|
|
76
|
+
* onCycleEnd={() => console.log('Cycle completed!')}
|
|
94
77
|
* />
|
|
95
78
|
* ```
|
|
96
79
|
*
|
|
97
80
|
* Control Functions:
|
|
98
|
-
* - `startNewCycle(maxTimeSeconds
|
|
81
|
+
* - `startNewCycle(maxTimeSeconds, elapsedSeconds?)` - Start a new timer cycle
|
|
99
82
|
* - `pause()` - Pause the countdown while preserving remaining time
|
|
100
83
|
* - `resume()` - Resume countdown from where it was paused
|
|
101
84
|
* - `isPaused()` - Check current pause state
|
|
@@ -109,29 +92,17 @@ export const CycleTimer = externalizeComponent(
|
|
|
109
92
|
variant = "default",
|
|
110
93
|
compact = false,
|
|
111
94
|
className,
|
|
112
|
-
hasError = false,
|
|
113
95
|
}: CycleTimerProps) => {
|
|
114
96
|
const theme = useTheme()
|
|
115
97
|
const { t } = useTranslation()
|
|
116
98
|
const [remainingTime, setRemainingTime] = useState(0)
|
|
117
|
-
const [maxTime, setMaxTime] = useState
|
|
99
|
+
const [maxTime, setMaxTime] = useState(0)
|
|
118
100
|
const [isRunning, setIsRunning] = useState(false)
|
|
119
101
|
const [isPausedState, setIsPausedState] = useState(false)
|
|
120
102
|
const [currentProgress, setCurrentProgress] = useState(0)
|
|
121
103
|
const animationRef = useRef<number | null>(null)
|
|
122
104
|
const startTimeRef = useRef<number | null>(null)
|
|
123
105
|
const pausedTimeRef = useRef<number>(0)
|
|
124
|
-
const [wasRunningBeforeError, setWasRunningBeforeError] = useState(false)
|
|
125
|
-
|
|
126
|
-
// Brief animation states for pause and error visual feedback
|
|
127
|
-
const [showPauseAnimation, setShowPauseAnimation] = useState(false)
|
|
128
|
-
const [showErrorAnimation, setShowErrorAnimation] = useState(false)
|
|
129
|
-
const pauseAnimationTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
130
|
-
const errorAnimationTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
131
|
-
|
|
132
|
-
// Track mode changes for fade transitions
|
|
133
|
-
const [showLabels, setShowLabels] = useState(true)
|
|
134
|
-
const prevMaxTimeRef = useRef<number | null | undefined>(undefined)
|
|
135
106
|
|
|
136
107
|
// Spring-based interpolator for smooth gauge progress animations
|
|
137
108
|
// Uses physics simulation to create natural, smooth transitions between progress values
|
|
@@ -143,78 +114,37 @@ export const CycleTimer = externalizeComponent(
|
|
|
143
114
|
},
|
|
144
115
|
})
|
|
145
116
|
|
|
146
|
-
// Handle mode changes with fade transitions for labels only
|
|
147
|
-
useEffect(() => {
|
|
148
|
-
const currentIsCountUp = maxTime === null
|
|
149
|
-
const prevMaxTime = prevMaxTimeRef.current
|
|
150
|
-
const prevIsCountUp = prevMaxTime === null
|
|
151
|
-
|
|
152
|
-
// Check if mode actually changed (not just first render)
|
|
153
|
-
if (
|
|
154
|
-
prevMaxTimeRef.current !== undefined &&
|
|
155
|
-
prevIsCountUp !== currentIsCountUp
|
|
156
|
-
) {
|
|
157
|
-
// Mode changed - labels will fade based on the Fade component conditions
|
|
158
|
-
// We just need to ensure showLabels is true so Fade can control visibility
|
|
159
|
-
setShowLabels(true)
|
|
160
|
-
} else {
|
|
161
|
-
// No mode change or first time - set initial state
|
|
162
|
-
setShowLabels(true)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
prevMaxTimeRef.current = maxTime
|
|
166
|
-
}, [maxTime])
|
|
167
|
-
|
|
168
117
|
const startNewCycle = useCallback(
|
|
169
|
-
(maxTimeSeconds
|
|
170
|
-
setMaxTime(maxTimeSeconds
|
|
118
|
+
(maxTimeSeconds: number, elapsedSeconds: number = 0) => {
|
|
119
|
+
setMaxTime(maxTimeSeconds)
|
|
120
|
+
const remainingSeconds = Math.max(0, maxTimeSeconds - elapsedSeconds)
|
|
121
|
+
setRemainingTime(remainingSeconds)
|
|
171
122
|
setIsPausedState(false)
|
|
172
123
|
pausedTimeRef.current = 0
|
|
173
124
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// Animate progress smoothly to starting position
|
|
183
|
-
const initialProgress =
|
|
184
|
-
elapsedSeconds > 0 ? (elapsedSeconds / maxTimeSeconds) * 100 : 0
|
|
185
|
-
if (elapsedSeconds === 0) {
|
|
186
|
-
progressInterpolator.setTarget([0])
|
|
187
|
-
} else {
|
|
188
|
-
progressInterpolator.setTarget([initialProgress])
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (remainingSeconds === 0) {
|
|
192
|
-
setIsRunning(false)
|
|
193
|
-
startTimeRef.current = null
|
|
194
|
-
// Trigger completion callback immediately if time is already up
|
|
195
|
-
if (onCycleEnd) {
|
|
196
|
-
setTimeout(() => onCycleEnd(), 0)
|
|
197
|
-
}
|
|
198
|
-
} else if (autoStart) {
|
|
199
|
-
startTimeRef.current = Date.now() - elapsedSeconds * 1000
|
|
200
|
-
setIsRunning(true)
|
|
201
|
-
} else {
|
|
202
|
-
startTimeRef.current = null
|
|
203
|
-
}
|
|
125
|
+
// Animate progress smoothly to starting position
|
|
126
|
+
// For new cycles (no elapsed time), animate from current position to 0%
|
|
127
|
+
// For resumed cycles, animate to the appropriate progress percentage
|
|
128
|
+
const initialProgress =
|
|
129
|
+
elapsedSeconds > 0 ? (elapsedSeconds / maxTimeSeconds) * 100 : 0
|
|
130
|
+
if (elapsedSeconds === 0) {
|
|
131
|
+
progressInterpolator.setTarget([0])
|
|
204
132
|
} else {
|
|
205
|
-
// Count-up mode: start from elapsed time
|
|
206
|
-
setRemainingTime(elapsedSeconds)
|
|
207
|
-
|
|
208
|
-
// For count-up mode, progress is based on minute steps
|
|
209
|
-
const initialProgress = ((elapsedSeconds / 60) % 1) * 100
|
|
210
133
|
progressInterpolator.setTarget([initialProgress])
|
|
134
|
+
}
|
|
211
135
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
136
|
+
if (remainingSeconds === 0) {
|
|
137
|
+
setIsRunning(false)
|
|
138
|
+
startTimeRef.current = null
|
|
139
|
+
// Trigger completion callback immediately if time is already up
|
|
140
|
+
if (onCycleEnd) {
|
|
141
|
+
setTimeout(() => onCycleEnd(), 0)
|
|
217
142
|
}
|
|
143
|
+
} else if (autoStart) {
|
|
144
|
+
startTimeRef.current = Date.now() - elapsedSeconds * 1000
|
|
145
|
+
setIsRunning(true)
|
|
146
|
+
} else {
|
|
147
|
+
startTimeRef.current = null
|
|
218
148
|
}
|
|
219
149
|
},
|
|
220
150
|
[autoStart, onCycleEnd, progressInterpolator],
|
|
@@ -229,32 +159,11 @@ export const CycleTimer = externalizeComponent(
|
|
|
229
159
|
// Calculate exact progress position and smoothly animate to it when pausing
|
|
230
160
|
// This ensures the visual progress matches the actual elapsed time
|
|
231
161
|
const totalElapsed = pausedTimeRef.current / 1000
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
// Count-down mode
|
|
235
|
-
const exactProgress = Math.min(100, (totalElapsed / maxTime) * 100)
|
|
236
|
-
progressInterpolator.setTarget([exactProgress])
|
|
237
|
-
} else {
|
|
238
|
-
// Count-up mode: progress based on minute steps
|
|
239
|
-
const exactProgress = ((totalElapsed / 60) % 1) * 100
|
|
240
|
-
progressInterpolator.setTarget([exactProgress])
|
|
241
|
-
}
|
|
162
|
+
const exactProgress = Math.min(100, (totalElapsed / maxTime) * 100)
|
|
163
|
+
progressInterpolator.setTarget([exactProgress])
|
|
242
164
|
}
|
|
243
165
|
setIsRunning(false)
|
|
244
166
|
setIsPausedState(true)
|
|
245
|
-
|
|
246
|
-
// Trigger brief pause animation
|
|
247
|
-
setShowPauseAnimation(true)
|
|
248
|
-
|
|
249
|
-
// Clear any existing timeout
|
|
250
|
-
if (pauseAnimationTimeoutRef.current) {
|
|
251
|
-
clearTimeout(pauseAnimationTimeoutRef.current)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Reset animation after longer duration
|
|
255
|
-
pauseAnimationTimeoutRef.current = setTimeout(() => {
|
|
256
|
-
setShowPauseAnimation(false)
|
|
257
|
-
}, 800) // 800ms smooth animation
|
|
258
167
|
}, [isRunning, maxTime, progressInterpolator])
|
|
259
168
|
|
|
260
169
|
const resume = useCallback(() => {
|
|
@@ -269,49 +178,6 @@ export const CycleTimer = externalizeComponent(
|
|
|
269
178
|
return isPausedState
|
|
270
179
|
}, [isPausedState])
|
|
271
180
|
|
|
272
|
-
// Handle error state changes
|
|
273
|
-
useEffect(() => {
|
|
274
|
-
if (hasError) {
|
|
275
|
-
// Error occurred - pause timer if running and remember state
|
|
276
|
-
if (isRunning && !isPausedState) {
|
|
277
|
-
setWasRunningBeforeError(true)
|
|
278
|
-
pause()
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Trigger brief error animation
|
|
282
|
-
setShowErrorAnimation(true)
|
|
283
|
-
|
|
284
|
-
// Clear any existing timeout
|
|
285
|
-
if (errorAnimationTimeoutRef.current) {
|
|
286
|
-
clearTimeout(errorAnimationTimeoutRef.current)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Reset animation after longer duration
|
|
290
|
-
errorAnimationTimeoutRef.current = setTimeout(() => {
|
|
291
|
-
setShowErrorAnimation(false)
|
|
292
|
-
}, 600) // 600ms smooth animation
|
|
293
|
-
} else {
|
|
294
|
-
// Error resolved - resume if was running before error
|
|
295
|
-
if (wasRunningBeforeError && isPausedState) {
|
|
296
|
-
setWasRunningBeforeError(false)
|
|
297
|
-
resume()
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Clear error animation if error is resolved
|
|
301
|
-
setShowErrorAnimation(false)
|
|
302
|
-
if (errorAnimationTimeoutRef.current) {
|
|
303
|
-
clearTimeout(errorAnimationTimeoutRef.current)
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}, [
|
|
307
|
-
hasError,
|
|
308
|
-
isRunning,
|
|
309
|
-
isPausedState,
|
|
310
|
-
wasRunningBeforeError,
|
|
311
|
-
pause,
|
|
312
|
-
resume,
|
|
313
|
-
])
|
|
314
|
-
|
|
315
181
|
// Call onCycleComplete immediately to provide the timer control functions
|
|
316
182
|
useEffect(() => {
|
|
317
183
|
let isMounted = true
|
|
@@ -336,39 +202,30 @@ export const CycleTimer = externalizeComponent(
|
|
|
336
202
|
if (isRunning) {
|
|
337
203
|
// Single animation frame loop that handles both time updates and progress
|
|
338
204
|
const updateTimer = () => {
|
|
339
|
-
if (startTimeRef.current) {
|
|
205
|
+
if (startTimeRef.current && maxTime > 0) {
|
|
340
206
|
const now = Date.now()
|
|
341
207
|
const elapsed =
|
|
342
208
|
(now - startTimeRef.current + pausedTimeRef.current) / 1000
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
setTimeout(() => onCycleEnd(), 0)
|
|
362
|
-
}
|
|
363
|
-
return
|
|
209
|
+
const remaining = Math.max(0, maxTime - elapsed)
|
|
210
|
+
|
|
211
|
+
// Update remaining time based on timestamp calculation
|
|
212
|
+
setRemainingTime(Math.ceil(remaining))
|
|
213
|
+
|
|
214
|
+
// Smoothly animate progress based on elapsed time for fluid visual feedback
|
|
215
|
+
const progress = Math.min(100, (elapsed / maxTime) * 100)
|
|
216
|
+
progressInterpolator.setTarget([progress])
|
|
217
|
+
|
|
218
|
+
if (remaining <= 0) {
|
|
219
|
+
setIsRunning(false)
|
|
220
|
+
startTimeRef.current = null
|
|
221
|
+
setRemainingTime(0)
|
|
222
|
+
// Animate to 100% completion with smooth spring transition
|
|
223
|
+
progressInterpolator.setTarget([100])
|
|
224
|
+
// Call onCycleEnd when timer reaches zero to notify about completion
|
|
225
|
+
if (onCycleEnd) {
|
|
226
|
+
setTimeout(() => onCycleEnd(), 0)
|
|
364
227
|
}
|
|
365
|
-
|
|
366
|
-
// Count-up mode
|
|
367
|
-
setRemainingTime(Math.floor(elapsed))
|
|
368
|
-
|
|
369
|
-
// For count-up mode, progress completes every minute (0-100% per minute)
|
|
370
|
-
const progress = ((elapsed / 60) % 1) * 100
|
|
371
|
-
progressInterpolator.setTarget([progress])
|
|
228
|
+
return
|
|
372
229
|
}
|
|
373
230
|
|
|
374
231
|
// Continue animation loop while running
|
|
@@ -412,31 +269,12 @@ export const CycleTimer = externalizeComponent(
|
|
|
412
269
|
}
|
|
413
270
|
}, [progressInterpolator])
|
|
414
271
|
|
|
415
|
-
// Cleanup animation timeouts on unmount
|
|
416
|
-
useEffect(() => {
|
|
417
|
-
return () => {
|
|
418
|
-
if (pauseAnimationTimeoutRef.current) {
|
|
419
|
-
clearTimeout(pauseAnimationTimeoutRef.current)
|
|
420
|
-
}
|
|
421
|
-
if (errorAnimationTimeoutRef.current) {
|
|
422
|
-
clearTimeout(errorAnimationTimeoutRef.current)
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}, [])
|
|
426
|
-
|
|
427
272
|
// Keep interpolator synchronized with static progress when timer is stopped
|
|
428
273
|
// Ensures correct visual state when component initializes or timer stops
|
|
429
274
|
useEffect(() => {
|
|
430
|
-
if (!isRunning && !isPausedState) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const staticProgress = ((maxTime - remainingTime) / maxTime) * 100
|
|
434
|
-
progressInterpolator.setTarget([staticProgress])
|
|
435
|
-
} else if (maxTime === null) {
|
|
436
|
-
// Count-up mode
|
|
437
|
-
const staticProgress = ((remainingTime / 60) % 1) * 100
|
|
438
|
-
progressInterpolator.setTarget([staticProgress])
|
|
439
|
-
}
|
|
275
|
+
if (!isRunning && !isPausedState && maxTime > 0) {
|
|
276
|
+
const staticProgress = ((maxTime - remainingTime) / maxTime) * 100
|
|
277
|
+
progressInterpolator.setTarget([staticProgress])
|
|
440
278
|
}
|
|
441
279
|
}, [
|
|
442
280
|
isRunning,
|
|
@@ -463,92 +301,92 @@ export const CycleTimer = externalizeComponent(
|
|
|
463
301
|
sx={{
|
|
464
302
|
display: "flex",
|
|
465
303
|
alignItems: "center",
|
|
466
|
-
|
|
467
|
-
gap: 1, // 8px gap between circle and text
|
|
304
|
+
gap: 0.125, // Minimal gap - 1px
|
|
468
305
|
}}
|
|
469
306
|
>
|
|
470
|
-
{/* Animated progress
|
|
307
|
+
{/* Animated progress gauge icon */}
|
|
471
308
|
<Box
|
|
472
309
|
sx={{
|
|
473
|
-
|
|
474
|
-
|
|
310
|
+
position: "relative",
|
|
311
|
+
width: 40,
|
|
312
|
+
height: 40,
|
|
475
313
|
display: "flex",
|
|
476
314
|
alignItems: "center",
|
|
477
315
|
justifyContent: "center",
|
|
478
|
-
|
|
479
|
-
|
|
316
|
+
borderRadius: "50%",
|
|
317
|
+
overflow: "visible",
|
|
480
318
|
}}
|
|
481
319
|
>
|
|
482
|
-
<
|
|
483
|
-
width=
|
|
484
|
-
height=
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
{
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
320
|
+
<Gauge
|
|
321
|
+
width={40}
|
|
322
|
+
height={40}
|
|
323
|
+
value={progressValue}
|
|
324
|
+
valueMin={0}
|
|
325
|
+
valueMax={100}
|
|
326
|
+
innerRadius="70%"
|
|
327
|
+
outerRadius="95%"
|
|
328
|
+
skipAnimation={true}
|
|
329
|
+
sx={{
|
|
330
|
+
opacity: isPausedState ? 0.6 : 1,
|
|
331
|
+
transition: "opacity 0.2s ease",
|
|
332
|
+
[`& .MuiGauge-valueArc`]: {
|
|
333
|
+
fill: theme.palette.success.main,
|
|
334
|
+
},
|
|
335
|
+
[`& .MuiGauge-referenceArc`]: {
|
|
336
|
+
fill: theme.palette.success.main,
|
|
337
|
+
opacity: 0.3,
|
|
338
|
+
},
|
|
339
|
+
[`& .MuiGauge-valueText`]: {
|
|
340
|
+
display: "none",
|
|
341
|
+
},
|
|
342
|
+
[`& .MuiGauge-text`]: {
|
|
343
|
+
display: "none",
|
|
344
|
+
},
|
|
345
|
+
[`& text`]: {
|
|
346
|
+
display: "none",
|
|
347
|
+
},
|
|
348
|
+
// Hide any inner circle elements that might flash
|
|
349
|
+
[`& .MuiGauge-referenceArcBackground`]: {
|
|
350
|
+
display: "none",
|
|
351
|
+
},
|
|
352
|
+
[`& .MuiGauge-valueArcBackground`]: {
|
|
353
|
+
display: "none",
|
|
354
|
+
},
|
|
355
|
+
[`& circle`]: {
|
|
356
|
+
display: "none",
|
|
357
|
+
},
|
|
358
|
+
}}
|
|
359
|
+
/>
|
|
360
|
+
|
|
361
|
+
{/* Inner circle overlay to prevent flashing */}
|
|
362
|
+
<Box
|
|
363
|
+
sx={{
|
|
364
|
+
position: "absolute",
|
|
365
|
+
top: "50%",
|
|
366
|
+
left: "50%",
|
|
367
|
+
transform: "translate(-50%, -50%)",
|
|
368
|
+
width: 13,
|
|
369
|
+
height: 13,
|
|
370
|
+
borderRadius: "50%",
|
|
371
|
+
backgroundColor: theme.palette.background?.paper || "white",
|
|
372
|
+
pointerEvents: "none",
|
|
373
|
+
}}
|
|
374
|
+
/>
|
|
528
375
|
</Box>
|
|
529
376
|
|
|
530
377
|
{/* Timer text display */}
|
|
531
378
|
<Typography
|
|
532
379
|
variant="body2"
|
|
533
380
|
sx={{
|
|
534
|
-
color:
|
|
535
|
-
? theme.palette.error.light
|
|
536
|
-
: theme.palette.text.primary,
|
|
381
|
+
color: theme.palette.text.primary,
|
|
537
382
|
fontSize: "14px",
|
|
538
|
-
transition: "color 0.5s ease-out",
|
|
539
383
|
}}
|
|
540
384
|
>
|
|
541
|
-
{
|
|
542
|
-
?
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
? // Compact mode: show remaining time with "min." suffix
|
|
547
|
-
`${formatTime(remainingTime)} ${t("CycleTimer.Time.lb", { time: "" }).replace(/\s*$/, "")}`
|
|
548
|
-
: // Full mode: show "remaining / of total min." format
|
|
549
|
-
`${formatTime(remainingTime)} / ${t("CycleTimer.Time.lb", { time: formatTime(maxTime) })}`
|
|
550
|
-
: // Count-up mode: show elapsed time only
|
|
551
|
-
formatTime(remainingTime)}
|
|
385
|
+
{compact
|
|
386
|
+
? // Compact mode: show only remaining time
|
|
387
|
+
formatTime(remainingTime)
|
|
388
|
+
: // Full mode: show "remaining / of total min." format
|
|
389
|
+
`${formatTime(remainingTime)} / ${t("CycleTimer.Time.lb", { time: formatTime(maxTime) })}`}
|
|
552
390
|
</Typography>
|
|
553
391
|
</Box>
|
|
554
392
|
)
|
|
@@ -577,13 +415,10 @@ export const CycleTimer = externalizeComponent(
|
|
|
577
415
|
outerRadius="90%"
|
|
578
416
|
skipAnimation={true}
|
|
579
417
|
sx={{
|
|
580
|
-
opacity:
|
|
581
|
-
transition: "opacity 0.
|
|
418
|
+
opacity: isPausedState ? 0.6 : 1,
|
|
419
|
+
transition: "opacity 0.2s ease",
|
|
582
420
|
[`& .MuiGauge-valueArc`]: {
|
|
583
|
-
fill:
|
|
584
|
-
? theme.palette.error.light
|
|
585
|
-
: theme.palette.success.main,
|
|
586
|
-
transition: "fill 0.5s ease-out",
|
|
421
|
+
fill: theme.palette.success.main,
|
|
587
422
|
},
|
|
588
423
|
[`& .MuiGauge-referenceArc`]: {
|
|
589
424
|
fill: "white",
|
|
@@ -611,102 +446,42 @@ export const CycleTimer = externalizeComponent(
|
|
|
611
446
|
gap: 1,
|
|
612
447
|
}}
|
|
613
448
|
>
|
|
614
|
-
{/* "remaining time" label
|
|
615
|
-
<
|
|
449
|
+
{/* "remaining time" label */}
|
|
450
|
+
<Typography
|
|
451
|
+
variant="body2"
|
|
616
452
|
sx={{
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
alignItems: "center",
|
|
620
|
-
justifyContent: "center",
|
|
453
|
+
fontSize: "12px",
|
|
454
|
+
color: theme.palette.text.secondary,
|
|
621
455
|
marginBottom: 0.5,
|
|
622
456
|
}}
|
|
623
457
|
>
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
timeout={300}
|
|
627
|
-
>
|
|
628
|
-
<Typography
|
|
629
|
-
variant="body2"
|
|
630
|
-
sx={{
|
|
631
|
-
fontSize: "12px",
|
|
632
|
-
color: theme.palette.text.secondary,
|
|
633
|
-
}}
|
|
634
|
-
>
|
|
635
|
-
{t("CycleTimer.RemainingTime.lb")}
|
|
636
|
-
</Typography>
|
|
637
|
-
</Fade>
|
|
638
|
-
</Box>
|
|
458
|
+
{t("CycleTimer.RemainingTime.lb")}
|
|
459
|
+
</Typography>
|
|
639
460
|
|
|
640
|
-
{/* Main timer display
|
|
641
|
-
<
|
|
461
|
+
{/* Main timer display */}
|
|
462
|
+
<Typography
|
|
463
|
+
variant="h1"
|
|
642
464
|
sx={{
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
justifyContent: "center",
|
|
465
|
+
fontSize: "48px",
|
|
466
|
+
fontWeight: 500,
|
|
467
|
+
color: theme.palette.text.primary,
|
|
468
|
+
lineHeight: 1,
|
|
648
469
|
marginBottom: 0.5,
|
|
649
470
|
}}
|
|
650
471
|
>
|
|
651
|
-
{
|
|
652
|
-
|
|
653
|
-
<Typography
|
|
654
|
-
variant="h3"
|
|
655
|
-
sx={{
|
|
656
|
-
position: "absolute",
|
|
657
|
-
fontSize: "40px",
|
|
658
|
-
fontWeight: 400,
|
|
659
|
-
color: "#FFFFFF",
|
|
660
|
-
lineHeight: "116.7%",
|
|
661
|
-
}}
|
|
662
|
-
>
|
|
663
|
-
{t("CycleTimer.Error.lb", "Error")}
|
|
664
|
-
</Typography>
|
|
665
|
-
</Fade>
|
|
666
|
-
|
|
667
|
-
{/* Normal timer text */}
|
|
668
|
-
<Fade in={!hasError} timeout={500}>
|
|
669
|
-
<Typography
|
|
670
|
-
variant="h1"
|
|
671
|
-
sx={{
|
|
672
|
-
position: "absolute",
|
|
673
|
-
fontSize: "48px",
|
|
674
|
-
fontWeight: 500,
|
|
675
|
-
color: theme.palette.text.primary,
|
|
676
|
-
lineHeight: 1,
|
|
677
|
-
}}
|
|
678
|
-
>
|
|
679
|
-
{formatTime(remainingTime)}
|
|
680
|
-
</Typography>
|
|
681
|
-
</Fade>
|
|
682
|
-
</Box>
|
|
472
|
+
{formatTime(remainingTime)}
|
|
473
|
+
</Typography>
|
|
683
474
|
|
|
684
|
-
{/* Total time display
|
|
685
|
-
<
|
|
475
|
+
{/* Total time display */}
|
|
476
|
+
<Typography
|
|
477
|
+
variant="body2"
|
|
686
478
|
sx={{
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
alignItems: "center",
|
|
690
|
-
justifyContent: "center",
|
|
479
|
+
fontSize: "12px",
|
|
480
|
+
color: theme.palette.text.secondary,
|
|
691
481
|
}}
|
|
692
482
|
>
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
timeout={300}
|
|
696
|
-
>
|
|
697
|
-
<Typography
|
|
698
|
-
variant="body2"
|
|
699
|
-
sx={{
|
|
700
|
-
fontSize: "12px",
|
|
701
|
-
color: theme.palette.text.secondary,
|
|
702
|
-
}}
|
|
703
|
-
>
|
|
704
|
-
{maxTime !== null
|
|
705
|
-
? t("CycleTimer.OfTime.lb", { time: formatTime(maxTime) })
|
|
706
|
-
: ""}
|
|
707
|
-
</Typography>
|
|
708
|
-
</Fade>
|
|
709
|
-
</Box>
|
|
483
|
+
{t("CycleTimer.OfTime.lb", { time: formatTime(maxTime) })}
|
|
484
|
+
</Typography>
|
|
710
485
|
</Box>
|
|
711
486
|
</Box>
|
|
712
487
|
)
|