@wandelbots/wandelbots-js-react-components 2.42.0-pr.feat-add-universalrobots-ur12e.387.df3cdf0 → 2.42.0-pr.feature-seperate-timer.383.2e9936e

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 (54) hide show
  1. package/dist/components/CycleTimer/DefaultVariant.d.ts.map +1 -1
  2. package/dist/components/CycleTimer/SmallVariant.d.ts.map +1 -1
  3. package/dist/components/CycleTimer/index.d.ts +4 -5
  4. package/dist/components/CycleTimer/index.d.ts.map +1 -1
  5. package/dist/components/CycleTimer/types.d.ts +2 -3
  6. package/dist/components/CycleTimer/types.d.ts.map +1 -1
  7. package/dist/components/CycleTimer/useTimerLogic.d.ts +1 -2
  8. package/dist/components/CycleTimer/useTimerLogic.d.ts.map +1 -1
  9. package/dist/components/Timer/Timer.d.ts +3 -0
  10. package/dist/components/Timer/Timer.d.ts.map +1 -0
  11. package/dist/components/Timer/TimerDefaultVariant.d.ts +10 -0
  12. package/dist/components/Timer/TimerDefaultVariant.d.ts.map +1 -0
  13. package/dist/components/Timer/TimerSmallVariant.d.ts +11 -0
  14. package/dist/components/Timer/TimerSmallVariant.d.ts.map +1 -0
  15. package/dist/components/Timer/index.d.ts +19 -0
  16. package/dist/components/Timer/index.d.ts.map +1 -0
  17. package/dist/components/Timer/types.d.ts +36 -0
  18. package/dist/components/Timer/types.d.ts.map +1 -0
  19. package/dist/components/Timer/useTimerAnimations.d.ts +11 -0
  20. package/dist/components/Timer/useTimerAnimations.d.ts.map +1 -0
  21. package/dist/components/Timer/useTimerLogic.d.ts +20 -0
  22. package/dist/components/Timer/useTimerLogic.d.ts.map +1 -0
  23. package/dist/components/Timer/utils.d.ts +9 -0
  24. package/dist/components/Timer/utils.d.ts.map +1 -0
  25. package/dist/components/jogging/PoseCartesianValues.d.ts +3 -5
  26. package/dist/components/jogging/PoseCartesianValues.d.ts.map +1 -1
  27. package/dist/components/jogging/PoseJointValues.d.ts +3 -5
  28. package/dist/components/jogging/PoseJointValues.d.ts.map +1 -1
  29. package/dist/index.cjs +47 -47
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +5947 -5589
  34. package/dist/index.js.map +1 -1
  35. package/package.json +5 -5
  36. package/src/components/CycleTimer/DefaultVariant.tsx +0 -2
  37. package/src/components/CycleTimer/SmallVariant.tsx +2 -5
  38. package/src/components/CycleTimer/index.tsx +4 -5
  39. package/src/components/CycleTimer/types.ts +1 -3
  40. package/src/components/CycleTimer/useTimerLogic.ts +40 -96
  41. package/src/components/CycleTimer/utils.ts +3 -3
  42. package/src/components/Timer/Timer.ts +2 -0
  43. package/src/components/Timer/TimerDefaultVariant.tsx +140 -0
  44. package/src/components/Timer/TimerSmallVariant.tsx +140 -0
  45. package/src/components/Timer/index.tsx +101 -0
  46. package/src/components/Timer/types.ts +38 -0
  47. package/src/components/Timer/useTimerAnimations.ts +94 -0
  48. package/src/components/Timer/useTimerLogic.ts +214 -0
  49. package/src/components/Timer/utils.ts +15 -0
  50. package/src/components/jogging/PoseCartesianValues.tsx +16 -82
  51. package/src/components/jogging/PoseJointValues.tsx +16 -82
  52. package/src/i18n/locales/de/translations.json +1 -0
  53. package/src/i18n/locales/en/translations.json +1 -0
  54. package/src/index.ts +1 -0
