@wandelbots/wandelbots-js-react-components 1.3.1 → 1.3.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/package.json +18 -7
- package/src/components/3d-viewport/CoordinateSystemTransform.tsx +44 -0
- package/src/components/3d-viewport/PresetEnvironment.tsx +78 -0
- package/src/components/3d-viewport/SafetyZonesRenderer.tsx +54 -0
- package/src/components/LoadingButton.stories.tsx +61 -0
- package/src/components/LoadingButton.tsx +19 -0
- package/src/components/LoadingCover.tsx +75 -0
- package/src/components/ThemeSelect.tsx +49 -0
- package/src/components/VelocitySlider.stories.tsx +32 -0
- package/src/components/VelocitySlider.tsx +52 -0
- package/src/components/jogging/JoggingCartesianAxisControl.stories.tsx +41 -0
- package/src/components/jogging/JoggingCartesianAxisControl.tsx +127 -0
- package/src/components/jogging/JoggingCartesianTab.tsx +265 -0
- package/src/components/jogging/JoggingCartesianValues.tsx +45 -0
- package/src/components/jogging/JoggingFreedriveTab.tsx +9 -0
- package/src/components/jogging/JoggingJointLimitDetector.tsx +51 -0
- package/src/components/jogging/JoggingJointRotationControl.stories.tsx +38 -0
- package/src/components/jogging/JoggingJointRotationControl.tsx +197 -0
- package/src/components/jogging/JoggingJointTab.tsx +93 -0
- package/src/components/jogging/JoggingJointValues.tsx +45 -0
- package/src/components/jogging/JoggingOptions.tsx +96 -0
- package/src/components/jogging/JoggingPanel.stories.tsx +26 -0
- package/src/components/jogging/JoggingPanel.tsx +148 -0
- package/src/components/jogging/JoggingStore.tsx +294 -0
- package/src/components/jogging/JoggingVelocitySlider.tsx +56 -0
- package/src/components/robots/ABB_1200_07_7.tsx +123 -0
- package/src/components/robots/AxisConfig.ts +3 -0
- package/src/components/robots/DHRobot.tsx +129 -0
- package/src/components/robots/FANUC_ARC_Mate_100iD.tsx +187 -0
- package/src/components/robots/FANUC_ARC_Mate_120iD.tsx +187 -0
- package/src/components/robots/FANUC_CRX10iA.tsx +167 -0
- package/src/components/robots/FANUC_CRX25iA.tsx +167 -0
- package/src/components/robots/FANUC_CRX25iAL.tsx +178 -0
- package/src/components/robots/KUKA_KR210_R2700.tsx +291 -0
- package/src/components/robots/KUKA_KR270_R2700.tsx +244 -0
- package/src/components/robots/RobotAnimator.tsx +83 -0
- package/src/components/robots/SupportedRobot.tsx +131 -0
- package/src/components/robots/UniversalRobots_UR10.tsx +112 -0
- package/src/components/robots/UniversalRobots_UR10e.tsx +275 -0
- package/src/components/robots/UniversalRobots_UR3.tsx +112 -0
- package/src/components/robots/UniversalRobots_UR3e.tsx +112 -0
- package/src/components/robots/UniversalRobots_UR5.tsx +111 -0
- package/src/components/robots/UniversalRobots_UR5e.tsx +280 -0
- package/src/components/robots/Yaskawa_AR1440.tsx +152 -0
- package/src/components/robots/Yaskawa_AR1730.tsx +165 -0
- package/src/components/robots/Yaskawa_AR2010.tsx +159 -0
- package/src/components/robots/Yaskawa_AR3120.tsx +160 -0
- package/src/components/robots/Yaskawa_AR900.tsx +121 -0
- package/src/components/utils/converters.ts +23 -0
- package/src/components/utils/errorHandling.ts +30 -0
- package/src/components/utils/hooks.tsx +54 -0
- package/src/components/utils/robotTreeQuery.ts +27 -0
- package/src/components/wandelscript-editor/WandelscriptEditor.stories.tsx +45 -0
- package/src/components/wandelscript-editor/WandelscriptEditor.tsx +114 -0
- package/src/components/wandelscript-editor/wandelscript.tmLanguage.ts +62 -0
- package/src/declarations.d.ts +10 -0
- package/src/i18n/config.ts +27 -0
- package/src/i18n/locales/de/translations.json +12 -0
- package/src/i18n/locales/en/translations.json +12 -0
- package/src/icons/arrowForwardFilled.tsx +7 -0
- package/src/icons/axis-x.svg +3 -0
- package/src/icons/axis-y.svg +3 -0
- package/src/icons/axis-z.svg +3 -0
- package/src/icons/expandFilled.tsx +11 -0
- package/src/icons/home.tsx +12 -0
- package/src/icons/index.ts +6 -0
- package/src/icons/infoOutlined.tsx +10 -0
- package/src/icons/jogging.svg +3 -0
- package/src/icons/robot.svg +3 -0
- package/src/icons/robot.tsx +14 -0
- package/src/icons/rotation.svg +4 -0
- package/src/icons/wbLogo.tsx +21 -0
- package/src/index.ts +7 -0
- package/src/themes/color.tsx +74 -0
- package/src/themes/theme.ts +150 -0
- package/src/themes/wbTheme.stories.tsx +64 -0
- package/src/themes/wbTheme.ts +186 -0
package/package.json
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wandelbots/wandelbots-js-react-components",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "React UI toolkit for building applications on top of the Wandelbots platform",
|
|
5
|
-
"main": "dist/cjs/index.js",
|
|
6
|
-
"module": "dist/esm/index.js",
|
|
7
5
|
"files": [
|
|
8
6
|
"dist",
|
|
9
|
-
"
|
|
7
|
+
"src"
|
|
10
8
|
],
|
|
11
|
-
"
|
|
9
|
+
"type": "module",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"main": "./dist/index.cjs",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
12
19
|
"scripts": {
|
|
13
20
|
"dev": "storybook dev -p 6006 --no-open",
|
|
21
|
+
"dev:pack": "nodemon -w \".\" -e ts -i *.tgz -i dist -x \"npm run build && npm pack\"",
|
|
14
22
|
"dev:wbjs": "nodemon --watch $WBJS_PATH -e tgz --exec 'npm install file:$WBJS_PATH && storybook dev -p 6006 --no-open'",
|
|
15
23
|
"tsc": "tsc --pretty --noEmit",
|
|
16
|
-
"test": "
|
|
17
|
-
"build": "
|
|
24
|
+
"test": "npm run build",
|
|
25
|
+
"build": "rimraf dist && vite build && tsc --declaration --emitDeclarationOnly",
|
|
18
26
|
"build:dev": "rollup -c rollup.config.dev.mjs",
|
|
19
27
|
"build-storybook": "storybook build"
|
|
20
28
|
},
|
|
@@ -34,6 +42,7 @@
|
|
|
34
42
|
"@emotion/styled": "^11.13.0",
|
|
35
43
|
"@mui/material": "^5.13.6",
|
|
36
44
|
"@rollup/plugin-commonjs": "^26.0.1",
|
|
45
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
37
46
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
38
47
|
"@rollup/plugin-terser": "^0.4.4",
|
|
39
48
|
"@rollup/plugin-typescript": "^11.1.6",
|
|
@@ -45,6 +54,7 @@
|
|
|
45
54
|
"@storybook/react-vite": "^8.2.7",
|
|
46
55
|
"@storybook/test": "^8.2.7",
|
|
47
56
|
"@storybook/test-runner": "^0.19.1",
|
|
57
|
+
"@svgr/rollup": "^8.1.0",
|
|
48
58
|
"@types/lodash-es": "^4.17.12",
|
|
49
59
|
"@types/react": "^18.3.3",
|
|
50
60
|
"@vitejs/plugin-react": "^4.3.1",
|
|
@@ -53,6 +63,7 @@
|
|
|
53
63
|
"prop-types": "^15.8.1",
|
|
54
64
|
"react": "^18.3.1",
|
|
55
65
|
"react-dom": "^18.3.1",
|
|
66
|
+
"rimraf": "^6.0.1",
|
|
56
67
|
"rollup": "^4.19.2",
|
|
57
68
|
"rollup-plugin-dts": "^6.1.1",
|
|
58
69
|
"rollup-plugin-gltf": "^4.0.0",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CoordinateSystem } from "@wandelbots/wandelbots-api-client"
|
|
2
|
+
import type { ReactNode } from "react"
|
|
3
|
+
import { Vector3, Quaternion } from "three"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Applies a API coordinate system transformation to
|
|
7
|
+
* all react-three child components.
|
|
8
|
+
*/
|
|
9
|
+
export const CoordinateSystemTransform = ({
|
|
10
|
+
coordinateSystem,
|
|
11
|
+
children,
|
|
12
|
+
}: {
|
|
13
|
+
coordinateSystem?: CoordinateSystem
|
|
14
|
+
children: ReactNode
|
|
15
|
+
}) => {
|
|
16
|
+
const position = new Vector3(
|
|
17
|
+
(coordinateSystem?.position?.x ?? 0) / 1000,
|
|
18
|
+
(coordinateSystem?.position?.y ?? 0) / 1000,
|
|
19
|
+
(coordinateSystem?.position?.z ?? 0) / 1000,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
let rotation = coordinateSystem?.rotation
|
|
23
|
+
const rotationType = rotation?.type
|
|
24
|
+
if (rotationType && rotationType !== "ROTATION_VECTOR") {
|
|
25
|
+
console.warn(
|
|
26
|
+
`Unsupported rotation type ${rotationType}. Only ROTATION_VECTOR is supported.`,
|
|
27
|
+
)
|
|
28
|
+
rotation = { type: "ROTATION_VECTOR", angles: [0, 0, 0, 0] }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const rotationVector = new Vector3(
|
|
32
|
+
rotation?.angles[0] ?? 0,
|
|
33
|
+
rotation?.angles[1] ?? 0,
|
|
34
|
+
rotation?.angles[2] ?? 0,
|
|
35
|
+
)
|
|
36
|
+
const magnitude = rotationVector.length()
|
|
37
|
+
const axis = rotationVector.normalize()
|
|
38
|
+
const quaternion = new Quaternion().setFromAxisAngle(axis, magnitude)
|
|
39
|
+
return (
|
|
40
|
+
<group position={position} quaternion={quaternion}>
|
|
41
|
+
{children}
|
|
42
|
+
</group>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Environment, Lightformer } from "@react-three/drei"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renders a preset environment for the 3D scene.
|
|
5
|
+
* This component wraps the scene with an `Environment` component
|
|
6
|
+
* and builds a lightmap build with `Lightformers`.
|
|
7
|
+
*/
|
|
8
|
+
export function PresetEnvironment() {
|
|
9
|
+
return (
|
|
10
|
+
<Environment>
|
|
11
|
+
<Lightformers />
|
|
12
|
+
</Environment>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function Lightformers({ positions = [2, 0, 2, 0, 2, 0, 2, 0] }) {
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
{/* Ceiling */}
|
|
20
|
+
<Lightformer
|
|
21
|
+
intensity={5}
|
|
22
|
+
rotation-x={Math.PI / 2}
|
|
23
|
+
position={[0, 5, -9]}
|
|
24
|
+
scale={[10, 10, 1]}
|
|
25
|
+
/>
|
|
26
|
+
<group rotation={[0, 0.5, 0]}>
|
|
27
|
+
<group>
|
|
28
|
+
{positions.map((x, i) => (
|
|
29
|
+
<Lightformer
|
|
30
|
+
key={i}
|
|
31
|
+
form="circle"
|
|
32
|
+
intensity={5}
|
|
33
|
+
rotation={[Math.PI / 2, 0, 0]}
|
|
34
|
+
position={[x, 4, i * 4]}
|
|
35
|
+
scale={[3, 1, 1]}
|
|
36
|
+
/>
|
|
37
|
+
))}
|
|
38
|
+
</group>
|
|
39
|
+
</group>
|
|
40
|
+
{/* Sides */}
|
|
41
|
+
<Lightformer
|
|
42
|
+
intensity={40}
|
|
43
|
+
rotation-y={Math.PI / 2}
|
|
44
|
+
position={[-5, 1, -1]}
|
|
45
|
+
scale={[20, 0.1, 1]}
|
|
46
|
+
/>
|
|
47
|
+
<Lightformer
|
|
48
|
+
intensity={20}
|
|
49
|
+
rotation-y={-Math.PI}
|
|
50
|
+
position={[-5, -2, -1]}
|
|
51
|
+
scale={[20, 0.1, 1]}
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<Lightformer
|
|
55
|
+
rotation-y={Math.PI / 2}
|
|
56
|
+
position={[-5, -1, -1]}
|
|
57
|
+
scale={[20, 0.5, 1]}
|
|
58
|
+
intensity={5}
|
|
59
|
+
/>
|
|
60
|
+
<Lightformer
|
|
61
|
+
rotation-y={-Math.PI / 2}
|
|
62
|
+
position={[10, 1, 0]}
|
|
63
|
+
scale={[20, 1, 1]}
|
|
64
|
+
intensity={10}
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
{/* Key */}
|
|
68
|
+
<Lightformer
|
|
69
|
+
form="ring"
|
|
70
|
+
color="white"
|
|
71
|
+
intensity={5}
|
|
72
|
+
scale={10}
|
|
73
|
+
position={[-15, 4, -18]}
|
|
74
|
+
target={[0, 0, 0]}
|
|
75
|
+
/>
|
|
76
|
+
</>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { SafetySetupSafetyZone, Geometry } from "@wandelbots/wandelbots-js"
|
|
2
|
+
import * as THREE from "three"
|
|
3
|
+
import { ConvexGeometry } from "three-stdlib"
|
|
4
|
+
import { type GroupProps } from "@react-three/fiber"
|
|
5
|
+
|
|
6
|
+
export type SafetyZonesRendererProps = {
|
|
7
|
+
safetyZones: SafetySetupSafetyZone[]
|
|
8
|
+
} & GroupProps
|
|
9
|
+
|
|
10
|
+
export function SafetyZonesRenderer({
|
|
11
|
+
safetyZones,
|
|
12
|
+
...props
|
|
13
|
+
}: SafetyZonesRendererProps) {
|
|
14
|
+
return (
|
|
15
|
+
<group {...props}>
|
|
16
|
+
{safetyZones.map((zone, index) => {
|
|
17
|
+
let geometries: Geometry[] = []
|
|
18
|
+
if (zone.geometry) {
|
|
19
|
+
if (zone.geometry.compound) {
|
|
20
|
+
geometries = zone.geometry.compound.child_geometries
|
|
21
|
+
} else if (zone.geometry.convex_hull) {
|
|
22
|
+
geometries = [zone.geometry]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return geometries.map((geometry, i) => {
|
|
27
|
+
if (!geometry.convex_hull) return null
|
|
28
|
+
|
|
29
|
+
const vertices = geometry.convex_hull.vertices.map(
|
|
30
|
+
(v) => new THREE.Vector3(v.x / 1000, v.y / 1000, v.z / 1000),
|
|
31
|
+
)
|
|
32
|
+
const convexGeometry = new ConvexGeometry(vertices)
|
|
33
|
+
return (
|
|
34
|
+
<>
|
|
35
|
+
<mesh key={`${index}-${i}`} geometry={convexGeometry}>
|
|
36
|
+
<meshStandardMaterial
|
|
37
|
+
key={index}
|
|
38
|
+
attach="material"
|
|
39
|
+
color="#009f4d"
|
|
40
|
+
opacity={0.2}
|
|
41
|
+
depthTest={false}
|
|
42
|
+
depthWrite={false}
|
|
43
|
+
transparent
|
|
44
|
+
polygonOffset
|
|
45
|
+
polygonOffsetFactor={-i}
|
|
46
|
+
/>
|
|
47
|
+
</mesh>
|
|
48
|
+
</>
|
|
49
|
+
)
|
|
50
|
+
})
|
|
51
|
+
})}
|
|
52
|
+
</group>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { LoadingButton } from "./LoadingButton";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { PlayArrow } from "@mui/icons-material"
|
|
5
|
+
|
|
6
|
+
async function delay(ms: number) {
|
|
7
|
+
return new Promise<void>((resolve) => {
|
|
8
|
+
setTimeout(() => {
|
|
9
|
+
resolve();
|
|
10
|
+
}, ms);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DemoLoader = (props: React.ComponentProps<typeof LoadingButton>) => {
|
|
15
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
16
|
+
|
|
17
|
+
async function doThing() {
|
|
18
|
+
setIsLoading(true);
|
|
19
|
+
try {
|
|
20
|
+
await delay(1000);
|
|
21
|
+
} finally {
|
|
22
|
+
setIsLoading(false);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<LoadingButton
|
|
28
|
+
loading={isLoading}
|
|
29
|
+
onClick={doThing}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const meta: Meta<typeof LoadingButton> = {
|
|
36
|
+
component: LoadingButton,
|
|
37
|
+
|
|
38
|
+
args: {
|
|
39
|
+
children: "Click me",
|
|
40
|
+
loadingPosition: "start",
|
|
41
|
+
variant: "contained",
|
|
42
|
+
startIcon: <PlayArrow />,
|
|
43
|
+
},
|
|
44
|
+
argTypes: {
|
|
45
|
+
loadingPosition: {
|
|
46
|
+
options: ["center", "start", "end"],
|
|
47
|
+
control: { type: 'select' },
|
|
48
|
+
},
|
|
49
|
+
variant: {
|
|
50
|
+
options: ["contained", "outlined", "text"],
|
|
51
|
+
control: { type: 'select' },
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
render: (props) => {
|
|
55
|
+
return <DemoLoader {...props} />;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
export default meta;
|
|
59
|
+
|
|
60
|
+
export const Default: StoryObj<typeof LoadingButton> = {
|
|
61
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LoadingButton as MUILoadingButton } from "@mui/lab"
|
|
2
|
+
import { forwardRef } from "react"
|
|
3
|
+
|
|
4
|
+
type LoadingButtonProps = React.ComponentProps<typeof MUILoadingButton>
|
|
5
|
+
|
|
6
|
+
/** Button with a loading state to indicate a task is being processed */
|
|
7
|
+
export const LoadingButton = forwardRef<HTMLButtonElement, LoadingButtonProps>(
|
|
8
|
+
({ sx, ...rest }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<MUILoadingButton
|
|
11
|
+
sx={{
|
|
12
|
+
...sx,
|
|
13
|
+
}}
|
|
14
|
+
ref={ref}
|
|
15
|
+
{...rest}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
},
|
|
19
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { makeErrorMessage } from "./utils/errorHandling"
|
|
2
|
+
import { CircularProgress, Stack, useTheme } from "@mui/material"
|
|
3
|
+
import { useEffect, useState } from "react"
|
|
4
|
+
|
|
5
|
+
export const LoadingCover = (props: {
|
|
6
|
+
message?: string
|
|
7
|
+
error?: unknown
|
|
8
|
+
softTimeout?: number
|
|
9
|
+
}) => {
|
|
10
|
+
const softTimeout = props.softTimeout || 3000
|
|
11
|
+
|
|
12
|
+
const [showSlowLoadingMessage, setShowSlowLoadingMessage] = useState(false)
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const timeout = setTimeout(() => {
|
|
16
|
+
setShowSlowLoadingMessage(true)
|
|
17
|
+
}, softTimeout)
|
|
18
|
+
|
|
19
|
+
return () => clearTimeout(timeout)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Stack
|
|
24
|
+
width="100%"
|
|
25
|
+
height="100%"
|
|
26
|
+
alignItems="center"
|
|
27
|
+
justifyContent="center"
|
|
28
|
+
>
|
|
29
|
+
{props.error ? (
|
|
30
|
+
<LoadingErrorMessage message={props.message} error={props.error} />
|
|
31
|
+
) : (
|
|
32
|
+
<>
|
|
33
|
+
<CircularProgress sx={{ marginBottom: "24px" }} />
|
|
34
|
+
{!!props.message && <div>{props.message}</div>}
|
|
35
|
+
<Stack
|
|
36
|
+
sx={{
|
|
37
|
+
visibility: showSlowLoadingMessage ? "visible" : "hidden",
|
|
38
|
+
marginTop: "1rem",
|
|
39
|
+
color: "gray",
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
{"This is taking longer than expected..."}
|
|
43
|
+
</Stack>
|
|
44
|
+
</>
|
|
45
|
+
)}
|
|
46
|
+
</Stack>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const LoadingErrorMessage = (props: { message?: string; error: unknown }) => {
|
|
51
|
+
const errorMessage = makeErrorMessage(props.error)
|
|
52
|
+
const stack = props.error instanceof Error ? props.error.stack : null
|
|
53
|
+
const theme = useTheme()
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Stack
|
|
57
|
+
sx={{
|
|
58
|
+
maxHeight: "100%",
|
|
59
|
+
maxWidth: "min(100%, 800px)",
|
|
60
|
+
padding: 2,
|
|
61
|
+
overflow: "auto",
|
|
62
|
+
color: theme.palette.error.main,
|
|
63
|
+
"& pre": {
|
|
64
|
+
whiteSpace: "pre-wrap",
|
|
65
|
+
wordBreak: "break-word",
|
|
66
|
+
paddingBottom: "3rem",
|
|
67
|
+
},
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{`Error while: ${props.message} - ${errorMessage}`}
|
|
71
|
+
<br />
|
|
72
|
+
{stack && <pre>{stack}</pre>}
|
|
73
|
+
</Stack>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// TODO implement this as part of theme?
|
|
2
|
+
|
|
3
|
+
import { useThemeColors } from "../themes/wbTheme"
|
|
4
|
+
import { Select, type SxProps } from "@mui/material"
|
|
5
|
+
import { defaultsDeep } from "lodash-es"
|
|
6
|
+
|
|
7
|
+
type ThemeSelectProps = {
|
|
8
|
+
kind: "filled" | "outlined" | "text"
|
|
9
|
+
} & React.ComponentProps<typeof Select>
|
|
10
|
+
|
|
11
|
+
export const ThemeSelect = ({ kind, sx, ...rest }: ThemeSelectProps) => {
|
|
12
|
+
const colors = useThemeColors()
|
|
13
|
+
|
|
14
|
+
let style: SxProps = defaultsDeep(sx, {
|
|
15
|
+
backgroundColor: colors.selectBackground,
|
|
16
|
+
borderRadius: "10px",
|
|
17
|
+
borderStyle: "none",
|
|
18
|
+
color: "currentColor",
|
|
19
|
+
"& > div": {
|
|
20
|
+
padding: "4px 16px",
|
|
21
|
+
},
|
|
22
|
+
"& fieldset": {
|
|
23
|
+
border: "none",
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (kind === "outlined") {
|
|
28
|
+
style = defaultsDeep(
|
|
29
|
+
{
|
|
30
|
+
backgroundColor: "transparent",
|
|
31
|
+
},
|
|
32
|
+
style,
|
|
33
|
+
)
|
|
34
|
+
} else if (kind === "text") {
|
|
35
|
+
style = defaultsDeep(
|
|
36
|
+
{
|
|
37
|
+
backgroundColor: "transparent",
|
|
38
|
+
|
|
39
|
+
"& .MuiSvgIcon-root": {
|
|
40
|
+
borderStyle: "none",
|
|
41
|
+
color: "inherit",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
style,
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return <Select sx={style} {...rest} />
|
|
49
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { VelocitySlider } from "./VelocitySlider";
|
|
3
|
+
import { useArgs } from "@storybook/preview-api";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof VelocitySlider> = {
|
|
6
|
+
component: VelocitySlider,
|
|
7
|
+
|
|
8
|
+
args: {
|
|
9
|
+
velocity: 1,
|
|
10
|
+
min: 1,
|
|
11
|
+
max: 100,
|
|
12
|
+
disabled: false,
|
|
13
|
+
},
|
|
14
|
+
render: function Component(args) {
|
|
15
|
+
const [, setArgs] = useArgs();
|
|
16
|
+
|
|
17
|
+
function onVelocityChange(newVelocity: number) {
|
|
18
|
+
args.onVelocityChange?.(newVelocity);
|
|
19
|
+
setArgs({ velocity: newVelocity });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return <VelocitySlider
|
|
23
|
+
{...args}
|
|
24
|
+
onVelocityChange={onVelocityChange}
|
|
25
|
+
/>;
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
};
|
|
29
|
+
export default meta;
|
|
30
|
+
|
|
31
|
+
export const Default: StoryObj<typeof VelocitySlider> = {
|
|
32
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import Slider from "@mui/material/Slider"
|
|
2
|
+
import { isNumber } from "lodash-es"
|
|
3
|
+
import { observer } from "mobx-react-lite"
|
|
4
|
+
import { Typography } from "@mui/material"
|
|
5
|
+
|
|
6
|
+
type VelocitySliderProps = {
|
|
7
|
+
min: number
|
|
8
|
+
max: number
|
|
9
|
+
velocity: number
|
|
10
|
+
onVelocityChange: (newVelocity: number) => void
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
valueLabelFormat?: (value: number) => string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A slider for controlling the movement velocity of a robot */
|
|
16
|
+
export const VelocitySlider = observer((props: VelocitySliderProps) => {
|
|
17
|
+
const valueLabelFormat = props.valueLabelFormat || ((value: number) => `${value}`)
|
|
18
|
+
|
|
19
|
+
function onSliderChange(_event: Event, newVelocity: number | number[]) {
|
|
20
|
+
if (newVelocity === props.velocity || !isNumber(newVelocity)) return
|
|
21
|
+
|
|
22
|
+
props.onVelocityChange(newVelocity)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<Typography
|
|
28
|
+
sx={{
|
|
29
|
+
textAlign: "center",
|
|
30
|
+
fontSize: "15px",
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
{valueLabelFormat(props.velocity)}
|
|
34
|
+
</Typography>
|
|
35
|
+
<Slider
|
|
36
|
+
value={props.velocity}
|
|
37
|
+
onChange={onSliderChange}
|
|
38
|
+
min={props.min}
|
|
39
|
+
max={props.max}
|
|
40
|
+
aria-labelledby="input-slider"
|
|
41
|
+
disabled={props.disabled}
|
|
42
|
+
sx={{
|
|
43
|
+
"& .MuiSlider-valueLabelOpen": {
|
|
44
|
+
zIndex: 100,
|
|
45
|
+
backgroundColor: "transparent",
|
|
46
|
+
top: "0px",
|
|
47
|
+
},
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
</>
|
|
51
|
+
)
|
|
52
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { JoggingCartesianAxisControl } from "./JoggingCartesianAxisControl";
|
|
3
|
+
import { useRef, useState } from "react";
|
|
4
|
+
import { useAnimationFrame } from "../utils/hooks";
|
|
5
|
+
import { useArgs } from "@storybook/preview-api";
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof JoggingCartesianAxisControl> = {
|
|
8
|
+
component: JoggingCartesianAxisControl,
|
|
9
|
+
|
|
10
|
+
args: {
|
|
11
|
+
color: "#F14D42",
|
|
12
|
+
label: "X",
|
|
13
|
+
disabled: false,
|
|
14
|
+
},
|
|
15
|
+
render: function Component(args) {
|
|
16
|
+
const [, setArgs] = useArgs();
|
|
17
|
+
|
|
18
|
+
const joggingDirRef = useRef<"+" | "-" | null>(null);
|
|
19
|
+
const joggingValueRef = useRef(0);
|
|
20
|
+
|
|
21
|
+
useAnimationFrame(() => {
|
|
22
|
+
if (joggingDirRef.current === "+") {
|
|
23
|
+
joggingValueRef.current += 1;
|
|
24
|
+
} else if (joggingDirRef.current === "-") {
|
|
25
|
+
joggingValueRef.current -= 1;
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return <JoggingCartesianAxisControl
|
|
30
|
+
{...args}
|
|
31
|
+
startJogging={(direction) => joggingDirRef.current = direction}
|
|
32
|
+
stopJogging={() => joggingDirRef.current = null}
|
|
33
|
+
getDisplayedValue={() => joggingValueRef.current.toString()}
|
|
34
|
+
/>;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
};
|
|
38
|
+
export default meta;
|
|
39
|
+
|
|
40
|
+
export const Default: StoryObj<typeof JoggingCartesianAxisControl> = {
|
|
41
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Button, Typography } from "@mui/material"
|
|
2
|
+
import Stack from "@mui/material/Stack"
|
|
3
|
+
import { observer } from "mobx-react-lite"
|
|
4
|
+
import { useRef, type ReactNode } from "react"
|
|
5
|
+
import { useAnimationFrame } from "../utils/hooks"
|
|
6
|
+
|
|
7
|
+
type JoggingCartesianAxisControlProps = {
|
|
8
|
+
color?: string
|
|
9
|
+
label: ReactNode
|
|
10
|
+
getDisplayedValue: () => string
|
|
11
|
+
startJogging: (direction: "-" | "+") => void
|
|
12
|
+
stopJogging: () => void
|
|
13
|
+
disabled?: boolean
|
|
14
|
+
} & React.ComponentProps<typeof Stack>
|
|
15
|
+
|
|
16
|
+
export const JoggingCartesianAxisControl = observer(
|
|
17
|
+
({
|
|
18
|
+
color,
|
|
19
|
+
label,
|
|
20
|
+
getDisplayedValue,
|
|
21
|
+
startJogging,
|
|
22
|
+
stopJogging,
|
|
23
|
+
disabled,
|
|
24
|
+
...rest
|
|
25
|
+
}: JoggingCartesianAxisControlProps) => {
|
|
26
|
+
useAnimationFrame(() => {
|
|
27
|
+
const displayValue = getDisplayedValue()
|
|
28
|
+
const element = valueContainerRef.current
|
|
29
|
+
if (!element) return
|
|
30
|
+
|
|
31
|
+
element.textContent = displayValue
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const valueContainerRef = useRef<HTMLParagraphElement>(null)
|
|
35
|
+
|
|
36
|
+
color = color || "#F14D42"
|
|
37
|
+
|
|
38
|
+
function onPointerDownMinus(ev: React.PointerEvent) {
|
|
39
|
+
// Stop right click from triggering jog
|
|
40
|
+
if (ev.button === 0) startJogging("-")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function onPointerDownPlus(ev: React.PointerEvent) {
|
|
44
|
+
if (ev.button === 0) startJogging("+")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Stack height="72px" direction="row" {...rest}>
|
|
49
|
+
<Button
|
|
50
|
+
onPointerDown={onPointerDownMinus}
|
|
51
|
+
onPointerUp={stopJogging}
|
|
52
|
+
onPointerOut={stopJogging}
|
|
53
|
+
disabled={disabled}
|
|
54
|
+
sx={{
|
|
55
|
+
width: "105px",
|
|
56
|
+
backgroundColor: color,
|
|
57
|
+
color: "white",
|
|
58
|
+
alignContent: "center",
|
|
59
|
+
fontSize: "37px",
|
|
60
|
+
borderRadius: "16px 0px 0px 16px",
|
|
61
|
+
|
|
62
|
+
":hover": {
|
|
63
|
+
color: "white",
|
|
64
|
+
backgroundColor: color,
|
|
65
|
+
},
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
{"-"}
|
|
69
|
+
</Button>
|
|
70
|
+
|
|
71
|
+
<Stack
|
|
72
|
+
spacing="6px"
|
|
73
|
+
sx={{
|
|
74
|
+
width: "184px",
|
|
75
|
+
backgroundColor: color,
|
|
76
|
+
alignItems: "center",
|
|
77
|
+
justifyContent: "center",
|
|
78
|
+
opacity: "0.9",
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<Stack
|
|
82
|
+
height="22px"
|
|
83
|
+
direction="row"
|
|
84
|
+
alignItems="center"
|
|
85
|
+
justifyItems="center"
|
|
86
|
+
spacing={1}
|
|
87
|
+
sx={{ userSelect: "none" }}
|
|
88
|
+
>
|
|
89
|
+
{label}
|
|
90
|
+
</Stack>
|
|
91
|
+
<Typography
|
|
92
|
+
height="22px"
|
|
93
|
+
sx={{
|
|
94
|
+
fontSize: "15px",
|
|
95
|
+
color: "white",
|
|
96
|
+
}}
|
|
97
|
+
ref={valueContainerRef}
|
|
98
|
+
>
|
|
99
|
+
{getDisplayedValue()}
|
|
100
|
+
</Typography>
|
|
101
|
+
</Stack>
|
|
102
|
+
|
|
103
|
+
<Button
|
|
104
|
+
onPointerDown={onPointerDownPlus}
|
|
105
|
+
onPointerUp={stopJogging}
|
|
106
|
+
onPointerOut={stopJogging}
|
|
107
|
+
disabled={disabled}
|
|
108
|
+
sx={{
|
|
109
|
+
width: "105px",
|
|
110
|
+
backgroundColor: color,
|
|
111
|
+
color: "white",
|
|
112
|
+
alignContent: "center",
|
|
113
|
+
fontSize: "37px",
|
|
114
|
+
borderRadius: "0px 16px 16px 0px",
|
|
115
|
+
|
|
116
|
+
":hover": {
|
|
117
|
+
color: "white",
|
|
118
|
+
backgroundColor: color,
|
|
119
|
+
},
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{"+"}
|
|
123
|
+
</Button>
|
|
124
|
+
</Stack>
|
|
125
|
+
)
|
|
126
|
+
},
|
|
127
|
+
)
|