@wandelbots/wandelbots-js-react-components 2.42.0-pr.feature-seperate-timer.383.2e9936e → 2.42.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.
- package/dist/components/CycleTimer/DefaultVariant.d.ts.map +1 -1
- package/dist/components/CycleTimer/SmallVariant.d.ts.map +1 -1
- package/dist/components/CycleTimer/index.d.ts +5 -4
- package/dist/components/CycleTimer/index.d.ts.map +1 -1
- package/dist/components/CycleTimer/types.d.ts +3 -2
- package/dist/components/CycleTimer/types.d.ts.map +1 -1
- package/dist/components/CycleTimer/useTimerLogic.d.ts +2 -1
- package/dist/components/CycleTimer/useTimerLogic.d.ts.map +1 -1
- package/dist/components/jogging/PoseCartesianValues.d.ts +5 -3
- package/dist/components/jogging/PoseCartesianValues.d.ts.map +1 -1
- package/dist/components/jogging/PoseJointValues.d.ts +5 -3
- package/dist/components/jogging/PoseJointValues.d.ts.map +1 -1
- package/dist/index.cjs +47 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5593 -5951
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/components/CycleTimer/DefaultVariant.tsx +2 -0
- package/src/components/CycleTimer/SmallVariant.tsx +5 -2
- package/src/components/CycleTimer/index.tsx +5 -4
- package/src/components/CycleTimer/types.ts +3 -1
- package/src/components/CycleTimer/useTimerLogic.ts +96 -40
- package/src/components/CycleTimer/utils.ts +3 -3
- package/src/components/jogging/PoseCartesianValues.tsx +82 -16
- package/src/components/jogging/PoseJointValues.tsx +82 -16
- package/src/i18n/locales/de/translations.json +0 -1
- package/src/i18n/locales/en/translations.json +0 -1
- package/src/index.ts +0 -1
- package/dist/components/Timer/Timer.d.ts +0 -3
- package/dist/components/Timer/Timer.d.ts.map +0 -1
- package/dist/components/Timer/TimerDefaultVariant.d.ts +0 -10
- package/dist/components/Timer/TimerDefaultVariant.d.ts.map +0 -1
- package/dist/components/Timer/TimerSmallVariant.d.ts +0 -11
- package/dist/components/Timer/TimerSmallVariant.d.ts.map +0 -1
- package/dist/components/Timer/index.d.ts +0 -19
- package/dist/components/Timer/index.d.ts.map +0 -1
- package/dist/components/Timer/types.d.ts +0 -36
- package/dist/components/Timer/types.d.ts.map +0 -1
- package/dist/components/Timer/useTimerAnimations.d.ts +0 -11
- package/dist/components/Timer/useTimerAnimations.d.ts.map +0 -1
- package/dist/components/Timer/useTimerLogic.d.ts +0 -20
- package/dist/components/Timer/useTimerLogic.d.ts.map +0 -1
- package/dist/components/Timer/utils.d.ts +0 -9
- package/dist/components/Timer/utils.d.ts.map +0 -1
- package/src/components/Timer/Timer.ts +0 -2
- package/src/components/Timer/TimerDefaultVariant.tsx +0 -140
- package/src/components/Timer/TimerSmallVariant.tsx +0 -140
- package/src/components/Timer/index.tsx +0 -101
- package/src/components/Timer/types.ts +0 -38
- package/src/components/Timer/useTimerAnimations.ts +0 -94
- package/src/components/Timer/useTimerLogic.ts +0 -214
- package/src/components/Timer/utils.ts +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wandelbots/wandelbots-js-react-components",
|
|
3
|
-
"version": "2.42.0
|
|
3
|
+
"version": "2.42.0",
|
|
4
4
|
"description": "React UI toolkit for building applications on top of the Wandelbots platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
60
60
|
"@rollup/plugin-terser": "^0.4.4",
|
|
61
61
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
62
|
-
"@storybook/addon-docs": "^9.1.
|
|
63
|
-
"@storybook/react-vite": "^9.1.
|
|
62
|
+
"@storybook/addon-docs": "^9.1.3",
|
|
63
|
+
"@storybook/react-vite": "^9.1.3",
|
|
64
64
|
"@storybook/test-runner": "^0.23.0",
|
|
65
65
|
"@svgr/rollup": "^8.1.0",
|
|
66
66
|
"@testing-library/jest-dom": "^6.6.3",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"@vitejs/plugin-react": "^4.3.4",
|
|
73
73
|
"@wandelbots/nova-js": "^2.1.0",
|
|
74
74
|
"add": "^2.0.6",
|
|
75
|
-
"eslint-plugin-storybook": "^9.1.
|
|
75
|
+
"eslint-plugin-storybook": "^9.1.3",
|
|
76
76
|
"glob": "^11.0.1",
|
|
77
77
|
"http-server": "^14.1.1",
|
|
78
78
|
"husky": "^9.1.7",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
96
96
|
"rollup-plugin-postcss": "^4.0.2",
|
|
97
97
|
"semantic-release": "^24.2.3",
|
|
98
|
-
"storybook": "^9.1.
|
|
98
|
+
"storybook": "^9.1.3",
|
|
99
99
|
"storybook-preset-inline-svg": "^1.0.1",
|
|
100
100
|
"three": "^0.174.0",
|
|
101
101
|
"three-stdlib": "^2.35.14",
|
|
@@ -114,6 +114,7 @@ export const DefaultVariant = ({
|
|
|
114
114
|
showLabels &&
|
|
115
115
|
!hasError &&
|
|
116
116
|
currentState !== "idle" &&
|
|
117
|
+
currentState !== "countup" &&
|
|
117
118
|
currentState !== "success"
|
|
118
119
|
}
|
|
119
120
|
timeout={300}
|
|
@@ -276,6 +277,7 @@ export const DefaultVariant = ({
|
|
|
276
277
|
showLabels &&
|
|
277
278
|
!hasError &&
|
|
278
279
|
currentState !== "idle" &&
|
|
280
|
+
currentState !== "countup" &&
|
|
279
281
|
currentState !== "success"
|
|
280
282
|
}
|
|
281
283
|
timeout={300}
|
|
@@ -31,7 +31,7 @@ export const SmallVariant = ({
|
|
|
31
31
|
} = animationState
|
|
32
32
|
|
|
33
33
|
// Simple text-only mode for compact variant in certain states
|
|
34
|
-
if (compact && currentState === "idle") {
|
|
34
|
+
if (compact && (currentState === "countup" || currentState === "idle")) {
|
|
35
35
|
return (
|
|
36
36
|
<Box
|
|
37
37
|
className={className}
|
|
@@ -72,7 +72,10 @@ export const SmallVariant = ({
|
|
|
72
72
|
}}
|
|
73
73
|
>
|
|
74
74
|
{/* Animated progress ring icon */}
|
|
75
|
-
{!(
|
|
75
|
+
{!(
|
|
76
|
+
currentState === "countup" ||
|
|
77
|
+
(currentState === "idle" && compact)
|
|
78
|
+
) && (
|
|
76
79
|
<Box
|
|
77
80
|
sx={{
|
|
78
81
|
width: 20,
|
|
@@ -8,18 +8,19 @@ import { useAnimations } from "./useAnimations"
|
|
|
8
8
|
import { useTimerLogic } from "./useTimerLogic"
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* A circular gauge timer component
|
|
11
|
+
* A circular gauge timer component that shows the remaining time of a cycle or counts up
|
|
12
12
|
*
|
|
13
13
|
* Features:
|
|
14
14
|
* - Custom SVG circular gauge with 264px diameter and 40px thickness
|
|
15
|
-
* - Multiple states: idle, measuring, measured, countdown, success
|
|
15
|
+
* - Multiple states: idle, measuring, measured, countdown, countup, success
|
|
16
16
|
* - Idle state: shows "Waiting for program cycle" with transparent inner circle
|
|
17
17
|
* - Measuring state: counts up with "Cycle Time" / "measuring..." labels
|
|
18
18
|
* - Measured state: shows final time with "Cycle Time" / "determined" labels in pulsating green
|
|
19
19
|
* - Countdown mode: shows remaining time prominently, counts down to zero
|
|
20
|
+
* - Count-up mode: shows elapsed time without special labels
|
|
20
21
|
* - Success state: brief green flash after cycle completion
|
|
21
22
|
* - Displays appropriate labels based on state
|
|
22
|
-
* - Automatically counts down and triggers callback when reaching zero
|
|
23
|
+
* - Automatically counts down/up and triggers callback when reaching zero (countdown only)
|
|
23
24
|
* - Full timer control: start, pause, resume functionality
|
|
24
25
|
* - Support for starting with elapsed time (resume mid-cycle)
|
|
25
26
|
* - Error state support: pauses timer and shows error styling (red color)
|
|
@@ -27,7 +28,7 @@ import { useTimerLogic } from "./useTimerLogic"
|
|
|
27
28
|
* - Pulsating text animation for completed measuring state
|
|
28
29
|
* - Fully localized with i18next
|
|
29
30
|
* - Material-UI theming integration
|
|
30
|
-
* - Small variant with animated progress icon (gauge border only) next to text
|
|
31
|
+
* - Small variant with animated progress icon (gauge border only) next to text or simple text-only mode
|
|
31
32
|
*/
|
|
32
33
|
export const CycleTimer = externalizeComponent(
|
|
33
34
|
observer(
|
|
@@ -3,11 +3,13 @@ export type CycleTimerState =
|
|
|
3
3
|
| "measuring" // Counting up without max time, showing "Cycle Time" / "measuring..."
|
|
4
4
|
| "measured" // Completed measuring state showing "Cycle Time" / "determined" with pulsating green text
|
|
5
5
|
| "countdown" // Counting down with max time
|
|
6
|
+
| "countup" // Simple count up without special text
|
|
6
7
|
| "success" // Brief success state after cycle completion
|
|
7
8
|
|
|
8
9
|
export interface CycleTimerControls {
|
|
9
|
-
startNewCycle: (maxTimeSeconds
|
|
10
|
+
startNewCycle: (maxTimeSeconds?: number, elapsedSeconds?: number) => void
|
|
10
11
|
startMeasuring: (elapsedSeconds?: number) => void
|
|
12
|
+
startCountUp: (elapsedSeconds?: number) => void
|
|
11
13
|
setIdle: () => void
|
|
12
14
|
completeMeasuring: () => void
|
|
13
15
|
pause: () => void
|
|
@@ -88,66 +88,114 @@ export const useTimerLogic = ({
|
|
|
88
88
|
[autoStart, progressInterpolator],
|
|
89
89
|
)
|
|
90
90
|
|
|
91
|
-
const
|
|
92
|
-
(
|
|
93
|
-
|
|
94
|
-
setTimerState((prev) => ({ ...prev, isRunning: false }))
|
|
95
|
-
startTimeRef.current = null
|
|
96
|
-
|
|
91
|
+
const startCountUp = useCallback(
|
|
92
|
+
(elapsedSeconds: number = 0) => {
|
|
93
|
+
const initialProgress = ((elapsedSeconds / 60) % 1) * 100
|
|
97
94
|
setTimerState((prev) => ({
|
|
98
95
|
...prev,
|
|
99
|
-
currentState: "
|
|
100
|
-
maxTime:
|
|
96
|
+
currentState: "countup",
|
|
97
|
+
maxTime: null,
|
|
98
|
+
remainingTime: elapsedSeconds,
|
|
101
99
|
isPausedState: false,
|
|
100
|
+
currentProgress: initialProgress, // Immediately set progress
|
|
102
101
|
}))
|
|
103
102
|
pausedTimeRef.current = 0
|
|
104
103
|
|
|
105
|
-
//
|
|
106
|
-
const remainingSeconds = Math.max(0, maxTimeSeconds - elapsedSeconds)
|
|
107
|
-
const initialProgress =
|
|
108
|
-
elapsedSeconds > 0 ? (elapsedSeconds / maxTimeSeconds) * 100 : 0
|
|
104
|
+
progressInterpolator.setImmediate([initialProgress]) // Use setImmediate for instant reset
|
|
109
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"
|
|
110
123
|
setTimerState((prev) => ({
|
|
111
124
|
...prev,
|
|
112
|
-
|
|
113
|
-
|
|
125
|
+
currentState: newState,
|
|
126
|
+
maxTime: maxTimeSeconds ?? null,
|
|
127
|
+
isPausedState: false,
|
|
114
128
|
}))
|
|
129
|
+
pausedTimeRef.current = 0
|
|
115
130
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
123
158
|
}
|
|
124
|
-
} else if (autoStart) {
|
|
125
|
-
setTimeout(() => {
|
|
126
|
-
startTimeRef.current = Date.now() - elapsedSeconds * 1000
|
|
127
|
-
setTimerState((prev) => ({ ...prev, isRunning: true }))
|
|
128
|
-
}, 0)
|
|
129
159
|
} else {
|
|
130
|
-
|
|
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
|
+
}
|
|
131
178
|
}
|
|
132
179
|
},
|
|
133
180
|
[autoStart, onCycleEnd, progressInterpolator],
|
|
134
181
|
)
|
|
135
182
|
|
|
136
183
|
const completeMeasuring = useCallback(() => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
184
|
+
if (timerState.currentState === "measuring") {
|
|
185
|
+
setTimerState((prev) => ({
|
|
186
|
+
...prev,
|
|
187
|
+
isRunning: false,
|
|
188
|
+
currentState: "measured",
|
|
189
|
+
}))
|
|
190
|
+
startTimeRef.current = null
|
|
144
191
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
192
|
+
onStartPulsating(() => {
|
|
193
|
+
if (onMeasuringComplete) {
|
|
194
|
+
onMeasuringComplete()
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
}, [timerState.currentState, onStartPulsating, onMeasuringComplete])
|
|
151
199
|
|
|
152
200
|
const pause = useCallback(() => {
|
|
153
201
|
if (startTimeRef.current && timerState.isRunning) {
|
|
@@ -268,6 +316,13 @@ export const useTimerLogic = ({
|
|
|
268
316
|
}))
|
|
269
317
|
const progress = ((elapsed / 60) % 1) * 100
|
|
270
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])
|
|
271
326
|
}
|
|
272
327
|
|
|
273
328
|
if (timerState.isRunning) {
|
|
@@ -320,6 +375,7 @@ export const useTimerLogic = ({
|
|
|
320
375
|
controls: {
|
|
321
376
|
startNewCycle,
|
|
322
377
|
startMeasuring,
|
|
378
|
+
startCountUp,
|
|
323
379
|
setIdle,
|
|
324
380
|
completeMeasuring,
|
|
325
381
|
pause,
|
|
@@ -25,8 +25,8 @@ export const calculateProgress = (
|
|
|
25
25
|
return Math.min(100, (elapsed / maxTime) * 100)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
if (currentState === "measuring") {
|
|
29
|
-
//
|
|
28
|
+
if (currentState === "measuring" || currentState === "countup") {
|
|
29
|
+
// Count-up modes: progress based on minute steps (0-100% per minute)
|
|
30
30
|
return ((remainingTime / 60) % 1) * 100
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -45,7 +45,7 @@ export const calculateExactProgress = (
|
|
|
45
45
|
return Math.min(100, (totalElapsed / maxTime) * 100)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
if (currentState === "measuring") {
|
|
48
|
+
if (currentState === "measuring" || currentState === "countup") {
|
|
49
49
|
return ((totalElapsed / 60) % 1) * 100
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -1,41 +1,102 @@
|
|
|
1
|
-
import { Button, Stack } from "@mui/material"
|
|
1
|
+
import { Button, Stack, Typography } from "@mui/material"
|
|
2
2
|
import { poseToWandelscriptString } from "@wandelbots/nova-js"
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ConnectedMotionGroup,
|
|
5
|
+
MotionGroupStateResponse,
|
|
6
|
+
MotionStreamConnection,
|
|
7
|
+
} from "@wandelbots/nova-js/v1"
|
|
4
8
|
import { observer } from "mobx-react-lite"
|
|
5
|
-
import { useState } from "react"
|
|
6
|
-
import { externalizeComponent } from "../../externalizeComponent"
|
|
9
|
+
import { useRef, useState } from "react"
|
|
7
10
|
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
|
+
}
|
|
8
34
|
|
|
9
35
|
export type PoseCartesianValuesProps = {
|
|
10
|
-
|
|
36
|
+
/** Either a MotionStreamConnection or ConnectedMotionGroup */
|
|
37
|
+
motionStream?: MotionStreamConnection
|
|
38
|
+
connectedMotionGroup?: ConnectedMotionGroup
|
|
11
39
|
showCopyButton?: boolean
|
|
12
40
|
}
|
|
13
41
|
|
|
14
|
-
export const PoseCartesianValues =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
}
|
|
18
68
|
|
|
19
69
|
const handleCopy = async () => {
|
|
20
70
|
try {
|
|
21
|
-
await navigator.clipboard.writeText(
|
|
71
|
+
await navigator.clipboard.writeText(getCurrentPoseString())
|
|
22
72
|
setCopyMessage("Copied!")
|
|
23
|
-
setTimeout(() => setCopyMessage(
|
|
73
|
+
setTimeout(() => setCopyMessage(""), 2000)
|
|
24
74
|
} catch {
|
|
25
75
|
setCopyMessage("Copy failed")
|
|
26
|
-
setTimeout(() => setCopyMessage(
|
|
76
|
+
setTimeout(() => setCopyMessage(""), 2000)
|
|
27
77
|
}
|
|
28
78
|
}
|
|
29
79
|
|
|
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
|
+
|
|
30
92
|
return (
|
|
31
93
|
<Stack
|
|
32
94
|
direction="row"
|
|
33
95
|
alignItems="center"
|
|
34
96
|
spacing={1}
|
|
35
97
|
sx={{ flexGrow: 1, minWidth: 0, overflow: "hidden" }}
|
|
36
|
-
data-testid="pose-cartesian-values"
|
|
37
98
|
>
|
|
38
|
-
<CopyableText value={
|
|
99
|
+
<CopyableText value={getCurrentPoseString()} ref={poseHolderRef} />
|
|
39
100
|
{showCopyButton && (
|
|
40
101
|
<Button
|
|
41
102
|
variant="contained"
|
|
@@ -44,10 +105,15 @@ export const PoseCartesianValues = externalizeComponent(
|
|
|
44
105
|
onClick={handleCopy}
|
|
45
106
|
sx={{ flexShrink: 0 }}
|
|
46
107
|
>
|
|
47
|
-
|
|
108
|
+
Copy
|
|
48
109
|
</Button>
|
|
49
110
|
)}
|
|
111
|
+
{copyMessage && (
|
|
112
|
+
<Typography variant="caption" color="success.main">
|
|
113
|
+
{copyMessage}
|
|
114
|
+
</Typography>
|
|
115
|
+
)}
|
|
50
116
|
</Stack>
|
|
51
117
|
)
|
|
52
|
-
}
|
|
118
|
+
},
|
|
53
119
|
)
|
|
@@ -1,40 +1,101 @@
|
|
|
1
|
-
import { Button, Stack } from "@mui/material"
|
|
2
|
-
import type {
|
|
1
|
+
import { Button, Stack, Typography } from "@mui/material"
|
|
2
|
+
import type {
|
|
3
|
+
ConnectedMotionGroup,
|
|
4
|
+
MotionGroupStateResponse,
|
|
5
|
+
MotionStreamConnection,
|
|
6
|
+
} from "@wandelbots/nova-js/v1"
|
|
3
7
|
import { observer } from "mobx-react-lite"
|
|
4
|
-
import { useState } from "react"
|
|
5
|
-
import { externalizeComponent } from "../../externalizeComponent"
|
|
8
|
+
import { useRef, useState } from "react"
|
|
6
9
|
import { CopyableText } from "../CopyableText"
|
|
10
|
+
import { useAnimationFrame } from "../utils/hooks"
|
|
11
|
+
|
|
12
|
+
/** Minimal interface for what PoseJointValues needs from motion stream */
|
|
13
|
+
type MotionStateProvider = {
|
|
14
|
+
rapidlyChangingMotionState: MotionGroupStateResponse
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Creates a motion state provider from either a MotionStreamConnection or ConnectedMotionGroup */
|
|
18
|
+
function createMotionStateProvider(
|
|
19
|
+
motionStream?: MotionStreamConnection,
|
|
20
|
+
connectedMotionGroup?: ConnectedMotionGroup,
|
|
21
|
+
): MotionStateProvider | undefined {
|
|
22
|
+
if (motionStream) {
|
|
23
|
+
return motionStream
|
|
24
|
+
}
|
|
25
|
+
if (connectedMotionGroup) {
|
|
26
|
+
return {
|
|
27
|
+
rapidlyChangingMotionState:
|
|
28
|
+
connectedMotionGroup.rapidlyChangingMotionState,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
7
33
|
|
|
8
34
|
export type PoseJointValuesProps = {
|
|
9
|
-
|
|
35
|
+
/** Either a MotionStreamConnection or ConnectedMotionGroup */
|
|
36
|
+
motionStream?: MotionStreamConnection
|
|
37
|
+
connectedMotionGroup?: ConnectedMotionGroup
|
|
10
38
|
showCopyButton?: boolean
|
|
11
39
|
}
|
|
12
40
|
|
|
13
|
-
export const PoseJointValues =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
41
|
+
export const PoseJointValues = observer(
|
|
42
|
+
({
|
|
43
|
+
motionStream,
|
|
44
|
+
connectedMotionGroup,
|
|
45
|
+
showCopyButton = false,
|
|
46
|
+
}: PoseJointValuesProps) => {
|
|
47
|
+
const poseHolderRef = useRef<HTMLDivElement>(null)
|
|
48
|
+
const [copyMessage, setCopyMessage] = useState("")
|
|
49
|
+
|
|
50
|
+
const activeMotionStream = createMotionStateProvider(
|
|
51
|
+
motionStream,
|
|
52
|
+
connectedMotionGroup,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if (!activeMotionStream) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"PoseJointValues requires either motionStream or connectedMotionGroup prop",
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getCurrentPoseString() {
|
|
62
|
+
if (!activeMotionStream) return ""
|
|
63
|
+
const { joints } =
|
|
64
|
+
activeMotionStream.rapidlyChangingMotionState.state.joint_position
|
|
65
|
+
return `[${joints.map((j: number) => parseFloat(j.toFixed(4))).join(", ")}]`
|
|
66
|
+
}
|
|
17
67
|
|
|
18
68
|
const handleCopy = async () => {
|
|
19
69
|
try {
|
|
20
|
-
await navigator.clipboard.writeText(
|
|
70
|
+
await navigator.clipboard.writeText(getCurrentPoseString())
|
|
21
71
|
setCopyMessage("Copied!")
|
|
22
|
-
setTimeout(() => setCopyMessage(
|
|
72
|
+
setTimeout(() => setCopyMessage(""), 2000)
|
|
23
73
|
} catch {
|
|
24
74
|
setCopyMessage("Copy failed")
|
|
25
|
-
setTimeout(() => setCopyMessage(
|
|
75
|
+
setTimeout(() => setCopyMessage(""), 2000)
|
|
26
76
|
}
|
|
27
77
|
}
|
|
28
78
|
|
|
79
|
+
useAnimationFrame(() => {
|
|
80
|
+
if (!poseHolderRef.current) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const newPoseContent = getCurrentPoseString()
|
|
85
|
+
if (poseHolderRef.current.textContent === newPoseContent) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
poseHolderRef.current.textContent = newPoseContent
|
|
89
|
+
})
|
|
90
|
+
|
|
29
91
|
return (
|
|
30
92
|
<Stack
|
|
31
93
|
direction="row"
|
|
32
94
|
alignItems="center"
|
|
33
95
|
spacing={1}
|
|
34
96
|
sx={{ flexGrow: 1, minWidth: 0, overflow: "hidden" }}
|
|
35
|
-
data-testid="pose-joint-values"
|
|
36
97
|
>
|
|
37
|
-
<CopyableText value={
|
|
98
|
+
<CopyableText value={getCurrentPoseString()} ref={poseHolderRef} />
|
|
38
99
|
{showCopyButton && (
|
|
39
100
|
<Button
|
|
40
101
|
variant="contained"
|
|
@@ -43,10 +104,15 @@ export const PoseJointValues = externalizeComponent(
|
|
|
43
104
|
onClick={handleCopy}
|
|
44
105
|
sx={{ flexShrink: 0 }}
|
|
45
106
|
>
|
|
46
|
-
|
|
107
|
+
Copy
|
|
47
108
|
</Button>
|
|
48
109
|
)}
|
|
110
|
+
{copyMessage && (
|
|
111
|
+
<Typography variant="caption" color="success.main">
|
|
112
|
+
{copyMessage}
|
|
113
|
+
</Typography>
|
|
114
|
+
)}
|
|
49
115
|
</Stack>
|
|
50
116
|
)
|
|
51
|
-
}
|
|
117
|
+
},
|
|
52
118
|
)
|
|
@@ -52,7 +52,6 @@
|
|
|
52
52
|
"CycleTimer.CycleTime.lb": "Zykluszeit",
|
|
53
53
|
"CycleTimer.Measuring.lb": "wird gemessen...",
|
|
54
54
|
"CycleTimer.Determined.lb": "bestimmt",
|
|
55
|
-
"Timer.error": "Fehler",
|
|
56
55
|
"ProgramControl.Start.bt": "Start",
|
|
57
56
|
"ProgramControl.Resume.bt": "Weiter",
|
|
58
57
|
"ProgramControl.Retry.bt": "Wiederholen",
|
|
@@ -53,7 +53,6 @@
|
|
|
53
53
|
"CycleTimer.CycleTime.lb": "Cycle Time",
|
|
54
54
|
"CycleTimer.Measuring.lb": "measuring...",
|
|
55
55
|
"CycleTimer.Determined.lb": "determined",
|
|
56
|
-
"Timer.error": "Error",
|
|
57
56
|
"ProgramControl.Start.bt": "Start",
|
|
58
57
|
"ProgramControl.Resume.bt": "Resume",
|
|
59
58
|
"ProgramControl.Retry.bt": "Retry",
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,6 @@ export * from "./components/3d-viewport/SafetyZonesRenderer"
|
|
|
4
4
|
export * from "./components/3d-viewport/TrajectoryRenderer"
|
|
5
5
|
export * from "./components/AppHeader"
|
|
6
6
|
export * from "./components/CycleTimer"
|
|
7
|
-
export * from "./components/Timer"
|
|
8
7
|
export * from "./components/DataGrid"
|
|
9
8
|
export * from "./components/jogging/JoggingCartesianAxisControl"
|
|
10
9
|
export * from "./components/jogging/JoggingJointRotationControl"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Timer.d.ts","sourceRoot":"","sources":["../../../src/components/Timer/Timer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { TimerState, TimerAnimationState } from "./types";
|
|
2
|
-
interface TimerDefaultVariantProps {
|
|
3
|
-
timerState: TimerState;
|
|
4
|
-
animationState: TimerAnimationState;
|
|
5
|
-
hasError: boolean;
|
|
6
|
-
className?: string;
|
|
7
|
-
}
|
|
8
|
-
export declare const TimerDefaultVariant: ({ timerState, animationState, hasError, className, }: TimerDefaultVariantProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
-
export {};
|
|
10
|
-
//# sourceMappingURL=TimerDefaultVariant.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TimerDefaultVariant.d.ts","sourceRoot":"","sources":["../../../src/components/Timer/TimerDefaultVariant.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAG9D,UAAU,wBAAwB;IAChC,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE,mBAAmB,CAAA;IACnC,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,mBAAmB,GAAI,sDAKjC,wBAAwB,4CAwH1B,CAAA"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { TimerState, TimerAnimationState } from "./types";
|
|
2
|
-
interface TimerSmallVariantProps {
|
|
3
|
-
timerState: TimerState;
|
|
4
|
-
animationState: TimerAnimationState;
|
|
5
|
-
hasError: boolean;
|
|
6
|
-
compact: boolean;
|
|
7
|
-
className?: string;
|
|
8
|
-
}
|
|
9
|
-
export declare const TimerSmallVariant: ({ timerState, animationState, hasError, compact, className, }: TimerSmallVariantProps) => import("react/jsx-runtime").JSX.Element;
|
|
10
|
-
export {};
|
|
11
|
-
//# sourceMappingURL=TimerSmallVariant.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TimerSmallVariant.d.ts","sourceRoot":"","sources":["../../../src/components/Timer/TimerSmallVariant.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAG9D,UAAU,sBAAsB;IAC9B,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE,mBAAmB,CAAA;IACnC,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,iBAAiB,GAAI,+DAM/B,sBAAsB,4CAuHxB,CAAA"}
|