@wandelbots/wandelbots-js-react-components 2.34.0 → 2.34.1-pr.feature-robot-precondition-list.372.c1de8ff
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/AppHeader.d.ts +34 -0
- package/dist/components/AppHeader.d.ts.map +1 -0
- package/dist/components/CycleTimer.d.ts.map +1 -1
- package/dist/components/DataGrid.d.ts +66 -0
- package/dist/components/DataGrid.d.ts.map +1 -0
- package/dist/components/LogPanel.d.ts +38 -0
- package/dist/components/LogPanel.d.ts.map +1 -0
- package/dist/components/LogStore.d.ts +12 -0
- package/dist/components/LogStore.d.ts.map +1 -0
- package/dist/components/LogViewer.d.ts +46 -0
- package/dist/components/LogViewer.d.ts.map +1 -0
- package/dist/components/ProgramControl.d.ts +8 -2
- 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/RobotCard.d.ts +100 -0
- package/dist/components/RobotCard.d.ts.map +1 -0
- package/dist/components/RobotListItem.d.ts +34 -0
- package/dist/components/RobotListItem.d.ts.map +1 -0
- package/dist/components/RobotSetupReadinessIndicator.d.ts +31 -0
- package/dist/components/RobotSetupReadinessIndicator.d.ts.map +1 -0
- package/dist/components/RobotSetupReadinessIndicator.test.d.ts +2 -0
- package/dist/components/RobotSetupReadinessIndicator.test.d.ts.map +1 -0
- package/dist/components/TabBar.d.ts +30 -0
- package/dist/components/TabBar.d.ts.map +1 -0
- package/dist/components/robots/Robot.d.ts +3 -2
- package/dist/components/robots/Robot.d.ts.map +1 -1
- package/dist/components/robots/manufacturerHomePositions.d.ts +21 -0
- package/dist/components/robots/manufacturerHomePositions.d.ts.map +1 -0
- package/dist/icons/DropdownArrowIcon.d.ts +3 -0
- package/dist/icons/DropdownArrowIcon.d.ts.map +1 -0
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.d.ts.map +1 -1
- package/dist/index.cjs +49 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8772 -7044
- package/dist/index.js.map +1 -1
- package/dist/themes/createDarkTheme.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/components/AppHeader.md +84 -0
- package/src/components/AppHeader.tsx +199 -0
- package/src/components/CycleTimer.tsx +43 -64
- package/src/components/DataGrid.tsx +659 -0
- package/src/components/LogPanel.tsx +69 -0
- package/src/components/LogStore.ts +44 -0
- package/src/components/LogViewer.tsx +370 -0
- package/src/components/ProgramControl.tsx +27 -12
- package/src/components/ProgramStateIndicator.tsx +25 -8
- package/src/components/RobotCard.tsx +559 -0
- package/src/components/RobotListItem.tsx +150 -0
- package/src/components/RobotSetupReadinessIndicator.test.tsx +60 -0
- package/src/components/RobotSetupReadinessIndicator.tsx +124 -0
- package/src/components/TabBar.tsx +144 -0
- package/src/components/robots/Robot.tsx +5 -2
- package/src/components/robots/manufacturerHomePositions.ts +76 -0
- package/src/i18n/locales/de/translations.json +7 -1
- package/src/i18n/locales/en/translations.json +7 -1
- package/src/icons/DropdownArrowIcon.tsx +13 -0
- package/src/icons/chevronDown.svg +3 -0
- package/src/icons/index.ts +1 -0
- package/src/index.ts +14 -0
- package/src/themes/createDarkTheme.ts +75 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SxProps } from "@mui/material"
|
|
2
|
+
import { observer } from "mobx-react-lite"
|
|
3
|
+
import { useEffect, useMemo, useRef } from "react"
|
|
4
|
+
import { externalizeComponent } from "../externalizeComponent"
|
|
5
|
+
import { LogStore } from "./LogStore"
|
|
6
|
+
import { LogViewer } from "./LogViewer"
|
|
7
|
+
|
|
8
|
+
export type LogPanelProps = {
|
|
9
|
+
/** Log store instance to use, or create one automatically if not provided */
|
|
10
|
+
store?: LogStore
|
|
11
|
+
/** Height of the component */
|
|
12
|
+
height?: string | number
|
|
13
|
+
/** Additional styles */
|
|
14
|
+
sx?: SxProps
|
|
15
|
+
/** Ref to the log store for external access */
|
|
16
|
+
onStoreReady?: (store: LogStore) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A complete log panel component with built-in state management.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* function MyComponent() {
|
|
25
|
+
* const [logStore, setLogStore] = useState<LogStore>()
|
|
26
|
+
*
|
|
27
|
+
* return (
|
|
28
|
+
* <LogPanel
|
|
29
|
+
* height={400}
|
|
30
|
+
* onStoreReady={setLogStore}
|
|
31
|
+
* />
|
|
32
|
+
* )
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* // Then use the store to add messages
|
|
36
|
+
* logStore?.addInfo("Operation completed successfully")
|
|
37
|
+
* logStore?.addError("Something went wrong")
|
|
38
|
+
* logStore?.addWarning("Warning message")
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export const LogPanel = externalizeComponent(
|
|
42
|
+
observer((props: LogPanelProps) => {
|
|
43
|
+
const { store: externalStore, onStoreReady, ...logViewerProps } = props
|
|
44
|
+
const onStoreReadyRef = useRef(onStoreReady)
|
|
45
|
+
|
|
46
|
+
// Update ref when callback changes
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
onStoreReadyRef.current = onStoreReady
|
|
49
|
+
}, [onStoreReady])
|
|
50
|
+
|
|
51
|
+
const store = useMemo(() => {
|
|
52
|
+
const logStore = externalStore || new LogStore()
|
|
53
|
+
onStoreReadyRef.current?.(logStore)
|
|
54
|
+
return logStore
|
|
55
|
+
}, [externalStore])
|
|
56
|
+
|
|
57
|
+
const handleClear = () => {
|
|
58
|
+
store.clearMessages()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<LogViewer
|
|
63
|
+
{...logViewerProps}
|
|
64
|
+
messages={store.messages}
|
|
65
|
+
onClear={handleClear}
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { action, makeObservable, observable } from "mobx"
|
|
2
|
+
import type { LogLevel, LogMessage } from "./LogViewer"
|
|
3
|
+
|
|
4
|
+
export class LogStore {
|
|
5
|
+
messages: LogMessage[] = []
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
makeObservable(this, {
|
|
9
|
+
messages: observable,
|
|
10
|
+
addMessage: action,
|
|
11
|
+
clearMessages: action,
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
addMessage = (message: string, level: LogLevel = "info") => {
|
|
16
|
+
const logMessage: LogMessage = {
|
|
17
|
+
id: Math.random().toString(36).substring(2, 11),
|
|
18
|
+
timestamp: new Date(),
|
|
19
|
+
message,
|
|
20
|
+
level,
|
|
21
|
+
}
|
|
22
|
+
this.messages.push(logMessage)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
clearMessages = () => {
|
|
26
|
+
this.messages = []
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
addInfo = (message: string) => {
|
|
30
|
+
this.addMessage(message, "info")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
addWarning = (message: string) => {
|
|
34
|
+
this.addMessage(message, "warning")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
addError = (message: string) => {
|
|
38
|
+
this.addMessage(message, "error")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
addDebug = (message: string) => {
|
|
42
|
+
this.addMessage(message, "debug")
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ContentCopy,
|
|
3
|
+
DescriptionOutlined as DocumentIcon,
|
|
4
|
+
ExpandLess,
|
|
5
|
+
ExpandMore,
|
|
6
|
+
} from "@mui/icons-material"
|
|
7
|
+
import type { SxProps } from "@mui/material"
|
|
8
|
+
import {
|
|
9
|
+
Box,
|
|
10
|
+
Button,
|
|
11
|
+
IconButton,
|
|
12
|
+
Paper,
|
|
13
|
+
Typography,
|
|
14
|
+
useTheme,
|
|
15
|
+
} from "@mui/material"
|
|
16
|
+
import { observer } from "mobx-react-lite"
|
|
17
|
+
import { useEffect, useRef, useState } from "react"
|
|
18
|
+
import { externalizeComponent } from "../externalizeComponent"
|
|
19
|
+
|
|
20
|
+
export type LogLevel = "info" | "error" | "warning" | "debug"
|
|
21
|
+
|
|
22
|
+
export type LogMessage = {
|
|
23
|
+
id: string
|
|
24
|
+
timestamp: Date
|
|
25
|
+
message: string
|
|
26
|
+
level: LogLevel
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type LogViewerProps = {
|
|
30
|
+
/** Log messages to display */
|
|
31
|
+
messages: LogMessage[]
|
|
32
|
+
/** Callback when clear button is clicked */
|
|
33
|
+
onClear?: () => void
|
|
34
|
+
/** Height of the component */
|
|
35
|
+
height?: string | number
|
|
36
|
+
/** Additional styles */
|
|
37
|
+
sx?: SxProps
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Utility function to create a log message
|
|
42
|
+
*/
|
|
43
|
+
export const createLogMessage = (
|
|
44
|
+
message: string,
|
|
45
|
+
level: LogLevel,
|
|
46
|
+
id?: string,
|
|
47
|
+
): LogMessage => ({
|
|
48
|
+
id: id || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
49
|
+
timestamp: new Date(),
|
|
50
|
+
message,
|
|
51
|
+
level,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Utility function to create a debug log message
|
|
56
|
+
*/
|
|
57
|
+
export const createDebugMessage = (message: string, id?: string): LogMessage =>
|
|
58
|
+
createLogMessage(message, "debug", id)
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Utility function to create an info log message
|
|
62
|
+
*/
|
|
63
|
+
export const createInfoMessage = (message: string, id?: string): LogMessage =>
|
|
64
|
+
createLogMessage(message, "info", id)
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Utility function to create a warning log message
|
|
68
|
+
*/
|
|
69
|
+
export const createWarningMessage = (
|
|
70
|
+
message: string,
|
|
71
|
+
id?: string,
|
|
72
|
+
): LogMessage => createLogMessage(message, "warning", id)
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Utility function to create an error log message
|
|
76
|
+
*/
|
|
77
|
+
export const createErrorMessage = (message: string, id?: string): LogMessage =>
|
|
78
|
+
createLogMessage(message, "error", id)
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A log viewer component that displays timestamped log messages with different levels.
|
|
82
|
+
* Features a header with document icon and clear button, and scrollable message area.
|
|
83
|
+
*/
|
|
84
|
+
export const LogViewer = externalizeComponent(
|
|
85
|
+
observer((props: LogViewerProps) => {
|
|
86
|
+
const { messages = [], onClear, height = 400, sx } = props
|
|
87
|
+
const theme = useTheme()
|
|
88
|
+
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
|
89
|
+
|
|
90
|
+
// Auto-scroll to bottom when new messages are added
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (messages.length === 0) return
|
|
93
|
+
|
|
94
|
+
const scrollContainer = scrollContainerRef.current
|
|
95
|
+
if (!scrollContainer) return
|
|
96
|
+
|
|
97
|
+
// Use a timeout to scroll after the DOM updates
|
|
98
|
+
const timeoutId = setTimeout(() => {
|
|
99
|
+
// Scroll the container to the bottom, not the entire browser
|
|
100
|
+
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
|
101
|
+
}, 10)
|
|
102
|
+
|
|
103
|
+
return () => clearTimeout(timeoutId)
|
|
104
|
+
}, [messages.length])
|
|
105
|
+
|
|
106
|
+
const formatTimestamp = (timestamp: Date) => {
|
|
107
|
+
return timestamp.toLocaleTimeString("en-US", {
|
|
108
|
+
hour12: false,
|
|
109
|
+
hour: "2-digit",
|
|
110
|
+
minute: "2-digit",
|
|
111
|
+
second: "2-digit",
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const getMessageColor = (level: LogLevel) => {
|
|
116
|
+
switch (level) {
|
|
117
|
+
case "error":
|
|
118
|
+
return theme.palette.error.main
|
|
119
|
+
case "warning":
|
|
120
|
+
return theme.palette.warning.main
|
|
121
|
+
case "info":
|
|
122
|
+
return theme.palette.info.main
|
|
123
|
+
case "debug":
|
|
124
|
+
return theme.palette.text.disabled
|
|
125
|
+
default:
|
|
126
|
+
return theme.palette.text.secondary
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Component for individual log messages with expand/copy functionality
|
|
131
|
+
const LogMessage = ({ message }: { message: LogMessage }) => {
|
|
132
|
+
const [isExpanded, setIsExpanded] = useState(false)
|
|
133
|
+
const [copyTooltip, setCopyTooltip] = useState(false)
|
|
134
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
135
|
+
const isLongMessage = message.message.length > 150
|
|
136
|
+
|
|
137
|
+
const handleCopy = async () => {
|
|
138
|
+
try {
|
|
139
|
+
await navigator.clipboard.writeText(message.message)
|
|
140
|
+
setCopyTooltip(true)
|
|
141
|
+
setTimeout(() => setCopyTooltip(false), 2000)
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.error("Failed to copy message:", err)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const displayMessage =
|
|
148
|
+
isLongMessage && !isExpanded
|
|
149
|
+
? message.message.substring(0, 150) + "..."
|
|
150
|
+
: message.message
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<Box
|
|
154
|
+
key={message.id}
|
|
155
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
156
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
157
|
+
sx={{
|
|
158
|
+
display: "flex",
|
|
159
|
+
gap: 1,
|
|
160
|
+
fontFamily: "monospace",
|
|
161
|
+
flexDirection: "column",
|
|
162
|
+
"&:hover": {
|
|
163
|
+
backgroundColor: theme.palette.action.hover,
|
|
164
|
+
},
|
|
165
|
+
borderRadius: "4px",
|
|
166
|
+
padding: "2px 4px",
|
|
167
|
+
margin: "-2px -4px",
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
<Box sx={{ display: "flex", gap: 1 }}>
|
|
171
|
+
{/* Timestamp */}
|
|
172
|
+
<Typography
|
|
173
|
+
component="span"
|
|
174
|
+
sx={{
|
|
175
|
+
fontWeight: 400,
|
|
176
|
+
fontSize: "12px",
|
|
177
|
+
lineHeight: "18px",
|
|
178
|
+
letterSpacing: "0.4px",
|
|
179
|
+
color: theme.palette.text.disabled,
|
|
180
|
+
whiteSpace: "nowrap",
|
|
181
|
+
flexShrink: 0,
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
[{formatTimestamp(message.timestamp)}]
|
|
185
|
+
</Typography>
|
|
186
|
+
|
|
187
|
+
{/* Message */}
|
|
188
|
+
<Typography
|
|
189
|
+
component="span"
|
|
190
|
+
sx={{
|
|
191
|
+
fontWeight: 400,
|
|
192
|
+
fontSize: "12px",
|
|
193
|
+
lineHeight: "18px",
|
|
194
|
+
letterSpacing: "0.4px",
|
|
195
|
+
color: getMessageColor(message.level),
|
|
196
|
+
wordBreak: "break-word",
|
|
197
|
+
overflowWrap: "anywhere",
|
|
198
|
+
hyphens: "auto",
|
|
199
|
+
flex: 1,
|
|
200
|
+
whiteSpace: "pre-wrap",
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
{displayMessage}
|
|
204
|
+
</Typography>
|
|
205
|
+
|
|
206
|
+
{/* Action buttons - only visible on hover */}
|
|
207
|
+
<Box
|
|
208
|
+
sx={{
|
|
209
|
+
display: "flex",
|
|
210
|
+
alignItems: "flex-start",
|
|
211
|
+
gap: 0.5,
|
|
212
|
+
opacity: isHovered ? 1 : 0,
|
|
213
|
+
transition: "opacity 0.2s ease-in-out",
|
|
214
|
+
visibility: isHovered ? "visible" : "hidden",
|
|
215
|
+
}}
|
|
216
|
+
>
|
|
217
|
+
<IconButton
|
|
218
|
+
size="small"
|
|
219
|
+
onClick={handleCopy}
|
|
220
|
+
sx={{
|
|
221
|
+
padding: "2px",
|
|
222
|
+
color: theme.palette.text.secondary,
|
|
223
|
+
"&:hover": {
|
|
224
|
+
backgroundColor: theme.palette.action.hover,
|
|
225
|
+
},
|
|
226
|
+
}}
|
|
227
|
+
title={copyTooltip ? "Copied!" : "Copy message"}
|
|
228
|
+
>
|
|
229
|
+
<ContentCopy sx={{ fontSize: 12 }} />
|
|
230
|
+
</IconButton>
|
|
231
|
+
|
|
232
|
+
{isLongMessage && (
|
|
233
|
+
<IconButton
|
|
234
|
+
size="small"
|
|
235
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
236
|
+
sx={{
|
|
237
|
+
padding: "2px",
|
|
238
|
+
color: theme.palette.text.secondary,
|
|
239
|
+
"&:hover": {
|
|
240
|
+
backgroundColor: theme.palette.action.hover,
|
|
241
|
+
},
|
|
242
|
+
}}
|
|
243
|
+
title={isExpanded ? "Collapse" : "Expand"}
|
|
244
|
+
>
|
|
245
|
+
{isExpanded ? (
|
|
246
|
+
<ExpandLess sx={{ fontSize: 12 }} />
|
|
247
|
+
) : (
|
|
248
|
+
<ExpandMore sx={{ fontSize: 12 }} />
|
|
249
|
+
)}
|
|
250
|
+
</IconButton>
|
|
251
|
+
)}
|
|
252
|
+
</Box>
|
|
253
|
+
</Box>
|
|
254
|
+
</Box>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<Paper
|
|
260
|
+
sx={{
|
|
261
|
+
backgroundColor:
|
|
262
|
+
theme.palette.backgroundPaperElevation?.[2] || "#171927",
|
|
263
|
+
backgroundImage: "none", // Override any gradient from elevation
|
|
264
|
+
height,
|
|
265
|
+
display: "flex",
|
|
266
|
+
flexDirection: "column",
|
|
267
|
+
overflow: "hidden",
|
|
268
|
+
...sx,
|
|
269
|
+
}}
|
|
270
|
+
>
|
|
271
|
+
{/* Header */}
|
|
272
|
+
<Box
|
|
273
|
+
sx={{
|
|
274
|
+
display: "flex",
|
|
275
|
+
alignItems: "center",
|
|
276
|
+
justifyContent: "space-between",
|
|
277
|
+
padding: "12px 16px",
|
|
278
|
+
}}
|
|
279
|
+
>
|
|
280
|
+
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
|
281
|
+
<DocumentIcon
|
|
282
|
+
sx={{
|
|
283
|
+
fontSize: 16,
|
|
284
|
+
color: theme.palette.action.active,
|
|
285
|
+
opacity: 0.56,
|
|
286
|
+
}}
|
|
287
|
+
/>
|
|
288
|
+
<Typography
|
|
289
|
+
sx={{
|
|
290
|
+
fontWeight: 500,
|
|
291
|
+
fontSize: "14px",
|
|
292
|
+
lineHeight: "143%",
|
|
293
|
+
letterSpacing: "0.17px",
|
|
294
|
+
color: theme.palette.text.primary,
|
|
295
|
+
}}
|
|
296
|
+
>
|
|
297
|
+
Log
|
|
298
|
+
</Typography>
|
|
299
|
+
</Box>
|
|
300
|
+
<Button
|
|
301
|
+
onClick={onClear}
|
|
302
|
+
variant="text"
|
|
303
|
+
sx={{
|
|
304
|
+
fontWeight: 500,
|
|
305
|
+
fontSize: "13px",
|
|
306
|
+
lineHeight: "22px",
|
|
307
|
+
letterSpacing: "0.46px",
|
|
308
|
+
color: theme.palette.primary.main,
|
|
309
|
+
textTransform: "none",
|
|
310
|
+
minWidth: "auto",
|
|
311
|
+
padding: "4px 8px",
|
|
312
|
+
"&:hover": {
|
|
313
|
+
backgroundColor: theme.palette.primary.main + "14", // 8% opacity
|
|
314
|
+
},
|
|
315
|
+
}}
|
|
316
|
+
>
|
|
317
|
+
Clear
|
|
318
|
+
</Button>
|
|
319
|
+
</Box>
|
|
320
|
+
|
|
321
|
+
{/* Messages Container */}
|
|
322
|
+
<Box
|
|
323
|
+
ref={scrollContainerRef}
|
|
324
|
+
sx={{
|
|
325
|
+
flex: 1,
|
|
326
|
+
overflow: "auto",
|
|
327
|
+
padding: "8px 16px",
|
|
328
|
+
display: "flex",
|
|
329
|
+
flexDirection: "column",
|
|
330
|
+
gap: "2px",
|
|
331
|
+
// Custom scrollbar styling to keep it consistently dark
|
|
332
|
+
scrollbarWidth: "thin",
|
|
333
|
+
scrollbarColor: `${theme.palette.divider} transparent`,
|
|
334
|
+
"&::-webkit-scrollbar": {
|
|
335
|
+
width: "6px",
|
|
336
|
+
},
|
|
337
|
+
"&::-webkit-scrollbar-track": {
|
|
338
|
+
background: "transparent",
|
|
339
|
+
},
|
|
340
|
+
"&::-webkit-scrollbar-thumb": {
|
|
341
|
+
backgroundColor: theme.palette.divider,
|
|
342
|
+
borderRadius: "3px",
|
|
343
|
+
"&:hover": {
|
|
344
|
+
backgroundColor: theme.palette.divider,
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
}}
|
|
348
|
+
>
|
|
349
|
+
{messages.length === 0 ? (
|
|
350
|
+
<Typography
|
|
351
|
+
sx={{
|
|
352
|
+
color: theme.palette.text.disabled,
|
|
353
|
+
fontSize: "12px",
|
|
354
|
+
fontStyle: "italic",
|
|
355
|
+
textAlign: "center",
|
|
356
|
+
marginTop: 2,
|
|
357
|
+
}}
|
|
358
|
+
>
|
|
359
|
+
No log messages
|
|
360
|
+
</Typography>
|
|
361
|
+
) : (
|
|
362
|
+
messages.map((message) => (
|
|
363
|
+
<LogMessage key={message.id} message={message} />
|
|
364
|
+
))
|
|
365
|
+
)}
|
|
366
|
+
</Box>
|
|
367
|
+
</Paper>
|
|
368
|
+
)
|
|
369
|
+
}),
|
|
370
|
+
)
|
|
@@ -4,7 +4,13 @@ import { observer } from "mobx-react-lite"
|
|
|
4
4
|
import { useTranslation } from "react-i18next"
|
|
5
5
|
import { externalizeComponent } from "../externalizeComponent"
|
|
6
6
|
|
|
7
|
-
export
|
|
7
|
+
export enum ProgramState {
|
|
8
|
+
IDLE = "idle",
|
|
9
|
+
RUNNING = "running",
|
|
10
|
+
PAUSED = "paused",
|
|
11
|
+
STOPPING = "stopping",
|
|
12
|
+
ERROR = "error",
|
|
13
|
+
}
|
|
8
14
|
|
|
9
15
|
export interface ProgramControlProps {
|
|
10
16
|
/** The current state of the program control */
|
|
@@ -46,7 +52,7 @@ interface ButtonConfig {
|
|
|
46
52
|
* A control component for program execution with run, pause, and stop functionality.
|
|
47
53
|
*
|
|
48
54
|
* Features:
|
|
49
|
-
* - State machine with idle, running, paused, and
|
|
55
|
+
* - State machine with idle, running, paused, stopping, and error states
|
|
50
56
|
* - Two variants: with_pause (3 buttons) and without_pause (2 buttons)
|
|
51
57
|
* - Optional manual reset functionality
|
|
52
58
|
* - Responsive design with 110px circular buttons
|
|
@@ -70,22 +76,28 @@ export const ProgramControl = externalizeComponent(
|
|
|
70
76
|
const getButtonConfigs = (): ButtonConfig[] => {
|
|
71
77
|
const baseConfigs: Record<string, ButtonConfig> = {
|
|
72
78
|
run: {
|
|
73
|
-
enabled:
|
|
79
|
+
enabled:
|
|
80
|
+
state === ProgramState.IDLE ||
|
|
81
|
+
state === ProgramState.PAUSED ||
|
|
82
|
+
state === ProgramState.ERROR,
|
|
74
83
|
label:
|
|
75
|
-
state ===
|
|
84
|
+
state === ProgramState.PAUSED
|
|
76
85
|
? t("ProgramControl.Resume.bt")
|
|
77
|
-
:
|
|
86
|
+
: state === ProgramState.ERROR
|
|
87
|
+
? t("ProgramControl.Retry.bt")
|
|
88
|
+
: t("ProgramControl.Start.bt"),
|
|
78
89
|
color: theme.palette.success.main,
|
|
79
90
|
onClick: onRun,
|
|
80
91
|
},
|
|
81
92
|
pause: {
|
|
82
|
-
enabled: state ===
|
|
93
|
+
enabled: state === ProgramState.RUNNING,
|
|
83
94
|
label: t("ProgramControl.Pause.bt"),
|
|
84
95
|
color: "#FFFFFF33",
|
|
85
96
|
onClick: onPause || (() => {}),
|
|
86
97
|
},
|
|
87
98
|
stop: {
|
|
88
|
-
enabled:
|
|
99
|
+
enabled:
|
|
100
|
+
state === ProgramState.RUNNING || state === ProgramState.PAUSED,
|
|
89
101
|
label: t("ProgramControl.Stop.bt"),
|
|
90
102
|
color: theme.palette.error.main,
|
|
91
103
|
onClick: onStop,
|
|
@@ -157,7 +169,7 @@ export const ProgramControl = externalizeComponent(
|
|
|
157
169
|
variant="contained"
|
|
158
170
|
disabled={
|
|
159
171
|
!config.enabled ||
|
|
160
|
-
(state ===
|
|
172
|
+
(state === ProgramState.STOPPING && !requiresManualReset)
|
|
161
173
|
}
|
|
162
174
|
onClick={config.onClick}
|
|
163
175
|
sx={{
|
|
@@ -167,14 +179,17 @@ export const ProgramControl = externalizeComponent(
|
|
|
167
179
|
backgroundColor: config.color,
|
|
168
180
|
opacity:
|
|
169
181
|
config.enabled &&
|
|
170
|
-
!(state ===
|
|
182
|
+
!(state === ProgramState.STOPPING && !requiresManualReset)
|
|
171
183
|
? 1
|
|
172
184
|
: 0.3,
|
|
173
185
|
"&:hover": {
|
|
174
186
|
backgroundColor: config.color,
|
|
175
187
|
opacity:
|
|
176
188
|
config.enabled &&
|
|
177
|
-
!(
|
|
189
|
+
!(
|
|
190
|
+
state === ProgramState.STOPPING &&
|
|
191
|
+
!requiresManualReset
|
|
192
|
+
)
|
|
178
193
|
? 0.8
|
|
179
194
|
: 0.3,
|
|
180
195
|
},
|
|
@@ -194,13 +209,13 @@ export const ProgramControl = externalizeComponent(
|
|
|
194
209
|
sx={{
|
|
195
210
|
color:
|
|
196
211
|
config.enabled &&
|
|
197
|
-
!(state ===
|
|
212
|
+
!(state === ProgramState.STOPPING && !requiresManualReset)
|
|
198
213
|
? config.color
|
|
199
214
|
: theme.palette.text.disabled,
|
|
200
215
|
textAlign: "center",
|
|
201
216
|
opacity:
|
|
202
217
|
config.enabled &&
|
|
203
|
-
!(state ===
|
|
218
|
+
!(state === ProgramState.STOPPING && !requiresManualReset)
|
|
204
219
|
? 1
|
|
205
220
|
: 0.3,
|
|
206
221
|
}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Chip, useTheme } from "@mui/material"
|
|
1
|
+
import { Chip, Typography, useTheme } from "@mui/material"
|
|
2
2
|
import type {
|
|
3
3
|
RobotControllerStateOperationModeEnum,
|
|
4
4
|
RobotControllerStateSafetyStateEnum,
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
import { observer } from "mobx-react-lite"
|
|
7
7
|
import { useTranslation } from "react-i18next"
|
|
8
8
|
import { externalizeComponent } from "../externalizeComponent"
|
|
9
|
-
import
|
|
9
|
+
import { ProgramState } from "./ProgramControl"
|
|
10
10
|
|
|
11
11
|
export interface ProgramStateIndicatorProps {
|
|
12
12
|
/** The current state of the program */
|
|
@@ -77,22 +77,27 @@ export const ProgramStateIndicator = externalizeComponent(
|
|
|
77
77
|
// For normal safety states, check program state
|
|
78
78
|
if (safetyState === "SAFETY_STATE_NORMAL") {
|
|
79
79
|
switch (programState) {
|
|
80
|
-
case
|
|
80
|
+
case ProgramState.RUNNING:
|
|
81
81
|
return {
|
|
82
82
|
label: t("ProgramStateIndicator.Running.lb"),
|
|
83
83
|
color: theme.palette.success.main,
|
|
84
84
|
}
|
|
85
|
-
case
|
|
85
|
+
case ProgramState.PAUSED:
|
|
86
86
|
return {
|
|
87
87
|
label: t("ProgramStateIndicator.Paused.lb"),
|
|
88
88
|
color: theme.palette.grey[600],
|
|
89
89
|
}
|
|
90
|
-
case
|
|
90
|
+
case ProgramState.STOPPING:
|
|
91
91
|
return {
|
|
92
92
|
label: t("ProgramStateIndicator.Stopped.lb"),
|
|
93
93
|
color: theme.palette.error.main,
|
|
94
94
|
}
|
|
95
|
-
case
|
|
95
|
+
case ProgramState.ERROR:
|
|
96
|
+
return {
|
|
97
|
+
label: t("ProgramStateIndicator.Error.lb"),
|
|
98
|
+
color: theme.palette.error.main,
|
|
99
|
+
}
|
|
100
|
+
case ProgramState.IDLE:
|
|
96
101
|
default:
|
|
97
102
|
return {
|
|
98
103
|
label: t("ProgramStateIndicator.Ready.lb"),
|
|
@@ -131,14 +136,26 @@ export const ProgramStateIndicator = externalizeComponent(
|
|
|
131
136
|
return (
|
|
132
137
|
<Chip
|
|
133
138
|
className={className}
|
|
134
|
-
label={
|
|
139
|
+
label={
|
|
140
|
+
<Typography
|
|
141
|
+
variant="body2"
|
|
142
|
+
sx={{
|
|
143
|
+
fontSize: "0.75rem", // Smaller than body2
|
|
144
|
+
lineHeight: 1.2,
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
{fullLabel}
|
|
148
|
+
</Typography>
|
|
149
|
+
}
|
|
135
150
|
variant="filled"
|
|
136
151
|
sx={{
|
|
137
152
|
backgroundColor: color,
|
|
138
153
|
color: theme.palette.getContrastText(color),
|
|
139
154
|
fontWeight: 500,
|
|
155
|
+
height: "auto",
|
|
140
156
|
"& .MuiChip-label": {
|
|
141
|
-
paddingX:
|
|
157
|
+
paddingX: 1.5,
|
|
158
|
+
paddingY: 0.5,
|
|
142
159
|
},
|
|
143
160
|
}}
|
|
144
161
|
/>
|