@wandelbots/wandelbots-js-react-components 1.14.0 → 1.15.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wandelbots/wandelbots-js-react-components",
3
- "version": "1.14.0",
3
+ "version": "1.15.1",
4
4
  "description": "React UI toolkit for building applications on top of the Wandelbots platform",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -104,7 +104,7 @@
104
104
  "@mui/icons-material": "^5.16.7",
105
105
  "@mui/lab": "^5.0.0-alpha.173",
106
106
  "@shikijs/monaco": "^1.16.1",
107
- "@wandelbots/wandelbots-js": "^1.8.6",
107
+ "@wandelbots/wandelbots-js": "^1.9.0",
108
108
  "i18next-browser-languagedetector": "^8.0.0",
109
109
  "lodash-es": "^4.17.21",
110
110
  "mobx": "^6.13.1",
@@ -0,0 +1,24 @@
1
+ import { Stack } from "@mui/material"
2
+ import React from "react"
3
+
4
+ export const TransparentOverlay = (
5
+ props: React.ComponentProps<typeof Stack>,
6
+ ) => {
7
+ return (
8
+ <Stack
9
+ position="absolute"
10
+ left={0}
11
+ top={0}
12
+ width="100%"
13
+ height="100%"
14
+ alignItems="center"
15
+ justifyContent="center"
16
+ sx={{
17
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
18
+ backdropFilter: "blur(10px)",
19
+ zIndex: 100,
20
+ }}
21
+ {...props}
22
+ />
23
+ )
24
+ }
@@ -18,7 +18,6 @@ import { JoggingVelocitySlider } from "./JoggingVelocitySlider"
18
18
  import { useReaction } from "../utils/hooks"
19
19
  import { JoggingCartesianValues } from "./JoggingCartesianValues"
20
20
  import { JoggingJointLimitDetector } from "./JoggingJointLimitDetector"
21
- import { useEffect } from "react"
22
21
 