@@ -0,0 +1,101 @@
1
+ import { observer } from "mobx-react-lite"
2
+ import { useEffect } from "react"
3
+ import { externalizeComponent } from "../../externalizeComponent"
4
+ import { TimerDefaultVariant } from "./TimerDefaultVariant"
5
+ import { TimerSmallVariant } from "./TimerSmallVariant"
6
+ import type { TimerProps } from "./types"
7
+ import { useTimerAnimations } from "./useTimerAnimations"
8
+ import { useTimerLogic } from "./useTimerLogic"
9
+
10
+ /**
11
+ * A simple count-up timer component with visual progress indication
12
+ *
13
+ * Features:
14
+ * - Count-up timer that tracks elapsed time
15
+ * - Visual progress gauge that cycles every minute
16
+ * - Two display variants: large circular gauge (default) or small icon with text
17
+ * - Full timer control: start, pause, resume, reset functionality
18
+ * - Support for starting with elapsed time (resume mid-session)
19
+ * - Error state support: pauses timer and shows error styling
20
+ * - Smooth progress animations with spring physics
21
+ * - Fully localized with i18next
22
+ * - Material-UI theming integration
23
+ */
24
+ export const Timer = externalizeComponent(
25
+ observer(
26
+ ({
27
+ onTimerReady,
28
+ autoStart = true,
29
+ variant = "default",
30
+ compact = false,
31
+ className,
32
+ hasError = false,
33
+ }: TimerProps) => {
34
+ // Initialize animation hooks
35
+ const {
36
+ animationState,
37
+ triggerPauseAnimation,
38
+ triggerErrorAnimation,
39
+ clearErrorAnimation,
40
+ setInitialAnimationState,
41
+ cleanup,
42
+ } = useTimerAnimations()
43
+
44
+ // Initialize timer logic
45
+ const { timerState, controls } = useTimerLogic({
46
+ autoStart,
47
+ hasError,
48
+ onPauseAnimation: triggerPauseAnimation,
49
+ onErrorAnimation: triggerErrorAnimation,
50
+ onClearErrorAnimation: clearErrorAnimation,
51
+ })
52
+
53
+ // Set initial animation state
54
+ useEffect(() => {
55
+ setInitialAnimationState()
56
+ }, [setInitialAnimationState])
57
+
58
+ // Provide controls to parent component
59
+ useEffect(() => {
60
+ let isMounted = true
61
+ const timeoutId = setTimeout(() => {
62
+ if (isMounted) {
63
+ onTimerReady(controls)
64
+ }
65
+ }, 0)
66
+
67
+ return () => {
68
+ isMounted = false
69
+ clearTimeout(timeoutId)
70
+ }
71
+ }, [onTimerReady, controls])
72
+
73
+ // Cleanup on unmount
74
+ useEffect(() => {
75
+ return cleanup
76
+ }, [cleanup])
77
+
78
+ // Render appropriate variant
79
+ if (variant === "small") {
80
+ return (
81
+ <TimerSmallVariant
82
+ timerState={timerState}
83
+ animationState={animationState}
84
+ hasError={hasError}
85
+ compact={compact}
86
+ className={className}
87
+ />
88
+ )
89
+ }
90
+
91
+ return (
92
+ <TimerDefaultVariant
93
+ timerState={timerState}
94
+ animationState={animationState}
95
+ hasError={hasError}
96
+ className={className}
97
+ />
98
+ )
99
+ },
100
+ ),
101
+ )
@@ -0,0 +1,38 @@
1
+ export interface TimerControls {
2
+ start: (elapsedSeconds?: number) => void
3
+ pause: () => void
4
+ resume: () => void
5
+ reset: () => void
6
+ isPaused: () => boolean
7
+ }
8
+
9
+ export interface TimerProps {
10
+ /**
11
+ * Callback that receives the timer control functions
12
+ */
13
+ onTimerReady: (controls: TimerControls) => void
14
+ /** Whether the timer should start automatically when initialized */
15
+ autoStart?: boolean
16
+ /** Visual variant of the timer */
17
+ variant?: "default" | "small"
18
+ /** For small variant: whether to show compact display */
19
+ compact?: boolean
20
+ /** Additional CSS classes */
21
+ className?: string
22
+ /** Whether the timer is in an error state (pauses timer and shows error styling) */
23
+ hasError?: boolean
24
+ }
25
+
26
+ export interface TimerState {
27
+ elapsedTime: number
28
+ isRunning: boolean
29
+ isPausedState: boolean
30
+ currentProgress: number
31
+ wasRunningBeforeError: boolean
32
+ }
33
+
34
+ export interface TimerAnimationState {
35
+ showPauseAnimation: boolean
36
+ showErrorAnimation: boolean
37
+ showMainText: boolean
38
+ }
@@ -0,0 +1,94 @@
1
+ import { useCallback, useRef, useState } from "react"
2
+ import type { TimerAnimationState } from "./types"
3
+
4
+ export const useTimerAnimations = () => {
5
+ const [animationState, setAnimationState] = useState<TimerAnimationState>({
6
+ showPauseAnimation: false,
7
+ showErrorAnimation: false,
8
+ showMainText: true,
9
+ })
10
+
11
+ // Refs for managing timeouts
12
+ const pauseAnimationTimeoutRef = useRef<NodeJS.Timeout | null>(null)
13
+ const errorAnimationTimeoutRef = useRef<NodeJS.Timeout | null>(null)
14
+ const fadeTimeoutRef = useRef<NodeJS.Timeout | null>(null)
15
+
16
+ const triggerPauseAnimation = useCallback(() => {
17
+ setAnimationState((prev) => ({ ...prev, showPauseAnimation: true }))
18
+
19
+ if (pauseAnimationTimeoutRef.current) {
20
+ clearTimeout(pauseAnimationTimeoutRef.current)
21
+ }
22
+
23
+ pauseAnimationTimeoutRef.current = setTimeout(() => {
24
+ setAnimationState((prev) => ({ ...prev, showPauseAnimation: false }))
25
+ }, 800)
26
+ }, [])
27
+
28
+ const triggerErrorAnimation = useCallback(() => {
29
+ setAnimationState((prev) => ({ ...prev, showErrorAnimation: true }))
30
+
31
+ if (errorAnimationTimeoutRef.current) {
32
+ clearTimeout(errorAnimationTimeoutRef.current)
33
+ }
34
+
35
+ errorAnimationTimeoutRef.current = setTimeout(() => {
36
+ setAnimationState((prev) => ({ ...prev, showErrorAnimation: false }))
37
+ }, 600)
38
+ }, [])
39
+
40
+ const clearErrorAnimation = useCallback(() => {
41
+ setAnimationState((prev) => ({ ...prev, showErrorAnimation: false }))
42
+ if (errorAnimationTimeoutRef.current) {
43
+ clearTimeout(errorAnimationTimeoutRef.current)
44
+ }
45
+ }, [])
46
+
47
+ const triggerFadeTransition = useCallback(() => {
48
+ setAnimationState((prev) => ({
49
+ ...prev,
50
+ showMainText: false,
51
+ }))
52
+
53
+ if (fadeTimeoutRef.current) {
54
+ clearTimeout(fadeTimeoutRef.current)
55
+ }
56
+
57
+ fadeTimeoutRef.current = setTimeout(() => {
58
+ setAnimationState((prev) => ({
59
+ ...prev,
60
+ showMainText: true,
61
+ }))
62
+ }, 200)
63
+ }, [])
64
+
65
+ const setInitialAnimationState = useCallback(() => {
66
+ setAnimationState((prev) => ({
67
+ ...prev,
68
+ showMainText: true,
69
+ }))
70
+ }, [])
71
+
72
+ // Cleanup function
73
+ const cleanup = useCallback(() => {
74
+ if (pauseAnimationTimeoutRef.current) {
75
+ clearTimeout(pauseAnimationTimeoutRef.current)
76
+ }
77
+ if (errorAnimationTimeoutRef.current) {
78
+ clearTimeout(errorAnimationTimeoutRef.current)
79
+ }
80
+ if (fadeTimeoutRef.current) {
81
+ clearTimeout(fadeTimeoutRef.current)
82
+ }
83
+ }, [])
84
+
85
+ return {
86
+ animationState,
87
+ triggerPauseAnimation,
88
+ triggerErrorAnimation,
89
+ clearErrorAnimation,
90
+ triggerFadeTransition,
91
+ setInitialAnimationState,
92
+ cleanup,
93
+ }
94
+ }
@@ -0,0 +1,214 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react"
2
+ import { useInterpolation } from "../utils/interpolation"
3
+ import type { TimerState } from "./types"
4
+
5
+ interface UseTimerLogicProps {
6
+ autoStart: boolean
7
+ hasError: boolean
8
+ onPauseAnimation: () => void
9
+ onErrorAnimation: () => void
10
+ onClearErrorAnimation: () => void
11
+ }
12
+
13
+ export const useTimerLogic = ({
14
+ autoStart,
15
+ hasError,
16
+ onPauseAnimation,
17
+ onErrorAnimation,
18
+ onClearErrorAnimation,
19
+ }: UseTimerLogicProps) => {
20
+ const [timerState, setTimerState] = useState<TimerState>({
21
+ elapsedTime: 0,
22
+ isRunning: false,
23
+ isPausedState: false,
24
+ currentProgress: 0,
25
+ wasRunningBeforeError: false,
26
+ })
27
+
28
+ // Timer-related refs
29
+ const animationRef = useRef<number | null>(null)
30
+ const startTimeRef = useRef<number | null>(null)
31
+ const pausedTimeRef = useRef<number>(0)
32
+ const lastProgressRef = useRef<number>(0)
33
+
34
+ // Spring-based interpolator for smooth gauge progress animations
35
+ const [progressInterpolator] = useInterpolation([0], {
36
+ tension: 80,
37
+ friction: 18,
38
+ onChange: ([progress]) => {
39
+ setTimerState((prev) => ({ ...prev, currentProgress: progress }))
40
+ },
41
+ })
42
+
43
+ const start = useCallback(
44
+ (elapsedSeconds: number = 0) => {
45
+ const initialProgress = ((elapsedSeconds / 60) % 1) * 100
46
+ setTimerState((prev) => ({
47
+ ...prev,
48
+ elapsedTime: elapsedSeconds,
49
+ isPausedState: false,
50
+ currentProgress: initialProgress,
51
+ }))
52
+ pausedTimeRef.current = 0
53
+ lastProgressRef.current = initialProgress
54
+
55
+ progressInterpolator.setImmediate([initialProgress])
56
+
57
+ if (autoStart) {
58
+ startTimeRef.current = Date.now() - elapsedSeconds * 1000
59
+ setTimerState((prev) => ({ ...prev, isRunning: true }))
60
+ } else {
61
+ startTimeRef.current = null
62
+ }
63
+ },
64
+ [autoStart, progressInterpolator],
65
+ )
66
+
67
+ const pause = useCallback(() => {
68
+ if (startTimeRef.current && timerState.isRunning) {
69
+ const now = Date.now()
70
+ const totalElapsed = (now - startTimeRef.current) / 1000 + pausedTimeRef.current
71
+ const currentProgress = ((totalElapsed / 60) % 1) * 100
72
+ progressInterpolator.setTarget([currentProgress])
73
+
74
+ setTimerState((prev) => ({
75
+ ...prev,
76
+ elapsedTime: Math.floor(totalElapsed),
77
+ }))
78
+ }
79
+
80
+ setTimerState((prev) => ({
81
+ ...prev,
82
+ isRunning: false,
83
+ isPausedState: true,
84
+ }))
85
+ onPauseAnimation()
86
+ }, [
87
+ timerState.isRunning,
88
+ progressInterpolator,
89
+ onPauseAnimation,
90
+ ])
91
+
92
+ const resume = useCallback(() => {
93
+ if (timerState.isPausedState) {
94
+ pausedTimeRef.current = timerState.elapsedTime
95
+ startTimeRef.current = Date.now()
96
+ setTimerState((prev) => ({
97
+ ...prev,
98
+ isRunning: true,
99
+ isPausedState: false,
100
+ }))
101
+ }
102
+ }, [timerState.isPausedState, timerState.elapsedTime])
103
+
104
+ const reset = useCallback(() => {
105
+ setTimerState((prev) => ({
106
+ ...prev,
107
+ elapsedTime: 0,
108
+ isRunning: false,
109
+ isPausedState: false,
110
+ currentProgress: 0,
111
+ }))
112
+ pausedTimeRef.current = 0
113
+ startTimeRef.current = null
114
+ lastProgressRef.current = 0
115
+ progressInterpolator.setImmediate([0])
116
+ }, [progressInterpolator])
117
+
118
+ const isPaused = useCallback(() => {
119
+ return timerState.isPausedState
120
+ }, [timerState.isPausedState])
121
+
122
+ // Handle error state changes
123
+ useEffect(() => {
124
+ if (hasError) {
125
+ if (timerState.isRunning) {
126
+ setTimerState((prev) => ({ ...prev, wasRunningBeforeError: true }))
127
+ pause()
128
+ }
129
+ onErrorAnimation()
130
+ } else {
131
+ if (timerState.wasRunningBeforeError && !timerState.isRunning) {
132
+ setTimerState((prev) => ({ ...prev, wasRunningBeforeError: false }))
133
+ resume()
134
+ }
135
+ onClearErrorAnimation()
136
+ }
137
+ }, [
138
+ hasError,
139
+ timerState.isRunning,
140
+ timerState.wasRunningBeforeError,
141
+ pause,
142
+ resume,
143
+ onErrorAnimation,
144
+ onClearErrorAnimation,
145
+ ])
146
+
147
+ // Main timer loop
148
+ useEffect(() => {
149
+ if (timerState.isRunning) {
150
+ const updateTimer = () => {
151
+ if (startTimeRef.current) {
152
+ const now = Date.now()
153
+ const totalElapsed = (now - startTimeRef.current) / 1000 + pausedTimeRef.current
154
+ const currentProgress = ((totalElapsed / 60) % 1) * 100
155
+
156
+ setTimerState((prev) => ({
157
+ ...prev,
158
+ elapsedTime: Math.floor(totalElapsed),
159
+ }))
160
+
161
+ // Only update progress interpolator if progress changed significantly
162
+ const progressDiff = Math.abs(currentProgress - lastProgressRef.current)
163
+ if (progressDiff > 0.1) {
164
+ progressInterpolator.setTarget([currentProgress])
165
+ lastProgressRef.current = currentProgress
166
+ }
167
+ }
168
+ animationRef.current = requestAnimationFrame(updateTimer)
169
+ }
170
+ animationRef.current = requestAnimationFrame(updateTimer)
171
+ } else {
172
+ if (animationRef.current) {
173
+ cancelAnimationFrame(animationRef.current)
174
+ animationRef.current = null
175
+ }
176
+ }
177
+
178
+ return () => {
179
+ if (animationRef.current) {
180
+ cancelAnimationFrame(animationRef.current)
181
+ animationRef.current = null
182
+ }
183
+ }
184
+ }, [timerState.isRunning, progressInterpolator])
185
+
186
+ // Interpolation animation loop
187
+ useEffect(() => {
188
+ let interpolationAnimationId: number | null = null
189
+
190
+ const animateInterpolation = () => {
191
+ progressInterpolator.update()
192
+ interpolationAnimationId = requestAnimationFrame(animateInterpolation)
193
+ }
194
+
195
+ interpolationAnimationId = requestAnimationFrame(animateInterpolation)
196
+
197
+ return () => {
198
+ if (interpolationAnimationId) {
199
+ cancelAnimationFrame(interpolationAnimationId)
200
+ }
201
+ }
202
+ }, [progressInterpolator])
203
+
204
+ return {
205
+ timerState,
206
+ controls: {
207
+ start,
208
+ pause,
209
+ resume,
210
+ reset,
211
+ isPaused,
212
+ },
213
+ }
214
+ }
@@ -0,0 +1,15 @@
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 timer (minute-based cycles)
12
+ */
13
+ export const calculateTimerProgress = (elapsedTime: number): number => {
14
+ return ((elapsedTime / 60) % 1) * 100
15
+ }
@@ -1,102 +1,41 @@
1
- import { Button, Stack, Typography } from "@mui/material"
1
+ import { Button, Stack } from "@mui/material"
2
2
  import { poseToWandelscriptString } from "@wandelbots/nova-js"