23
22
  type JoggingCartesianOpts = {
24
23
  axis: "x" | "y" | "z"
@@ -50,31 +49,6 @@ export const JoggingCartesianTab = observer(
50
49
  { fireImmediately: true } as any,
51
50
  )
52
51
 
53
- useEffect(() => {
54
- // Start in increment mode with no websockets open
55
- store.jogger.setJoggingMode("increment")
56
-
57
- window.addEventListener("blur", disconnectJogger)
58
-
59
- return () => {
60
- window.removeEventListener("blur", disconnectJogger)
61
- }
62
- }, [])
63
-
64
- async function connectJogger() {
65
- store.jogger.setJoggingMode(
66
- store.activeDiscreteIncrement ? "increment" : "cartesian",
67
- {
68
- tcpId: store.selectedTcpId,
69
- coordSystemId: store.activeCoordSystemId,
70
- },
71
- )
72
- }
73
-
74
- async function disconnectJogger() {
75
- store.jogger.setJoggingMode("increment")
76
- }
77
-
78
52
  async function runIncrementalCartesianJog(
79
53
  opts: JoggingCartesianOpts,
80
54
  increment: DiscreteIncrementOption,
@@ -111,8 +85,6 @@ export const JoggingCartesianTab = observer(
111
85
  async function startCartesianJogging(opts: JoggingCartesianOpts) {
112
86
  if (store.isLocked) return
113
87
 
114
- connectJogger()
115
-
116
88
  if (store.activeDiscreteIncrement) {
117
89
  return runIncrementalCartesianJog(opts, store.activeDiscreteIncrement)
118
90
  }
@@ -171,7 +143,7 @@ export const JoggingCartesianTab = observer(
171
143
  }
172
144
 
173
145
  return (
174
- <Stack onMouseEnter={connectJogger} onMouseLeave={disconnectJogger}>
146
+ <Stack>
175
147
  {/* Show Wandelscript string for the current coords */}
176
148
  <JoggingCartesianValues store={store} />
177
149
 
@@ -9,25 +9,6 @@ import { useEffect } from "react"
9
9
 
10
10
  export const JoggingJointTab = observer(
11
11
  ({ store }: { store: JoggingStore }) => {
12
- useEffect(() => {
13
- // Start in increment mode with no websockets open
14
- store.jogger.setJoggingMode("increment")
15
-
16
- window.addEventListener("blur", disconnectJogger)
17
-
18
- return () => {
19
- window.removeEventListener("blur", disconnectJogger)
20
- }
21
- }, [])
22
-
23
- async function connectJogger() {
24
- store.jogger.setJoggingMode("joint")
25
- }
26
-
27
- async function disconnectJogger() {
28
- store.jogger.setJoggingMode("increment")
29
- }
30
-
31
12
  async function startJointJogging(opts: {
32
13
  joint: number
33
14
  direction: "-" | "+"
@@ -44,7 +25,7 @@ export const JoggingJointTab = observer(
44
25
  }
45
26
 
46
27
  return (
47
- <Stack onMouseEnter={connectJogger} onMouseLeave={disconnectJogger}>
28
+ <Stack>
48
29
  <JoggingJointValues store={store} />
49
30
  <Stack>
50
31
  {store.jogger.motionStream.joints.map((joint) => {
@@ -15,7 +15,7 @@ export const JoggingJointValues = observer(
15
15
  const { joints } =
16
16
  store.jogger.motionStream.rapidlyChangingMotionState.state
17
17
  .joint_position
18
- return `{${joints.map((j) => parseFloat(j.toFixed(4))).join(", ")}}`
18
+ return `[${joints.map((j) => parseFloat(j.toFixed(4))).join(", ")}]`
19
19
  }
20
20
 
21
21
  useAnimationFrame(() => {
@@ -1,14 +1,16 @@
1
- import { Paper, Stack, Tab, Tabs } from "@mui/material"
1
+ import { Button, Paper, Stack, Tab, Tabs } from "@mui/material"
2
2
  import { observer, useLocalObservable } from "mobx-react-lite"
3
3
  import { useEffect } from "react"
4
4
  import { JoggingCartesianTab } from "./JoggingCartesianTab"
5
5
  import { JoggingJointTab } from "./JoggingJointTab"
6
6
  import { JoggingStore } from "./JoggingStore"
7
7
  import { LoadingCover } from "../LoadingCover"
8
+ import { TransparentOverlay } from "../TransparentOverlay"
8
9
  import { runInAction } from "mobx"
9
10
  import { NovaClient } from "@wandelbots/wandelbots-js"
10
11
  import { externalizeComponent } from "../../externalizeComponent"
11
12
  import { isString } from "lodash-es"
13
+ import { useReaction } from "../utils/hooks"
12
14
 
13
15
  export type JoggingPanelProps = {
14
16
  /** Either an existing NovaClient or the base url of a deployed Nova instance */
@@ -19,6 +21,8 @@ export type JoggingPanelProps = {
19
21
  onSetup?: (store: JoggingStore) => void
20
22
  /** Any children will go at the bottom of the panel under the default contents */
21
23
  children?: React.ReactNode
24
+ /** Set this to true to disable jogging UI temporarily e.g. when a program is executing */
25
+ locked?: boolean
22
26
  }
23
27
 
24
28
  /**
@@ -62,101 +66,142 @@ export const JoggingPanel = externalizeComponent(
62
66
  }
63
67
  }, [props.nova])
64
68
 
65
- // Set correct jogging mode on jogger based on user selections
66
69
  useEffect(() => {
67
- if (!state.joggingStore) return
70
+ const store = state.joggingStore
71
+ if (!store) return
68
72
 
69
- const {
70
- currentTab,
71
- selectedTcpId,
72
- activeCoordSystemId,
73
- activeDiscreteIncrement,
74
- } = state.joggingStore
73
+ if (props.locked) {
74
+ store.locks.add("external")
75
+ } else {
76
+ store.locks.delete("external")
77
+ }
78
+ }, [props.locked])
75
79
 
76
- if (currentTab.id !== "cartesian" && currentTab.id !== "joint") return
80
+ return (
81
+ <Stack
82
+ sx={{
83
+ maxWidth: "460px",
84
+ minWidth: "350px",
85
+ overflowY: "auto",
86
+ position: "relative",
87
+ height: "100%",
88
+ }}
89
+ >
90
+ <Paper
91
+ sx={{
92
+ height: "100%",
93
+ }}
94
+ >
95
+ {state.joggingStore ? (
96
+ <JoggingPanelInner store={state.joggingStore}>
97
+ {props.children}
98
+ </JoggingPanelInner>
99
+ ) : (
100
+ <LoadingCover
101
+ message="Loading jogging"
102
+ error={state.loadingError}
103
+ />
104
+ )}
105
+ </Paper>
106
+ </Stack>
107
+ )
108
+ }),
109
+ )
77
110
 
78
- const cartesianJoggingOpts = {
79
- tcpId: selectedTcpId,
80
- coordSystemId: activeCoordSystemId,
111
+ const JoggingPanelInner = observer(
112
+ ({
113
+ store,
114
+ children,
115
+ }: {
116
+ store: JoggingStore
117
+ children?: React.ReactNode
118
+ }) => {
119
+ // Jogger is only active as long as the tab is focused
120
+ useEffect(() => {
121
+ window.addEventListener("blur", store.deactivate)
122
+
123
+ return () => {
124
+ window.removeEventListener("blur", store.deactivate)
81
125
  }
126
+ })
127
+
128
+ // Update jogging mode on jogger based on user selections
129
+ useReaction(
130
+ () => [
131
+ store.currentTab,
132
+ store.selectedTcpId,
133
+ store.activeCoordSystemId,
134
+ store.activeDiscreteIncrement,
135
+ ],
136
+ () => {
137
+ if (store.activationState === "active") store.activate()
138
+ },
139
+ )
82
140
 
83
- if (activeDiscreteIncrement && currentTab.id === "cartesian") {
84
- state.joggingStore.jogger.setJoggingMode(
85
- "increment",
86
- cartesianJoggingOpts,
141
+ function renderOverlay() {
142
+ if (store.activationState === "inactive" && !store.activationError) {
143
+ return (
144
+ <TransparentOverlay>
145
+ <Button
146
+ color="primary"
147
+ variant="contained"
148
+ onClick={store.activate}
149
+ disabled={store.isLocked}
150
+ >
151
+ Activate jogging
152
+ </Button>
153
+ </TransparentOverlay>
87
154
  )
88
- } else {
89
- state.joggingStore.jogger.setJoggingMode(
90
- currentTab.id,
91
- cartesianJoggingOpts,
155
+ } else if (store.activationState === "loading" || store.activationError) {
156
+ return (
157
+ <TransparentOverlay>
158
+ <LoadingCover
159
+ message="Activating jogging"
160
+ error={store.activationError}
161
+ />
162
+ </TransparentOverlay>
92
163
  )
93
164
  }
94
- }, [
95
- state.joggingStore?.currentTab,
96
- state.joggingStore?.selectedTcpId,
97
- state.joggingStore?.activeCoordSystemId,
98
- state.joggingStore?.activeDiscreteIncrement,
99
- ])
100
-
101
- if (!state.joggingStore || state.loadingError) {
102
- return (
103
- <JoggingPanelOuter>
104
- <LoadingCover message="Loading jogging" error={state.loadingError} />
105
- </JoggingPanelOuter>
106
- )
107
165
  }
108
166
 
109
- const { joggingStore: store } = state
167
+ function renderTabContent() {
168
+ if (store.currentTab.id === "cartesian") {
169
+ return (
170
+ <>
171
+ <JoggingCartesianTab store={store} />
172
+ {children}
173
+ </>
174
+ )
175
+ } else if (store.currentTab.id === "joint") {
176
+ return (
177
+ <>
178
+ <JoggingJointTab store={store} />
179
+ {children}
180
+ </>
181
+ )
182
+ }
183
+ }
110
184
 
111
185
  return (
112
- <JoggingPanelOuter>
113
- <Stack flexGrow={1}>
114
- {/* Tab selection */}
115
- <Tabs value={store.tabIndex} onChange={store.onTabChange}>
116
- {store.tabs.map((tab) => (
117
- <Tab
118
- key={tab.id}
119
- label={tab.label}
120
- id={`jogging-tab-${tab.id}`}
121
- aria-controls={`jogging-tab-${tab.id}`}
122
- />
123
- ))}
124
- </Tabs>
125
-
126
- {/* Current tab content */}
127
- <Stack flexGrow={1}>
128
- {store.currentTab.id === "cartesian" && (
129
- <JoggingCartesianTab store={store} />
130
- )}
131
- {store.currentTab.id === "joint" && (
132
- <JoggingJointTab store={store} />
133
- )}
134
- {props.children}
135
- </Stack>
186
+ <Stack flexGrow={1} height="100%">
187
+ {/* Tab selection */}
188
+ <Tabs value={store.tabIndex} onChange={store.onTabChange}>
189
+ {store.tabs.map((tab) => (
190
+ <Tab
191
+ key={tab.id}
192
+ label={tab.label}
193
+ id={`jogging-tab-${tab.id}`}
194
+ aria-controls={`jogging-tab-${tab.id}`}
195
+ />
196
+ ))}
197
+ </Tabs>
198
+
199
+ {/* Current tab content */}
200
+ <Stack flexGrow={1} position="relative">
201
+ {renderOverlay()}
202
+ {renderTabContent()}
136
203
  </Stack>
137
- </JoggingPanelOuter>
204
+ </Stack>
138
205
  )
139
- }),
206
+ },
140
207
  )
141
-
142
- function JoggingPanelOuter({ children }: { children: React.ReactNode }) {
143
- return (
144
- <Stack
145
- sx={{
146
- maxWidth: "460px",
147
- minWidth: "350px",
148
- overflowY: "auto",
149
- position: "relative",
150
- height: "100%",
151
- }}
152
- >
153
- <Paper
154
- sx={{
155
- height: "100%",
156
- }}
157
- >
158
- {children}
159
- </Paper>
160
- </Stack>
161
- )
162
- }
@@ -1,6 +1,11 @@
1
1
  import keyBy from "lodash-es/keyBy"
2
2
  import uniqueId from "lodash-es/uniqueId"
3
- import { autorun, makeAutoObservable, type IReactionDisposer } from "mobx"
3
+ import {
4
+ autorun,
5
+ makeAutoObservable,
6
+ runInAction,
7
+ type IReactionDisposer,
8
+ } from "mobx"
4
9
  import type {
5
10
  CoordinateSystem,
6
11
  JoggerConnection,
@@ -28,6 +33,16 @@ export type IncrementOptionId = IncrementOption["id"]
28
33
  export class JoggingStore {
29
34
  selectedTabId: "cartesian" | "joint" | "debug" = "cartesian"
30
35
 
36
+ /**
37
+ * State of the jogging panel. Starts as "inactive"
38
+ */
39
+ activationState: "inactive" | "loading" | "active" = "inactive"
40
+
41
+ /**
42
+ * If an error occurred connecting to the jogging websocket
43
+ */
44
+ activationError: unknown | null = null
45
+
31
46
  /** Locks to prevent UI interactions during certain operations */
32
47
  locks = new Set<string>()
33
48
 
@@ -145,6 +160,68 @@ export class JoggingStore {
145
160
  this.jogger.dispose()
146
161
  }
147
162
 
163
+ async deactivate() {
164
+ if (this.activationState === "inactive") return
165
+ const websocket = this.jogger.activeWebsocket
166
+
167
+ this.activationState = "inactive"
168
+ this.jogger.setJoggingMode("increment")
169
+
170
+ if (websocket) {
171
+ await websocket.closed()
172
+ }
173
+ }
174
+
175
+ /** Activate the jogger with current settings */
176
+ async activate() {
177
+ const {
178
+ currentTab,
179
+ selectedTcpId,
180
+ activeCoordSystemId,
181
+ activeDiscreteIncrement,
182
+ jogger,
183
+ } = this
184
+
185
+ if (this.activationState === "loading") return
186
+
187
+ runInAction(() => {
188
+ this.activationState = "loading"
189
+ this.activationError = null
190
+ })
191
+
192
+ if (currentTab.id === "cartesian") {
193
+ const cartesianJoggingOpts = {
194
+ tcpId: selectedTcpId,
195
+ coordSystemId: activeCoordSystemId,
196
+ }
197
+
198
+ if (activeDiscreteIncrement) {
199
+ jogger.setJoggingMode("increment", cartesianJoggingOpts)
200
+ } else {
201
+ jogger.setJoggingMode("cartesian", cartesianJoggingOpts)
202
+ }
203
+ } else {
204
+ jogger.setJoggingMode("joint")
205
+ }
206
+
207
+ if (jogger.activeWebsocket) {
208
+ try {
209
+ jogger.stop()
210
+ await jogger.activeWebsocket.nextMessage()
211
+ } catch (err) {
212
+ runInAction(() => {
213
+ this.activationState = "inactive"
214
+ this.activationError = err
215
+ })
216
+ return
217
+ }
218
+ }
219
+
220
+ runInAction(() => {
221
+ this.activationState = "active"
222
+ })
223
+ }
224
+
148
225
  loadFromLocalStorage() {
149
226
  const save = tryParseJson(localStorage.getItem("joggingToolStore"))
150
227
  if (!save) return
@@ -241,6 +318,11 @@ export class JoggingStore {
241
318
  return this.coordSystemsById[this.selectedCoordSystemId]
242
319
  }
243
320
 
321
+ /**
322
+ * The id of the coordinate system to use for jogging.
323
+ * If in tool orientation, this is set to "tool", not the
324
+ * selected coordinate system.
325
+ */
244
326
  get activeCoordSystemId() {
245
327
  return this.selectedOrientation === "tool"
246
328
  ? "tool"