3
- import type {
4
- ConnectedMotionGroup,
5
- MotionGroupStateResponse,
6
- MotionStreamConnection,
7
- } from "@wandelbots/nova-js/v1"
3
+ import type { TcpPose } from "@wandelbots/nova-js/v1"
8
4
  import { observer } from "mobx-react-lite"
9
- import { useRef, useState } from "react"
5
+ import { useState } from "react"
6
+ import { externalizeComponent } from "../../externalizeComponent"
10
7
  import { CopyableText } from "../CopyableText"
11
- import { useAnimationFrame } from "../utils/hooks"
12
-
13
- /** Minimal interface for what PoseCartesianValues needs from motion stream */
14
- type MotionStateProvider = {
15
- rapidlyChangingMotionState: MotionGroupStateResponse
16
- }
17
-
18
- /** Creates a motion state provider from either a MotionStreamConnection or ConnectedMotionGroup */
19
- function createMotionStateProvider(
20
- motionStream?: MotionStreamConnection,
21
- connectedMotionGroup?: ConnectedMotionGroup,
22
- ): MotionStateProvider | undefined {
23
- if (motionStream) {
24
- return motionStream
25
- }
26
- if (connectedMotionGroup) {
27
- return {
28
- rapidlyChangingMotionState:
29
- connectedMotionGroup.rapidlyChangingMotionState,
30
- }
31
- }
32
- return undefined
33
- }
34
8
 
35
9
  export type PoseCartesianValuesProps = {
36
- /** Either a MotionStreamConnection or ConnectedMotionGroup */
37
- motionStream?: MotionStreamConnection
38
- connectedMotionGroup?: ConnectedMotionGroup
10
+ tcpPose: TcpPose
39
11
  showCopyButton?: boolean
40
12
  }
41
13
 
42
- export const PoseCartesianValues = observer(
43
- ({
44
- motionStream,
45
- connectedMotionGroup,
46
- showCopyButton = false,
47
- }: PoseCartesianValuesProps) => {
48
- const poseHolderRef = useRef<HTMLDivElement>(null)
49
- const [copyMessage, setCopyMessage] = useState("")
50
-
51
- const activeMotionStream = createMotionStateProvider(
52
- motionStream,
53
- connectedMotionGroup,
54
- )
55
-
56
- if (!activeMotionStream) {
57
- throw new Error(
58
- "PoseCartesianValues requires either motionStream or connectedMotionGroup prop",
59
- )
60
- }
61
-
62
- function getCurrentPoseString() {
63
- if (!activeMotionStream) return ""
64
- const tcpPose = activeMotionStream.rapidlyChangingMotionState.tcp_pose
65
- if (!tcpPose) return ""
66
- return poseToWandelscriptString(tcpPose)
67
- }
14
+ export const PoseCartesianValues = externalizeComponent(
15
+ observer(({ tcpPose, showCopyButton = false }: PoseCartesianValuesProps) => {
16
+ const [copyMessage, setCopyMessage] = useState<string | null>(null)
17
+ const poseString = poseToWandelscriptString(tcpPose)
68
18
 
69
19
  const handleCopy = async () => {
70
20
  try {
71
- await navigator.clipboard.writeText(getCurrentPoseString())
21
+ await navigator.clipboard.writeText(poseString)
72
22
  setCopyMessage("Copied!")
73
- setTimeout(() => setCopyMessage(""), 2000)
23
+ setTimeout(() => setCopyMessage(null), 2000)
74
24
  } catch {
75
25
  setCopyMessage("Copy failed")
76
- setTimeout(() => setCopyMessage(""), 2000)
26
+ setTimeout(() => setCopyMessage(null), 2000)
77
27
  }
78
28
  }
79
29
 
80
- useAnimationFrame(() => {
81
- if (!poseHolderRef.current) {
82
- return
83
- }
84
- const newPoseContent = getCurrentPoseString()
85
- if (poseHolderRef.current.textContent === newPoseContent) {
86
- return
87
- }
88
-
89
- poseHolderRef.current.textContent = newPoseContent
90
- })
91
-
92
30
  return (
93
31
  <Stack
94
32
  direction="row"
95
33
  alignItems="center"
96
34
  spacing={1}
97
35
  sx={{ flexGrow: 1, minWidth: 0, overflow: "hidden" }}
36
+ data-testid="pose-cartesian-values"
98
37
  >
99
- <CopyableText value={getCurrentPoseString()} ref={poseHolderRef} />
38
+ <CopyableText value={poseString} />
100
39
  {showCopyButton && (
101
40
  <Button
102
41
  variant="contained"
@@ -105,15 +44,10 @@ export const PoseCartesianValues = observer(
105
44
  onClick={handleCopy}
106
45
  sx={{ flexShrink: 0 }}
107
46
  >
108
- Copy
47
+ { copyMessage ? copyMessage : "Copy"}
109
48
  </Button>
110
49
  )}
111
- {copyMessage && (
112
- <Typography variant="caption" color="success.main">
113
- {copyMessage}
114
- </Typography>
115
- )}
116
50
  </Stack>
117
51
  )
118
- },
52
+ }),
119
53
  )