MVP with Tensorboard

This commit is contained in:
IDONTSUDO 2024-12-01 16:12:08 +00:00 committed by Igor Brylev
parent 5b9b51ad59
commit a0d089d5bb
549 changed files with 39977 additions and 31449 deletions

3
ui/.gitignore vendored
View file

@ -21,4 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
todo.md
todo.md
package-lock.json

1
ui/.mason/bricks.json Normal file
View file

@ -0,0 +1 @@
{"base_feature":"/Users/idontsudo/ozon/ui/bricks/base_feature","form":"/Users/idontsudo/webservice/ui/bricks/form"}

View file

@ -0,0 +1,11 @@
import { Result } from "../../core/helper/result";
export class NumberTriviaModel {
constructor() {}
isValid(): Result<string, void> {
return Result.ok();
}
static empty() {
return new NumberTriviaModel();
}
}

View file

@ -0,0 +1,3 @@
export class {{name.pascalCase()}}Repository {
}

View file

@ -0,0 +1,10 @@
import { observer } from "mobx-react-lite";
import { {{name.pascalCase()}}Store } from "./{{name.snakeCase()}}_store";
import React from "react";
export const {{name.pascalCase()}}ScreenPath = "/auth";
export const {{name.pascalCase()}}Screen = observer(() => {
const [store] = React.useState(() => new {{name.pascalCase()}}Store());
return <></>;
});

View file

@ -0,0 +1,7 @@
import makeAutoObservable from "mobx-store-inheritance";
export class {{name.pascalCase()}}Store {
constructor() {
makeAutoObservable(this);
}
}

View file

@ -0,0 +1,16 @@
name: base_feature
description: A brick to create Clean Architecture Feature
version: 0.0.1
# The following defines the environment for the current brick.
# It includes the version of mason that the brick requires.
environment:
mason: ">=0.1.0-dev.26 <0.1.0"
vars:
name:
type: string
description: The feature name
default: my feature
prompt: What's your feature name (e.g. dance school)

View file

@ -0,0 +1,11 @@
import { Result } from "../../../../../../core/helper/result";
export class NumberTriviaModel {
constructor() {}
isValid(): Result<string, void> {
return Result.ok();
}
static empty() {
return new NumberTriviaModel();
}
}

View file

@ -0,0 +1,14 @@
import React from "react";
import { observer } from "mobx-react-lite";
import { {{ name.pascalCase() }}Store } from "./{{name.snakeCase()}}_store";
import { IPropsForm } from "../forms";
import { NumberTriviaModel } from "./number_trivia";
export const {{ name.pascalCase()}} = observer((props: IPropsForm<Partial<NumberTriviaModel>>) => {
const [store] = React.useState(() => new {{ name.pascalCase() }}Store());
React.useEffect(() => {
store.init();
}, [store]);
return <></>;
});

View file

@ -0,0 +1,5 @@
import { HttpRepository } from "../../../../../../core/repository/http_repository";
export class {{name.pascalCase()}}HttpRepository extends HttpRepository {
}

View file

@ -0,0 +1,19 @@
import makeAutoObservable from "mobx-store-inheritance";
import { NavigateFunction } from "react-router-dom";
import { NumberTriviaModel } from "./number_trivia";
import { FormState, CoreError } from "../../../../../../core/store/base_store";
import { {{name.pascalCase()}}HttpRepository } from "./{{name.snakeCase()}}_http_repository";
export class {{name.pascalCase()}}Store extends FormState<NumberTriviaModel, CoreError> {
constructor() {
super();
makeAutoObservable(this);
}
viewModel: NumberTriviaModel = NumberTriviaModel.empty();
cameraDeviceHttpRepository: {{name.pascalCase()}}HttpRepository = new {{name.pascalCase()}}HttpRepository();
errorHandingStrategy = (error: CoreError) => { }
init = async (navigate?: NavigateFunction | undefined) => {
}
}

16
ui/bricks/form/brick.yaml Normal file
View file

@ -0,0 +1,16 @@
name: form
description: A brick to create form at behavior_tree_builder
version: 0.0.1
# The following defines the environment for the current brick.
# It includes the version of mason that the brick requires.
environment:
mason: ">=0.1.0-dev.26 <0.1.0"
vars:
name:
type: string
description: The feature name
default: my feature
prompt: What's your feature name (e.g. dance school)

1
ui/mason-lock.json Normal file
View file

@ -0,0 +1 @@
{"bricks":{"base_feature":{"path":"bricks/base_feature"},"form":{"path":"bricks/form"}}}

5
ui/mason.yaml Normal file
View file

@ -0,0 +1,5 @@
bricks:
base_feature:
path: base_feature
form:
path: form

18562
ui/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,50 +3,46 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@monaco-editor/react": "^4.6.0",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.46",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@types/socket.io-client": "^3.0.0",
"@types/uuid": "^9.0.2",
"@foxglove/cdr": "^3.3.0",
"@foxglove/rosmsg": "^5.0.4",
"@foxglove/rosmsg2-serialization": "^2.0.3",
"@foxglove/ws-protocol": "^0.7.3",
"antd": "^4.24.15",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"formik-antd": "^2.0.4",
"i18next": "^23.6.0",
"just-clone": "^6.2.0",
"mobx": "^6.10.0",
"mobx-react-lite": "^4.0.4",
"mobx-store-inheritance": "^1.0.6",
"pattern-matching-ts": "^2.0.0",
"react": "^18.2.0",
"react-accessible-treeview": "^2.8.3",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-i18next": "^13.3.1",
"react-infinite-scroll-component": "^6.1.0",
"react-resizable-panels": "^2.1.7",
"react-router-dom": "^6.18.0",
"react-scripts": "5.0.1",
"reflect-metadata": "^0.1.13",
"rete-connection-plugin": "^2.0.0",
"rete-react-plugin": "^2.0.4",
"rete-render-utils": "^2.0.1",
"sass": "^1.66.1",
"serve": "^14.2.1",
"socket.io-client": "^4.7.2",
"source-map-loader": "^5.0.0",
"styled-components": "^6.1.8",
"three": "^0.159.0",
"three-stdlib": "^2.28.9",
"three-transform-controls": "^1.0.4",
"ts-pattern": "^5.0.6",
"typescript": "^4.9.5",
"three": "^0.152.2",
"ts-pattern": "^5.1.1",
"typescript": "^5.0.2",
"urdf-loader": "^0.12.1",
"uuid": "^9.0.1",
"web-vitals": "^2.1.4",
"ws": "^8.17.0",
"xml-formatter": "^3.6.2"
},
"overrides": {
"typescript": "^5.0.2"
},
"scripts": {
"dev": "react-scripts start",
"build": "react-scripts build",
@ -56,8 +52,7 @@
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
"react-app"
]
},
"browserslist": {
@ -73,6 +68,12 @@
]
},
"devDependencies": {
"@types/three": "^0.158.3"
"@types/jest": "^27.5.2",
"@types/node": "^16.18.46",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@types/socket.io-client": "^3.0.0",
"@types/three": "^0.158.3",
"@types/uuid": "^9.0.2"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1,29 +1,187 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/logo.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap"
rel="stylesheet"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
rel="stylesheet"
/>
<title>Robossembler</title>
</head>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>robossembler: pipeline </title>
</head>
<body style="overflow: auto;">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div style="width: 100%; height: 100%" id="root"></div>
</body>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<style>
@import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap");
</style>
<style>
[contenteditable]:focus {
outline: 0px solid transparent;
}
/* Absolute Center Spinner */
.loading {
}
</body>
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap')
</style>
<style>
[contenteditable]:focus {
outline: 0px solid transparent;
}
</style>
/* Transparent Overlay */
.loading:before {
content: "";
display: block;
}
</html>
/* :not(:required) hides these rules from IE9 and below */
.loading:not(:required) {
/* hide "loading..." text */
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
.loading:not(:required):after {
margin-top: 14px;
content: "";
display: block;
font-size: 8px;
width: 8px;
height: 8px;
-webkit-animation: spinner 1500ms infinite linear;
-moz-animation: spinner 1500ms infinite linear;
-ms-animation: spinner 1500ms infinite linear;
-o-animation: spinner 1500ms infinite linear;
animation: spinner 1500ms infinite linear;
border-radius: 0.5em;
-webkit-box-shadow: white 1.5em 0 0 0, white 1.1em 1.1em 0 0, white 0 1.5em 0 0, white -1.1em 1.1em 0 0,
rgba(0, 0, 0, 0.5) -1.5em 0 0 0, rgba(0, 0, 0, 0.5) -1.1em -1.1em 0 0, white 0 -1.5em 0 0,
white 1.1em -1.1em 0 0;
box-shadow: white 1.5em 0 0 0, white 1.1em 1.1em 0 0, white 0 1.5em 0 0, white -1.1em 1.1em 0 0,
white -1.5em 0 0 0, white -1.1em -1.1em 0 0, white 0 -1.5em 0 0, white 1.1em -1.1em 0 0;
}
/* Animation */
@-webkit-keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-moz-keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-o-keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spinner {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-ms-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.dotted {
background-image: -webkit-repeating-radial-gradient(
center center,
rgba(147, 147, 147, 30%),
rgba(147, 147, 147, 30%) 2px,
transparent 2px,
transparent 100%
);
background-image: -moz-repeating-radial-gradient(
center center,
rgba(147, 147, 147, 30%),
rgba(147, 147, 147, 30%) 2px,
transparent 2px,
transparent 100%
);
background-image: -ms-repeating-radial-gradient(
center center,
rgba(147, 147, 147, 30%),
rgba(147, 147, 147, 30%) 2px,
transparent 2px,
transparent 100%
);
background-image: repeating-radial-gradient(
center center,
rgba(147, 147, 147, 30%),
rgba(147, 147, 147, 30%) 2px,
transparent 2px,
transparent 100%
);
-webkit-background-size: 20px 20px;
-moz-background-size: 20px 20px;
background-size: 20px 20px;
width: 100%;
height: 100%;
}
::-webkit-scrollbar-thumb {
background-color: #d6dee1;
border-radius: 20px;
}
</style>
</html>

BIN
ui/public/logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
ui/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

54
ui/readme.md Normal file
View file

@ -0,0 +1,54 @@
# Установка зависимостей
```
brew install mason
```
### Инициализация
В корне проекта вызовите команду `init`, которая создаст папку `.mason/`
```
mason init
```
### Использование 👷
Все готовые `brick'и` хранятся в папке `/bricks`
Для примера попробуем использовать `brick` под названием `base_feature`
```
# Добавляем `brick` себе в mason (посмотреть уже добавленные можно через `mason ls/list`, а удалить через `mason remove`)
mason add base_feature --path bricks/base_feature
# Используем `brick` для в интересующей нас папке (поскольку это шаблон фичи, выбрана папка src/features/)
mason make base_feature -o lib/features
```
Далее необходимо ответить на вопросы задаваемые в CLI и на основе ответов `brick` сгенериует фичу
### Разработка
В папке `bricks/` вы можете создать свой `brick`
```
# создать hello brick
mason new my_brick_name
```
Далее всю структуру папок и файлов необходимо описать в папке `__brick__`
Для большей информации [читайте и смотрите примеры](https://github.com/felangel/mason/blob/master/packages/mason_cli/README.md)
# Добавить новую форму в Behavior tree builder
mason make form -o ./src/features/behavior_tree_builder/presentation/ui/forms
# Добавить новую форму в Scene Manager
mason make form -o ./src/features/scene_manager/presentation/forms
# Добавить новый экран
mason make base_feature -o ./src/features/

View file

@ -3,10 +3,10 @@
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_bgCarrier" strokeWidth="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M12 7.75732L12 16.2426" stroke="#f46036" stroke-width="2" stroke-linecap="round"/> <path d="M7.75735 12L16.2426 12" stroke="#f46036" stroke-width="2" stroke-linecap="round"/> <rect x="3" y="3" width="18" height="18" rx="3" stroke="#f46036" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g>
<g id="SVGRepo_iconCarrier"> <path d="M12 7.75732L12 16.2426" stroke="#f46036" strokeWidth="2" strokeLinecap="round"/> <path d="M7.75735 12L16.2426 12" stroke="#f46036" strokeWidth="2" strokeLinecap="round"/> <rect x="3" y="3" width="18" height="18" rx="3" stroke="#f46036" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/> </g>
</svg>

Before

Width:  |  Height:  |  Size: 769 B

After

Width:  |  Height:  |  Size: 759 B

Before After
Before After

View file

@ -1,7 +1,12 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <g id="Interface / Check"> <path id="Vector" d="M6 12L10.2426 16.2426L18.727 7.75732" stroke="#f8f9fa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g> </g>
</svg>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="64px" height="12" "strokeWidth="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" strokeWidth="0"/>strokeWidth
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"/>
<g id="SVGRepo_iconCarrier"> <g id="Interface / Check"> <path id="Vector" d="M6 12L10.2426 16.2426L18.727 7.75732" stroke="#f8f9fa" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/> </g> </g>
</svg>

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 636 B

Before After
Before After

View file

@ -1,7 +1,12 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M15.8351 11.6296L9.20467 5.1999C8.79094 4.79869 8 5.04189 8 5.5703L8 18.4297C8 18.9581 8.79094 19.2013 9.20467 18.8001L15.8351 12.3704C16.055 12.1573 16.0549 11.8427 15.8351 11.6296Z" fill="#013a63"/> </g>
</svg>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="64px" height="6strokeWidth="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" strokeWidth="0"/>
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M15.8351 11.6296L9.20467 5.1999C8.79094 4.79869 8 5.04189 8 5.5703L8 18.4297C8 18.9581 8.79094 19.2013 9.20467 18.8001L15.8351 12.3704C16.055 12.1573 16.0549 11.8427 15.8351 11.6296Z" fill="#013a63"/> </g>
</svg>

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 660 B

Before After
Before After

View file

@ -3,11 +3,11 @@
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="64px" height="64px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_bgCarrier" strokeWidth="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"/>
<g id="SVGRepo_iconCarrier">
<rect width="24" height="24" fill="none"/> <path d="M7 17L16.8995 7.10051" stroke="#f55d3e" stroke-linecap="round" stroke-linejoin="round"/> <path d="M7 7.00001L16.8995 16.8995" stroke="#f46036" stroke-linecap="round" stroke-linejoin="round"/> </g>
<rect width="24" height="24" fill="none"/> <path d="M7 17L16.8995 7.10051" stroke="#f55d3e" strokeLinecap="round" strokeLinejoin="round"/> <path d="M7 7.00001L16.8995 16.8995" stroke="#f46036" strokeLinecap="round" strokeLinejoin="round"/> </g>
</svg>

Before

Width:  |  Height:  |  Size: 701 B

After

Width:  |  Height:  |  Size: 694 B

Before After
Before After

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 64 64" id="error"><circle cx="32" cy="32" r="28" fill="none" stroke="#010101" stroke-miterlimit="10" stroke-width="4"></circle><line x1="32" x2="32" y1="18" y2="38" fill="none" stroke="#010101" stroke-miterlimit="10" stroke-width="4"></line><line x1="32" x2="32" y1="42" y2="46" fill="none" stroke="#010101" stroke-miterlimit="10" stroke-width="4"></line></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 64 64" id="error"><circle cx="32" cy="32" r="28" fill="none" stroke="#010101" stroke-miterlimit="10" strokeWidth="4"></circle><line x1="32" x2="32" y1="18" y2="38" fill="none" stroke="#010101" stroke-miterlimit="10" strokeWidth="4"></line><line x1="32" x2="32" y1="42" y2="46" fill="none" stroke="#010101" stroke-miterlimit="10" strokeWidth="4"></line></svg>

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 431 B

Before After
Before After

View file

@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800px" height="800px" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7071 4.29289C12.0976 4.68342 12.0976 5.31658 11.7071 5.70711L6.41421 11H20C20.5523 11 21 11.4477 21 12C21 12.5523 20.5523 13 20 13H6.41421L11.7071 18.2929C12.0976 18.6834 12.0976 19.3166 11.7071 19.7071C11.3166 20.0976 10.6834 20.0976 10.2929 19.7071L3.29289 12.7071C3.10536 12.5196 3 12.2652 3 12C3 11.7348 3.10536 11.4804 3.29289 11.2929L10.2929 4.29289C10.6834 3.90237 11.3166 3.90237 11.7071 4.29289Z" fill="#000000"/>
<path fillRule="evenodd" clipRule="evenodd" d="M11.7071 4.29289C12.0976 4.68342 12.0976 5.31658 11.7071 5.70711L6.41421 11H20C20.5523 11 21 11.4477 21 12C21 12.5523 20.5523 13 20 13H6.41421L11.7071 18.2929C12.0976 18.6834 12.0976 19.3166 11.7071 19.7071C11.3166 20.0976 10.6834 20.0976 10.2929 19.7071L3.29289 12.7071C3.10536 12.5196 3 12.2652 3 12C3 11.7348 3.10536 11.4804 3.29289 11.2929L10.2929 4.29289C10.6834 3.90237 11.3166 3.90237 11.7071 4.29289Z" fill="#000000"/>
</svg>

Before

Width:  |  Height:  |  Size: 584 B

After

Width:  |  Height:  |  Size: 582 B

Before After
Before After

View file

@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800px" height="800px" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.7071 1.29289C14.0976 1.68342 14.0976 2.31658 13.7071 2.70711L12.4053 4.00896C17.1877 4.22089 21 8.16524 21 13C21 17.9706 16.9706 22 12 22C7.02944 22 3 17.9706 3 13C3 12.4477 3.44772 12 4 12C4.55228 12 5 12.4477 5 13C5 16.866 8.13401 20 12 20C15.866 20 19 16.866 19 13C19 9.2774 16.0942 6.23349 12.427 6.01281L13.7071 7.29289C14.0976 7.68342 14.0976 8.31658 13.7071 8.70711C13.3166 9.09763 12.6834 9.09763 12.2929 8.70711L9.29289 5.70711C9.10536 5.51957 9 5.26522 9 5C9 4.73478 9.10536 4.48043 9.29289 4.29289L12.2929 1.29289C12.6834 0.902369 13.3166 0.902369 13.7071 1.29289Z" fill="#0F1729"/>
<path fillRule="evenodd" clipRule="evenodd" d="M13.7071 1.29289C14.0976 1.68342 14.0976 2.31658 13.7071 2.70711L12.4053 4.00896C17.1877 4.22089 21 8.16524 21 13C21 17.9706 16.9706 22 12 22C7.02944 22 3 17.9706 3 13C3 12.4477 3.44772 12 4 12C4.55228 12 5 12.4477 5 13C5 16.866 8.13401 20 12 20C15.866 20 19 16.866 19 13C19 9.2774 16.0942 6.23349 12.427 6.01281L13.7071 7.29289C14.0976 7.68342 14.0976 8.31658 13.7071 8.70711C13.3166 9.09763 12.6834 9.09763 12.2929 8.70711L9.29289 5.70711C9.10536 5.51957 9 5.26522 9 5C9 4.73478 9.10536 4.48043 9.29289 4.29289L12.2929 1.29289C12.6834 0.902369 13.3166 0.902369 13.7071 1.29289Z" fill="#0F1729"/>
</svg>

Before

Width:  |  Height:  |  Size: 755 B

After

Width:  |  Height:  |  Size: 753 B

Before After
Before After

View file

@ -1,7 +1,28 @@
import { Result } from "../helper/result";
/* eslint-disable @typescript-eslint/no-this-alias */
export const ArrayExtensions = () => {
if ([].indexOfR === undefined) {
Array.prototype.indexOfR = function (element) {
if (this.indexOf(element) === -1) {
return Result.error(undefined);
}
return Result.ok(this);
};
}
if ([].atR === undefined) {
Array.prototype.atR = function (index) {
if (index === undefined) {
return Result.error(undefined);
}
const result = this.at(index);
if (result) {
return Result.ok(result);
}
return Result.error(undefined);
};
}
if ([].equals === undefined) {
// eslint-disable-next-line no-extend-native
Array.prototype.equals = function (array, strict = true) {
if (!array) return false;
@ -21,8 +42,25 @@ export const ArrayExtensions = () => {
return true;
};
}
if ([].replacePropIndex === undefined) {
Array.prototype.replacePropIndex = function (property, index) {
return this.map((element, i) => {
if (i === index) {
element = Object.assign(element, property);
}
return element;
});
};
}
if ([].someR === undefined) {
Array.prototype.someR = function (predicate) {
if (this.some(predicate)) {
return Result.error(undefined);
}
return Result.ok(this);
};
}
if ([].lastElement === undefined) {
// eslint-disable-next-line no-extend-native
Array.prototype.lastElement = function () {
const instanceCheck = this;
if (instanceCheck === undefined) {
@ -34,27 +72,52 @@ export const ArrayExtensions = () => {
};
}
if ([].isEmpty === undefined) {
// eslint-disable-next-line no-extend-native
Array.prototype.isEmpty = function () {
return this.length === 0;
};
}
if ([].isNotEmpty === undefined) {
// eslint-disable-next-line no-extend-native
Array.prototype.isNotEmpty = function () {
return this.length !== 0;
};
}
if ([].hasIncludeElement === undefined) {
// eslint-disable-next-line no-extend-native
Array.prototype.hasIncludeElement = function (element) {
return this.indexOf(element) !== -1;
};
}
if ([].repeat === undefined) {
// eslint-disable-next-line no-extend-native
Array.prototype.repeat = function (quantity) {
return Array(quantity).fill(this[0]);
return Array(quantity).fill(this).flat(1);
};
}
if ([].rFind === undefined) {
Array.prototype.rFind = function (predicate) {
const result = this.find(predicate as any);
if (result === undefined) {
return Result.error(undefined);
}
return Result.ok(result);
};
}
if ([].maxLength === undefined) {
Array.prototype.maxLength = function (length) {
if (this.length > length) {
return this;
} else {
return this.slice(0, length);
}
};
}
if ([].add === undefined) {
Array.prototype.add = function (element) {
this.push(element);
return this;
};
}
if ([].updateAll === undefined) {
Array.prototype.updateAll = function (element) {
return this.map((el) => Object.assign(el, element));
};
}
};

View file

@ -0,0 +1,12 @@
export const DateExtensions = () => {
if (Date.prototype.fromUnixDate === undefined) {
Date.prototype.fromUnixDate = function (unix) {
return new Date(unix * 1000);
};
}
if (Date.prototype.formatDate === undefined) {
Date.prototype.formatDate = function () {
return `${this.getFullYear()}.${this.getMonth()}.${this.getDay()} ${this.getHours()}:${this.getMinutes()}`;
};
}
};

View file

@ -1,4 +1,6 @@
import { Result } from "../helper/result";
import { ArrayExtensions } from "./array";
import { DateExtensions } from "./date";
import { MapExtensions } from "./map";
import { NumberExtensions } from "./number";
import { StringExtensions } from "./string";
@ -20,35 +22,63 @@ declare global {
isNotEmpty(): boolean;
hasIncludeElement(element: T): boolean;
repeat(quantity: number): Array<T>;
rFind<T>(predicate: (value: T, index: number, obj: never[]) => boolean, thisArg?: any): Result<void, T>;
maxLength(length: number): Array<T>;
add(element: T): Array<T>;
indexOfR(element: T): Result<void, Array<T>>;
replacePropIndex(property: Partial<T>, index: number): T[];
someR(predicate: (value: T) => boolean): Result<void, Array<T>>;
updateAll(value: Partial<T>): Array<T>;
atR(index: number | undefined): Result<void, T>;
}
interface Date {
formatDate(): string;
fromUnixDate(unix: number): Date;
}
interface Number {
fromArray(): number[];
toPx(): string;
unixFromDate(): string;
isValid(str: string): boolean;
randRange(min:number,max:number):number
randRange(min: number, max: number): number;
isPositive(): boolean;
isNegative(): boolean;
isEven(): boolean;
isOdd(): boolean;
isEqualR(number: number): Result<void, void>;
}
interface String {
isEmpty(): boolean;
isNotEmpty(): boolean;
isNotEmptyR(): Result<void, string>;
replaceMany(searchValues: string[], replaceValue: string): string;
isEqual(str: string): boolean;
isEqualMany(str: string[]): boolean;
hasPattern(pattern: string): boolean;
hasNoPattern(pattern: string): boolean;
divideByIndex(index: number): string[];
isEqualR(str: string): Result<void, string>;
}
interface Map<K, V> {
addValueOrMakeCallback(key: K, value: V, callBack: CallBackVoidFunction): void;
getKeyFromValueIsExists(value: V): K | undefined;
overrideValue(key: K, value: OptionalProperties<V>): void;
keysToJson(): string;
toArray(): V[];
getPredicateValue(callBack: (value: V) => boolean): K[];
toArrayEntries(): { key: K; value: V }[];
getPredicateKey(callBack: (value: V) => boolean): K[];
incrementValue(key: K): void;
}
interface Boolean {
r(): Result<void, void>;
}
interface Vector3 {}
}
export const extensions = () => {
StringExtensions();
ArrayExtensions();
NumberExtensions();
MapExtensions();
DateExtensions();
};

View file

@ -1,6 +1,5 @@
export const MapExtensions = () => {
if (Map.prototype.addValueOrMakeCallback === undefined) {
// eslint-disable-next-line no-extend-native
Map.prototype.addValueOrMakeCallback = function (key, value, fn) {
if (this.has(key)) {
this.set(key, value);
@ -12,7 +11,6 @@ export const MapExtensions = () => {
};
}
if (Map.prototype.getKeyFromValueIsExists === undefined) {
// eslint-disable-next-line no-extend-native
Map.prototype.getKeyFromValueIsExists = function (value) {
let result;
this.forEach((el, key) => {
@ -24,7 +22,6 @@ export const MapExtensions = () => {
};
}
if (Map.prototype.overrideValue === undefined) {
// eslint-disable-next-line no-extend-native
Map.prototype.overrideValue = function (key, value) {
const result = this.get(key);
@ -32,22 +29,29 @@ export const MapExtensions = () => {
};
}
if (Map.prototype.keysToJson === undefined) {
// eslint-disable-next-line no-extend-native
Map.prototype.keysToJson = function () {
const result: any[] = [];
this.forEach((el) => result.push(el));
return JSON.stringify(result);
};
}
if (Map.prototype.toArrayEntries === undefined) {
Map.prototype.toArrayEntries = function () {
const result = [];
for (const el of this.entries()) {
result.push({ key: el[0], value: el[1] });
}
return result;
};
}
if (Map.prototype.toArray === undefined) {
// eslint-disable-next-line no-extend-native
Map.prototype.toArray = function () {
return Array.from(this.values());
};
}
if (Map.prototype.getPredicateValue === undefined) {
// eslint-disable-next-line no-extend-native
Map.prototype.getPredicateValue = function (callBack) {
if (Map.prototype.getPredicateKey === undefined) {
Map.prototype.getPredicateKey = function (callBack) {
const result: any[] = [];
this.forEach((el, key) => {
const callBackExecute = callBack(el);
@ -58,4 +62,13 @@ export const MapExtensions = () => {
return result;
};
}
if (Map.prototype.incrementValue === undefined) {
Map.prototype.incrementValue = function (key) {
if (this.get(key)) {
this.set(key, this.get(key) + 1);
} else {
this.set(key, 1);
}
};
}
};

View file

@ -1,33 +1,58 @@
import { Result } from "../helper/result";
export const NumberExtensions = () => {
if (Number().fromArray === undefined) {
// eslint-disable-next-line no-extend-native
Number.prototype.fromArray = function () {
return Array.from(this.toString()).map((el) => Number(el));
};
}
if (Number().toPx === undefined) {
// eslint-disable-next-line no-extend-native
Number.prototype.toPx = function () {
return String(this) + "px";
};
}
if (Number().unixFromDate === undefined) {
// eslint-disable-next-line no-extend-native
Number.prototype.unixFromDate = function () {
const date = new Date(Number(this) * 1000);
return `${date.getUTCFullYear()}.${date.getMonth()}.${date.getDay()} ${date.getHours()}:${date.getMinutes()}`;
};
}
if (Number().isEqualR === undefined) {
Number.prototype.isEqualR = function (num) {
if(this === num) {
return Result.ok(undefined)
}
return Result.error(undefined)
};
}
if (Number().isValid === undefined) {
// eslint-disable-next-line no-extend-native
Number.prototype.isValid = function (str: string) {
return !isNaN(Number(str));
};
}
if(Number().randRange === undefined){
// eslint-disable-next-line no-extend-native
Number.prototype.randRange = function (min,max) {
if (Number().randRange === undefined) {
Number.prototype.randRange = function (min, max) {
return Math.random() * (max - min) + min;
}
};
}
if (Number().isPositive === undefined) {
Number.prototype.isPositive = function () {
return Math.sign(Number(this)) === 1;
};
}
if (Number().isNegative === undefined) {
Number.prototype.isNegative = function () {
return !this.isPositive();
};
}
if (Number().isEven === undefined) {
Number.prototype.isEven = function () {
return Number(this) % 2 === 0;
};
}
if (Number().isOdd === undefined) {
Number.prototype.isOdd = function () {
return !this.isEven();
};
}
};

View file

@ -1,7 +1,6 @@
/* eslint-disable no-extend-native */
export const ObjectExtensionsIsKeyExists = (obj: any, keys: string[]): boolean => {
return true;
export const ObjectIsNotEmpty = (obj: Object | undefined) => {
if (obj === undefined) {
return false;
}
return Object.keys(obj).length !== 0;
};
// {"objectThatSticksName":"cube2","objectThatSticksNamePoints":[{"x":25,"y":4.987889622413917,"z":10.504078531217838}],"objectsToWhichItSticksName":"cube1","objectsToWhichItSticksPoints":[{"x":5,"y":3.0783236330074963,"z":1.1333166084347885}]}

View file

@ -1,17 +1,33 @@
import { Result } from "../helper/result";
/* eslint-disable no-extend-native */
export const StringExtensions = () => {
if ("".isEmpty === undefined) {
// eslint-disable-next-line no-extend-native
String.prototype.isEmpty = function () {
return this.length === 0;
};
}
if ("".isNotEmpty === undefined) {
// eslint-disable-next-line no-extend-native
String.prototype.isNotEmpty = function () {
return this.length !== 0;
};
}
if ("".isNotEmptyR === undefined) {
String.prototype.isNotEmptyR = function () {
if (this.isEmpty()) {
return Result.error(undefined);
}
return Result.ok(String(this));
};
}
if ("".isEqualR === undefined) {
String.prototype.isEqualR = function (str) {
if (this === str) {
return Result.ok(String(this));
}
return Result.error(undefined);
};
}
if ("".replaceMany === undefined) {
String.prototype.replaceMany = function (searchValues: string[], replaceValue: string) {
let result = this as string;
@ -36,4 +52,23 @@ export const StringExtensions = () => {
return false;
};
}
if ("".hasPattern === undefined) {
String.prototype.hasPattern = function (pattern) {
return new RegExp(pattern).test(this as string);
};
}
if ("".hasNoPattern === undefined) {
String.prototype.hasNoPattern = function (pattern) {
return !this.hasPattern(pattern);
};
}
if ("".divideByIndex === undefined) {
String.prototype.divideByIndex = function (index) {
if (this.at(index) === undefined) {
return [];
}
return [this.slice(0, index), this.slice(index + 1, this.length)];
};
}
};

View file

@ -40,3 +40,5 @@ export class TypedEvent<T> {
return this.on((e) => te.emit(e));
};
}

View file

@ -0,0 +1,31 @@
import { ClassConstructor } from "class-transformer";
import React from "react";
import { NavigateFunction, useNavigate } from "react-router-dom";
interface LifeCycleStore {
init?: (navigate?: NavigateFunction | undefined) => void;
dispose?: () => void;
}
export const useStore = <S extends LifeCycleStore>(storeConstructor: ClassConstructor<S>) => {
const [store] = React.useState(new storeConstructor());
const navigate = useNavigate();
React.useEffect(() => {
store?.init?.(navigate);
return () => {
store?.dispose?.();
};
}, []);
return store;
};
export const useStoreClass = <S extends LifeCycleStore>(storeClass: S) => {
const [store] = React.useState(storeClass);
const navigate = useNavigate();
React.useEffect(() => {
store?.init?.(navigate);
return () => {
store?.dispose?.();
};
}, []);
return store;
};

View file

@ -1,3 +0,0 @@
export function validateRequired(value: string) {
return value ? undefined : "required";
}

View file

@ -1,26 +0,0 @@
import React from "react";
export const useKeyLister = (fn: Function) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
const pressed = new Map();
const registerKeyPress = React.useCallback(
(event: KeyboardEvent, codes: string[], callBack: Function) => {
if (codes.hasIncludeElement(event.code)) {
pressed.addValueOrMakeCallback(event.code, event.type, (e) => {
if (Array.from(pressed.values()).equals(["keydown", "keydown"], false)) {
callBack();
}
});
}
},
[pressed]
);
React.useEffect(() => {
window.addEventListener("keyup", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => fn));
window.addEventListener("keydown", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => {}));
}, [fn, registerKeyPress]);
return [];
};

View file

@ -0,0 +1,53 @@
import { Quaternion, Vector3, PerspectiveCamera, CameraHelper } from "three";
import { CoreThreeRepository, UserData } from "../repository/core_three_repository";
import { Result } from "../helper/result";
import { SceneModelsTypes } from "./scene_models_type";
import { Instance } from "./scene_asset";
export enum CameraTypes {
RGB = "RGB",
}
export class CameraModel implements Instance {
type = SceneModelsTypes.CAMERA;
jointType = 'fixed';
constructor(
public orientation: number[],
public position: Vector3,
public name: string,
public cameraType: CameraTypes,
public width: number,
public updateRate: number,
public fov: number,
public near: number,
public far: number,
public height: number,
public topic: string,
public aspect: number,
public parent?: string,
public fixed?: string
) {}
update = (coreThreeRepository: CoreThreeRepository) => this.toWebGl(coreThreeRepository);
icon: string = "Camera";
toWebGl = (coreThreeRepository: CoreThreeRepository) => {
const camera = coreThreeRepository.scene.getObjectByName(this.name);
if (camera) {
coreThreeRepository.scene.remove(coreThreeRepository.scene.getObjectByName(this.name + "camera_helper")!);
coreThreeRepository.scene.remove(camera);
}
const perspectiveCamera = new PerspectiveCamera(this.fov, this.aspect, this.near, this.far);
perspectiveCamera.name = this.name;
perspectiveCamera.position.copy(this.position);
perspectiveCamera.quaternion.copy(new Quaternion().fromArray(this.orientation));
const cameraHelper = new CameraHelper(perspectiveCamera);
cameraHelper.name = this.name + "camera_helper";
perspectiveCamera.userData[UserData.selectedObject] = true;
cameraHelper.userData[UserData.selectedObject] = true;
coreThreeRepository.scene.add(...[perspectiveCamera, cameraHelper]);
};
validate = (): Result<string, CameraModel> => {
return Result.ok(this);
};
static empty = () => new CameraModel([], new Vector3(0, 0, 0), "", CameraTypes.RGB, 0, 0, 50, 0.1, 2000, 0, "", 0);
}

View file

@ -0,0 +1,63 @@
export interface Cameras {
camera: Camera[];
}
export interface Camera {
sid: string;
DevicePackage: DevicePackage;
Module: Module;
Launch: Launch;
DTwin: DTwin[];
Interface: InterfaceClass;
Settings: Setting[];
}
export interface DTwin {
interface: Interface;
}
export interface Interface {
input: Input[];
}
export interface Input {
camera_namespace?: string;
camera_name?: string;
serial_port?: string;
pose?: string;
}
export interface DevicePackage {
name: string;
version: string;
format: string;
}
export interface InterfaceClass {
param: Param[];
}
export interface Param {
type: string;
dependency: Dependency;
}
export interface Dependency {
}
export interface Launch {
package: string;
executable: string;
}
export interface Module {
name: string;
description: string;
}
export interface Setting {
name: string;
description: string;
type: string;
defaultValue: string;
}

View file

@ -1,17 +0,0 @@
import { Vector3 } from "three";
export class CoreVector3 {
vector: Vector3;
constructor(vector: Vector3) {
this.vector = vector;
}
static divideLineIntoEqualSegments(beginPoint: Vector3, endPoint: Vector3, segments: number): Vector3[] {
return Number(segments)
.fromArray()
.map((el) => new Vector3().lerpVectors(beginPoint, endPoint, (el * 1) / segments));
}
add(vector: Vector3) {
return new CoreVector3(new Vector3(this.vector.x + vector.x, this.vector.y + vector.y, this.vector.z + vector.z));
}
}

View file

@ -1,3 +1,3 @@
export interface DatabaseModel {
export interface DatabaseModelId {
_id?: string;
}

View file

@ -0,0 +1,17 @@
import { IsNotEmpty } from "class-validator";
import { Result } from "../helper/result";
import { IDeviceDependency } from "./skill_model";
import { ValidationModel } from "./validation_model";
export class SidViewModel extends ValidationModel implements IDeviceDependency {
@IsNotEmpty()
sid: string;
constructor(sid: string) {
super();
this.sid = sid;
}
static empty() {
return new SidViewModel("");
}
}

View file

@ -0,0 +1,8 @@
export enum FormType {
weights = "weights",
robotName = "robot_name",
cameraDeviceForm = "camera",
topic = "topic",
formBuilder = "formBuilder",
moveToPose = "move_to_pose",
}

View file

@ -0,0 +1,144 @@
import { IsNotEmpty, IsString } from "class-validator";
import { BehaviorTreeBuilderStore } from "../../features/behavior_tree_builder/presentation/behavior_tree_builder_store";
import {
datasetFormMockContext,
datasetFormMockResult,
defaultFormValue,
} from "../../features/dataset/dataset_model";
import { DependencyViewModel } from "./skill_model";
import { ValidationModel } from "./validation_model";
import { FormType } from "./form";
import makeAutoObservable from "mobx-store-inheritance";
export class FormBuilderValidationModel
extends ValidationModel
implements DependencyViewModel
{
@IsNotEmpty()
@IsString()
public result: string;
@IsNotEmpty()
@IsString()
public context: string;
public form: string[];
public output: any;
constructor(context: string, result: string, form: string[], output: string) {
super();
makeAutoObservable(this);
this.context = context;
this.result = result;
this.form = form;
this.output = output;
}
type: FormType = FormType.formBuilder;
toView = (store: BehaviorTreeBuilderStore | undefined) => <>IsFilled</>;
static isEmpty = (formBuilderValidationModel: FormBuilderValidationModel) =>
formBuilderValidationModel.context.isEmpty() &&
formBuilderValidationModel.result.isEmpty() &&
formBuilderValidationModel.form.isEmpty();
static test = () =>
new FormBuilderValidationModel(ffContext, ff1Result, [], "");
static datasetEmpty = () =>
new FormBuilderValidationModel(
datasetFormMockContext,
datasetFormMockResult,
[],
defaultFormValue
);
static empty = () => new FormBuilderValidationModel("", "", [], "");
static emptyTest = () =>
new FormBuilderValidationModel(``, ``, [], defaultFormValue);
static creteDataSetTest = () =>
new FormBuilderValidationModel(``, scene, [], "");
static emptySimple = () =>
new FormBuilderValidationModel("", simpleFormBuilder, [], "");
static vision = () =>
new FormBuilderValidationModel(
`
ENUM T = "ObjectDetection","PoseEstimation";
ENUM L = "POINT","SUN";
ENUM F = "JPEG","PNG";
ENUM COLLISION_SHAPE = "BOX","COLLISION";
type OBJECTS_SCENE = {
"name": \${NAME:string:default},
"collision_shape": \${collision_shape:Enum<COLLISION_SHAPE>:BOX},
"loc_xyz": [\${LOC_XYZ_1:number:0}, \${LOC_XYZ_2:number:0}, \${LOC_XYZ_3:number:0}],
"rot_euler": [\${ROT_EULER_1:number:0},\${ROT_EULER_2:number:0}, \${ROT_EULER_3:number:0}],
"material_randomization": {
"specular": [\${SPECULAR_1:number:0}, \${SPECULAR_2:number:1}],
"roughness": [\${ROUGHNESS_1:number:0}, \${ROUGHNESS_2:number:1}],
"metallic": [\${METALLIC_1:number:0}, \${METALLIC_2:number:1}],
"base_color": [
[
\${BASE_COLOR_1:number:0},
\${BASE_COLOR_2:number:0},
\${BASE_COLOR_3:number:0},
\${BASE_COLOR_4:number:1}
],
[
\${BASE_COLOR_5:number:1},
\${BASE_COLOR_6:number:1},
\${BASE_COLOR_7:number:1},
\${BASE_COLOR_8:number:1}
]
]
}
};
type LIGHTS = {
"id": \${ID:number:1},
"type": \${type:Enum<L>:POINT},
"loc_xyz": [\${LOC_XYZ_1:number:5}, \${LOC_XYZ_2:number:5}, \${LOC_XYZ_3:number:5}],
"rot_euler": [\${ROT_EULER_1:number:-0.06}, \${ROT_EULER_2:number:0.61}, \${ROT_EULER_3:number:-0.19}],
"color_range_low": [\${COLOR_RANGE_LOW_1:number:0.5}, \${COLOR_RANGE_LOW_2:number:0.5}, \${COLOR_RANGE_LOW_3:number:0.5}],
"color_range_high":[\${COLOR_RANGE_HIGH_1:number:1}, \${COLOR_RANGE_HIGH_2:number:1}, $\{COLOR_RANGE_HIGH_3:number:1}],
"energy_range":[\${ENERGY_RANGE_1:number:400},\${ENERGY_RANGE_2:number:900}]
};`,
`
{
"datasetObjects":\${<SelectDetail/>:OBJECT:{"details": []},
"typedataset": \${typedataset:Enum<T>:ObjectDetection},
"models_randomization":{
"loc_range_low": [\${LOC_RANGE_LOW_1:number:-1}, \${LOC_RANGE_LOW_2:number:-1},\${LOC_RANGE_LOW_3:number:0}],
"loc_range_high": [\${LOC_RANGE_HIGH_1:number:1}, \${LOC_RANGE_HIGH_2:number:1},\${LOC_RANGE_HIGH_3:number:2}]
},
"scene":{
"objects": \${OBJECTS_SCENE:Array<OBJECTS_SCENE>:[]},
"lights": \${LIGHTS:Array<LIGHTS>:[]}
},
"camera_position":{
"center_shell": [\${CENTER_SHELL_1:number:0}, \${CENTER_SHELL_2:number:0}, \${CENTER_SHELL_3:number:0}],
"radius_range": [\${RADIUS_RANGE_1:number:1}, \${RADIUS_RANGE_2:number:1.4}],
"elevation_range": [\${ELEVATION_RANGE_1:number:10}, \${ELEVATION_RANGE_2:number:90}]
},
"generation":{
"n_cam_pose": \${N_CAM_POSE:number:5},
"n_sample_on_pose": \${N_SAMPLE_ON_POSE:number:3},
"n_series": \${N_SERIES:number:100},
"image_format": \${image_format:Enum<F>:JPEG},
"image_size_wh": [\${IMAGE_SIZE_WH_1:number:640}, \${IMAGE_SIZE_WH_2:number:480}]
}
}
`,
[],
""
);
}
export const scene = `{
"center_shell": [\${CENTER_SHELL_1:number:0}, \${CENTER_SHELL_2:number:0}, \${CENTER_SHELL_3:number:0}],
"scene":\${<SelectScene/>:OBJECT:{"details": []}
}`;
export const simpleFormBuilder = `{
"center_shell": [\${CENTER_SHELL_1:number:0}, \${CENTER_SHELL_2:number:0}, \${CENTER_SHELL_3:number:0}]
}`;
export const ffContext = `type ITEM = {
"name": \${NAME:string:default},
"value": \${VALUE:string:default}
};
`;
export const ff1Result = `{
"detailForm":\${<SelectDetail/>:OBJECT:{"details": []},
"empty":\${NAME:string:default},
"params": \${ITEM:Array<ITEM>:[]}
}`;

View file

@ -0,0 +1,57 @@
import { DirectionalLight, Object3D, PointLight, Quaternion, SpotLight, Vector3 } from "three";
import { Result } from "../helper/result";
import { SceneModelsTypes } from "./scene_models_type";
import { CoreThreeRepository } from "../repository/core_three_repository";
import { Instance } from "./scene_asset";
import { Type } from "class-transformer";
export enum TypeLight {
POINT = "POINT",
DIRECTIONAL = "DIRECTIONAL",
SPOT = "SPOT",
}
export class LightModel implements Instance {
type = SceneModelsTypes.LIGHT;
color: string;
typeLight: TypeLight;
intensity: number;
width: number;
height: number;
distance: number;
angle: number;
penumbra: number;
decay: number;
@Type(() => Vector3)
position: Vector3;
orientation: number[];
constructor() {}
update = (coreThreeRepository: CoreThreeRepository) => this.toWebGl(coreThreeRepository);
icon: string = "Light";
name: string;
isValid = (): Result<void, LightModel> => {
// SDF -> point, directional, spot.
// THREE -> PointLight,DirectionalLight,SpotLight
return Result.ok(this);
};
toWebGl = (coreThreeRepository: CoreThreeRepository) => {
// TODO: maybe change mapper
let light: Object3D;
this.typeLight
.isEqualR(TypeLight.DIRECTIONAL)
.map(() => (light = new DirectionalLight(this.color, this.intensity)));
this.typeLight
.isEqualR(TypeLight.POINT)
.map(() => (light = new PointLight(this.color, this.intensity, this.distance, this.decay)));
this.typeLight
.isEqualR(TypeLight.SPOT)
.map(
() => (light = new SpotLight(this.color, this.intensity, this.distance, this.angle, this.penumbra, this.decay))
);
light!.castShadow = true;
light!.position.copy(this.position);
light!.quaternion.copy(new Quaternion().fromArray(this.orientation));
coreThreeRepository.scene.add(light!);
};
static empty = () => new LightModel();
}

View file

@ -0,0 +1,57 @@
import { Quaternion, Vector3 } from "three";
import { Result } from "../helper/result";
import { SceneModelsTypes } from "./scene_models_type";
import { Instance } from "./scene_asset";
import { CoreThreeRepository } from "../repository/core_three_repository";
import { Type } from "class-transformer";
import { Pose } from "../../features/behavior_tree_builder/presentation/ui/forms/move_to_pose/move_to_pose_robot_model";
import { SceneBuilderStore, SceneItems } from "../../features/scene_builder/presentation/scene_builder_store";
export class PointModel implements Instance {
type = SceneModelsTypes.POINT;
name: string;
@Type(() => Vector3)
position: Vector3;
color: string = "#E91E63";
size: number = 0.01;
orientation: number[];
icon: string = "Point";
constructor() {}
update = (coreThreeRepository: CoreThreeRepository) => this.toWebGl(coreThreeRepository);
toWebGl = (coreThreeRepository: CoreThreeRepository) =>
coreThreeRepository.makePoint(
this.name,
this.position,
new Quaternion().fromArray(this.orientation),
this.color,
this.size
);
toSceneItems = (sceneMangerStore: SceneBuilderStore): SceneItems => {
return {
fn: () => {},
name: this.name,
isSelected: false,
icon: this.icon,
};
};
toDependency = (): Pose => {
return {
name: this.name,
position: this.position,
orientation: {
x: this.orientation[0],
y: this.orientation[1],
z: this.orientation[2],
w: this.orientation[3],
},
};
};
isValid(): Result<string, PointModel> {
return Result.ok();
}
static empty() {
return new PointModel();
}
}

View file

@ -1,172 +0,0 @@
import { IsArray, IsEnum, IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
export class Gravity {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
}
export class Pose {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
@IsNumber()
roll: number;
@IsNumber()
pitch: number;
@IsNumber()
yaw: number;
}
export class Position {
@IsNumber()
x: number;
@IsNumber()
y: number;
@IsNumber()
z: number;
}
export enum InstanceType {
RGB_CAMERA = "rgb_camera",
SCENE_SIMPLE_OBJECT = "scene_simple_object",
}
abstract class CoreInstances {}
export class Instance extends CoreInstances {
@IsEnum(InstanceType)
instanceType: InstanceType;
@Type(() => Position)
position: Position;
@IsArray()
quaternion: number[];
@IsOptional()
@IsString()
instanceAt: null | string = null;
}
export class SceneSimpleObject extends Instance {}
export class InstanceRgbCamera extends Instance {
@IsString()
cameraLink: string;
@IsString()
topicCameraInfo: string;
@IsOptional()
@IsString()
topicDepth: string | null;
@IsString()
topicImage: string;
}
export class Asset {
@IsString()
name: string;
@IsString()
ixx: string;
@IsString()
ixy: string;
@IsString()
ixz: string;
@IsString()
iyy: string;
@IsString()
izz: string;
@IsString()
mass: string;
@IsString()
posX: string;
@IsString()
posY: string;
@IsString()
posZ: string;
@IsString()
eulerX: string;
@IsString()
eulerY: string;
@IsString()
eulerZ: string;
@IsString()
iyz: string;
@IsString()
meshPath: string;
@IsString()
friction: string;
@IsString()
centerMassX: string;
@IsString()
centerMassY: string;
@IsString()
centerMassZ: string;
@IsArray()
@IsOptional()
actions: string[];
}
export class Physics {
@IsString()
engine_name: string;
@Type(() => Gravity)
gravity: Gravity;
}
export class RobossemblerAssets {
@ValidateNested()
@Type(() => Asset, {
discriminator: {
property: "type",
subTypes: [
{ value: InstanceRgbCamera, name: InstanceType.RGB_CAMERA },
{ value: SceneSimpleObject, name: InstanceType.SCENE_SIMPLE_OBJECT },
],
},
keepDiscriminatorProperty: true,
})
assets: Asset[];
@IsArray()
@Type(() => Instance, {
discriminator: {
property: "type",
subTypes: [
{ value: InstanceRgbCamera, name: InstanceType.RGB_CAMERA },
{ value: SceneSimpleObject, name: InstanceType.SCENE_SIMPLE_OBJECT },
],
},
keepDiscriminatorProperty: true,
})
instances: Instance[];
@IsOptional()
@ValidateNested()
@Type(() => Physics)
physics: Physics;
convertLocalPathsToServerPaths(server_address: string): RobossemblerAssets {
this.assets = this.assets.map((el) => {
el.meshPath = server_address + el.meshPath;
return el;
});
return this;
}
getAssetPath(assetName: string): string {
const findElement = this.assets.find((el) => el.name === assetName);
if (findElement === undefined) {
throw new Error("RobossemblerAssets.getAssetPath not found asset by name:" + assetName);
}
return findElement.meshPath;
}
getAssetAtInstance(instanceAt: string): Asset {
return this.assets.filter((el) => el.name === instanceAt)[0];
}
}

View file

@ -0,0 +1,74 @@
import { Quaternion, Vector3 } from "three";
import { SceneModelsTypes } from "./scene_models_type";
import { Result } from "../helper/result";
import { Instance } from "./scene_asset";
import { CoreThreeRepository } from "../repository/core_three_repository";
import { Type } from "class-transformer";
import { URDFRobot } from "urdf-loader";
import { SceneBuilderStore, SceneItems } from "../../features/scene_builder/presentation/scene_builder_store";
export enum ToolTypes {
RBS_GRIPPER = "RBS_GRIPPER",
}
interface RobotJoint {
limit: {
lower: number;
upper: number;
};
angle: number;
name: string;
}
export class RobotModel implements Instance {
type = SceneModelsTypes.ROBOT;
@Type(() => Vector3)
position: Vector3;
jointPosition: RobotJoint[];
orientation: number[];
name: string;
httpUrl: string;
nDof: number;
toolType: string;
constructor(
position: Vector3,
orientation: number[],
name: string,
httpUrl: string,
nDof: number,
toolType: string,
jointPosition: RobotJoint[]
) {
this.orientation = orientation;
this.position = position;
this.name = name;
this.httpUrl = httpUrl;
this.nDof = nDof;
this.toolType = toolType;
this.jointPosition = jointPosition;
}
icon: string = "Robot";
toSceneItems = (sceneBuilderStore: SceneBuilderStore): SceneItems => {
return {
fn: () => {},
name: this.name,
isSelected: false,
icon: "Robot",
};
};
toWebGl = (coreThreeRepository: CoreThreeRepository) => {
coreThreeRepository.loadUrdfRobot(this);
};
update = (coreThreeRepository: CoreThreeRepository) => {
const robot = coreThreeRepository.scene.getObjectByName(this.name) as URDFRobot;
robot.position.copy(this.position);
robot.quaternion.copy(new Quaternion().fromArray(this.orientation));
this.jointPosition.forEach((el) => {
robot.setJointValue(el.name, el.angle);
});
};
isValid(): Result<string, RobotModel> {
return Result.ok(this);
}
static empty = () => new RobotModel(new Vector3(0, 0, 0), [0, 0, 0, 1], "", "", 1, ToolTypes.RBS_GRIPPER, []);
}

View file

@ -0,0 +1,47 @@
export interface Robots {
robots: Robot[];
}
export interface Robot {
sid: string;
DevicePackage: DevicePackage;
Module: Module;
Launch: Launch;
DTwin: DTwin[];
Settings: Setting[];
}
export interface DTwin {
interface: Interface;
}
export interface Interface {
input: Input[];
}
export interface Input {
robot_namespace?: string;
dof?: number;
}
export interface DevicePackage {
name: string;
version: string;
format: string;
}
export interface Launch {
package: string;
executable: string;
}
export interface Module {
name: string;
description: string;
}
export interface Setting {
name: string;
description: string;
defaultValue: string;
}

View file

@ -0,0 +1,65 @@
import { IsArray } from "class-validator";
import { Type } from "class-transformer";
import { CameraModel } from "./camera_model";
import { SceneModelsTypes } from "./scene_models_type";
import { RobotModel } from "./robot_model";
import { SolidModel } from "./solid_model";
import { PointModel } from "./point_model";
import { ZoneModel } from "./zone_model";
import { CoreThreeRepository } from "../repository/core_three_repository";
import { LightModel } from "./light_model";
import { Vector3 } from "three";
import { SceneItems } from "../../features/scene_builder/presentation/scene_builder_store";
export abstract class Instance {
type: string;
abstract icon: string;
@Type(() => Vector3)
position: Vector3;
orientation: number[];
name: string;
toWebGl = (_coreThreeRepository: CoreThreeRepository) => {};
update = (_coreThreeRepository: CoreThreeRepository) => {};
}
export class SceneAsset {
name: string;
@IsArray()
@Type(() => Instance, {
discriminator: {
property: "type",
subTypes: [
{ value: SolidModel, name: SceneModelsTypes.SOLID },
{ value: CameraModel, name: SceneModelsTypes.CAMERA },
{ value: RobotModel, name: SceneModelsTypes.ROBOT },
{ value: PointModel, name: SceneModelsTypes.POINT },
{ value: LightModel, name: SceneModelsTypes.LIGHT },
{ value: ZoneModel, name: SceneModelsTypes.ZONE },
],
},
keepDiscriminatorProperty: true,
})
scene: (Instance | SolidModel | CameraModel | RobotModel | PointModel | ZoneModel)[];
toSceneItems = (): SceneItems[] => {
return this.scene.map((el) => {
return {
fn: () => {},
name: el.name,
icon: el.icon,
isSelected: false,
};
});
};
getElementByName = <T>(name: string) => this.scene.filter((el) => el.name.isEqual(name)).at(0) as T;
getAllRobotsTopics = () => this.scene.filter((el) => el.type.isEqual(SceneModelsTypes.ROBOT)).map((el) => el.name);
getAllCameraTopics = () => {};
getAllPoints = () => this.scene.filter((el) => el.type.isEqual(SceneModelsTypes.POINT)).map((el) => el.name);
static newScene = (
scene: (Instance | SolidModel | CameraModel | RobotModel | PointModel | ZoneModel)[],
name: string
) => {
const sceneAsset = new SceneAsset();
sceneAsset.scene = scene;
sceneAsset.name = name;
return sceneAsset;
};
}

View file

@ -0,0 +1,8 @@
export enum SceneModelsTypes {
SOLID = "SOLID",
ROBOT = "ROBOT",
LIGHT = "LIGHT",
CAMERA = "CAMERA",
POINT = "POINT",
ZONE = "ZONE",
}

View file

@ -0,0 +1,512 @@
import makeAutoObservable from "mobx-store-inheritance";
import clone from "just-clone";
import { IsArray, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateNested } from "class-validator";
import { Type } from "class-transformer";
import { ISkillView } from "../../features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree";
import { v4 } from "uuid";
import { Result } from "../helper/result";
import { ValidationModel } from "./validation_model";
import { ITopicModel, TopicViewModel } from "../../features/topics/topic_view_model";
import { BehaviorTreeBuilderStore } from "../../features/behavior_tree_builder/presentation/behavior_tree_builder_store";
import { TopicDependencyViewModel } from "../../features/behavior_tree_builder/presentation/ui/forms/topics_form/topic_dependency_view_model";
import { FormBuilderValidationModel } from "./form_builder_validation_model";
import { FormType } from "./form";
export interface IDependency {
skills: ISkillDependency[];
}
export interface ISkillDependency {
sid: string;
dependency: Object;
}
export interface ISkill {
sid?: string;
SkillPackage: ISkillPackage;
Module: IModule;
BTAction: IBTAction[];
}
export interface IWeightsDependency {
weights_name: string;
object_name: string;
weights_file: string;
dimensions: number[];
}
export interface IDeviceDependency {
sid: string;
}
export interface IDependency {}
export interface IParam {
isFilled: boolean;
type: string;
dependency?: DependencyViewModel;
}
abstract class IDependencyViewModel {
abstract type: FormType;
}
export class DependencyViewModel implements IDependencyViewModel {
type: FormType;
static empty = () => new DependencyViewModel();
toView = (store: BehaviorTreeBuilderStore | undefined): React.ReactNode => "Dependency view model error";
}
export class ParamViewModel implements IParam {
type: string;
@Type(() => DependencyViewModel, {
discriminator: {
property: "type",
subTypes: [
{ value: TopicDependencyViewModel, name: FormType.topic },
{ value: FormBuilderValidationModel, name: FormType.formBuilder },
],
},
keepDiscriminatorProperty: true,
})
dependency: DependencyViewModel;
isFilled: boolean = false;
constructor(type: string, dependency: DependencyViewModel) {
this.type = type;
this.dependency = dependency;
}
static empty = () => new ParamViewModel("", DependencyViewModel.empty());
}
export interface IBTAction {
name: string;
type: string;
param: IParam[];
result: string[];
}
export enum BtAction {
ACTION = "ACTION",
CONDITION = "CONDITION",
}
export class BtActionViewModel extends ValidationModel implements IBTAction {
@IsNotEmpty()
@IsString()
name: string;
@IsNotEmpty()
@IsString()
type: string;
@Type(() => ParamViewModel)
param: IParam[];
@IsNotEmpty()
@IsString()
result: string[];
@IsNotEmpty()
@IsEnum(BtAction)
typeAction: BtAction;
constructor(name: string, type: string, param: IParam[], result: string[], typeAction: BtAction) {
super();
this.name = name;
this.type = type;
this.param = param;
this.result = result;
this.typeAction = typeAction;
}
static empty = () => new BtActionViewModel("", "", [], [], BtAction.ACTION);
public validParam = (type: string) => this.param.someR((param) => param.type === type);
}
export interface IInterface {
Input: IPut[];
Output: IPut[];
}
export interface IPut {
name: string;
type: string;
}
export interface ILaunch {
executable: string;
}
export interface IModule {
name: string;
node_name: string;
description: string;
}
export interface IRos2 {
node_name: string;
}
export interface ISetting {
name: string;
value: number | string;
}
export interface ISkillPackage {
name: string;
version: string;
format: string;
}
export class SkillPackage implements ISkillPackage {
@IsNotEmpty()
@IsString()
name: string;
@IsNotEmpty()
@IsString()
version: string;
@IsNotEmpty()
@IsString()
format: string;
constructor(name: string, version: string, format: string) {
this.name = name;
this.format = format;
this.version = version;
}
static empty = () => new SkillPackage("", "", "");
}
export class Module implements IModule {
@IsString()
node_name: string;
@IsString()
name: string;
@IsString()
description: string;
constructor(name: string, description: string) {
this.name = name;
this.description = description;
}
static empty = () => new Module("", "");
}
export class BTAction implements IBTAction {
@IsString()
name: string;
@IsString()
type: string;
sid?: string;
@IsArray()
param: IParam[];
@IsArray()
result: string[];
}
export class Launch implements ILaunch {
@IsNotEmpty()
@IsString()
executable: string;
@IsNotEmpty()
@IsString()
package: string;
constructor(executable: string, plage: string) {
this.executable = executable;
this.package = plage;
}
static empty = () => new Launch("", "");
}
export class Ros2 implements IRos2 {
@IsString()
node_name: string;
}
export class Put implements IPut {
@IsString()
name: string;
@IsString()
type: string;
}
export class Interface implements IInterface {
@ValidateNested()
@Type(() => Put)
Input: IPut[];
@ValidateNested()
@Type(() => Put)
Output: IPut[];
}
export class Setting implements ISetting {
name: string;
value: string | number;
}
export class SkillModel extends ValidationModel implements ISkill {
@IsOptional()
@IsString()
_id?: string;
constructor() {
super();
makeAutoObservable(this);
}
bgColor: string = `rgba(5, 26, 39, 1)`;
borderColor: string = "rgba(25, 130, 196, 1)";
@IsOptional()
@IsString()
sid?: string;
@ValidateNested()
@Type(() => SkillPackage)
SkillPackage: ISkillPackage;
@ValidateNested()
@Type(() => Module)
Module: IModule;
@IsNotEmpty()
@IsArray()
@Type(() => BtActionViewModel)
BTAction: BtActionViewModel[];
topicsOut: TopicViewModel[] = [];
@Type(() => Launch)
Launch: Launch;
@Type(() => FormBuilderValidationModel)
Settings: FormBuilderValidationModel;
static empty() {
const skillModel = new SkillModel();
skillModel.BTAction = [];
skillModel.SkillPackage = SkillPackage.empty();
skillModel.Module = Module.empty();
skillModel.Launch = Launch.empty();
skillModel.Settings = FormBuilderValidationModel.empty();
return skillModel;
}
public static isEmpty(skill: SkillModel): Result<void, SkillModel> {
if (skill.BTAction.isEmpty()) {
return Result.error(undefined);
}
return Result.ok(Object.assign(skill, {}));
}
public getSid = () => this.sid;
public setSid = (sid: string) => {
const result = clone(this);
result.sid = sid;
return result;
};
emptyParam = () => this.BTAction.at(0)?.param.at(0)?.dependency === undefined;
}
export class SkillDependency implements IDependency {
constructor(public skills: ISkillDependency[]) {}
static empty() {
return new SkillDependency([]);
}
static isEmpty = (skill: SkillDependency) => {
if (skill.skills.isEmpty()) {
return Result.error(undefined);
}
return Result.ok(skill);
};
}
export class Skills {
constructor() {
makeAutoObservable(this);
}
@IsOptional()
@IsArray()
@Type(() => TopicViewModel)
topicsStack: ITopicModel[] = [];
@IsArray()
@Type(() => SkillModel)
skills: SkillModel[];
static fromSkills = (skilsModel: SkillModel[]) => {
const skills = this.empty();
skills.skills = skilsModel;
return skills;
};
deleteTopic = (sid: string) => (this.topicsStack = this.topicsStack.filter((el) => !el.sid?.isEqual(sid)));
getSkillAtSid = (sid: string): Result<void, SkillModel> => {
const result = this.skills.filter((skill) => skill.sid?.isEqual(sid)).at(0);
if (result === undefined) {
return Result.error(undefined);
}
return Result.ok(result);
};
validation = (): Result<string[], void> => {
const errors: string[] = [];
this.skills.forEach((skill) => {
skill.BTAction.forEach((action) => {
if (action.param.isNotEmpty()) {
action.param.forEach((param) => {
if (Object.keys(param?.dependency ?? {}).isEmpty()) {
errors.push(param.type);
}
});
}
});
});
if (errors.isNotEmpty()) {
return Result.error(errors);
}
return Result.ok(undefined);
};
getCssAtLabel = (label: string): React.CSSProperties =>
this.skills.reduce<React.CSSProperties>((acc, el) => {
el.BTAction.rFind<BtActionViewModel>((el) => el.name.isEqual(label)).map(() => {
acc = {
backgroundColor: el.bgColor,
border: `1px solid ${el.borderColor}`,
};
});
return acc;
}, {});
skillBySid = (sid: string) =>
SkillModel.isEmpty(
this.skills.reduce<SkillModel>((acc, el) => {
if (el.sid?.isEqual(sid)) {
acc = el;
}
return acc;
}, SkillModel.empty())
);
toSkillView = (): ISkillView[] =>
this.skills.map((el) => {
return {
name: el.Module.name,
children: el.BTAction.map((act) => {
return { name: act.name, uuid: v4() };
}),
};
});
getSkill = (name: string) =>
SkillModel.isEmpty(
this.skills.reduce<SkillModel>((acc, el) => {
if (el.BTAction.find((el) => el.name.isEqual(name))) {
el.BTAction.map((action) => {
action.param.map((param) => {
return param;
});
});
acc = el;
}
return acc;
}, SkillModel.empty())
);
getSkilsOut = (name: string) =>
this.skills
.reduce<string[][]>((acc, el) => {
if (el.BTAction.find((el) => el.name.isEqual(name))) {
acc = el.BTAction.filter((f) => f.name.isEqual(name)).map((z) => z.result);
}
return acc;
}, [])
.flat(1);
getSkillParams = (name: string) =>
this.skills
.reduce<IParam[][]>((acc, el) => {
if (el.BTAction.find((el) => el.name.isEqual(name))) {
acc = el.BTAction.filter((f) => f.name.isEqual(name)).map((z) => z.param);
}
return acc;
}, [])
.flat(1);
getSkillDo = (name: string) =>
this.skills.reduce((acc, el) => {
if (el.BTAction.find((el) => el.name.isEqual(name))) {
acc = el.Module.name;
}
return acc;
}, "error");
getSkillsNames = () =>
this.skills
.map((el) => {
return el.BTAction.map((act) => {
return { name: act.name };
});
})
.flat(1);
getForms = (skillLabel: string) =>
this.skills
.reduce<SkillModel[]>((acc, el) => {
if (el.BTAction.find((el) => el.name.isEqual(skillLabel))) {
acc.push(el);
}
return acc;
}, [])
.map((el) => el.BTAction.map((act) => act.param.map((el) => el.type).flat(1)))
.flat(1)
.flat(1)
.filter((el) => el !== "");
getDependencyBySkillLabelAndType = (skillType: string, sid: string): DependencyViewModel =>
this.skills
.reduce<DependencyViewModel[]>((acc, skill) => {
if (skill.sid?.isEqual(sid)) {
skill.BTAction.map((action) => {
action.param.map((param) => {
if (param.type.isEqualR(skillType)) {
acc.push(param?.dependency ?? DependencyViewModel.empty());
}
return param;
});
return action;
});
}
return acc;
}, [])
.at(0) ?? DependencyViewModel.empty();
static isEmpty(model: Skills): Result<void, void> {
if (model.skills.isEmpty()) {
return Result.error(undefined);
}
return Result.ok(undefined);
}
static empty() {
const skills = new Skills();
skills.skills = [];
return skills;
}
public dependencyIsFilled = (skillType: string, sid: string) =>
this.skills.reduce((acc, skill) => {
if (skill.sid?.isEqual(sid)) {
skill.BTAction.forEach((action) => {
action.param.forEach((param) => {
acc = param.isFilled;
});
});
}
return acc;
}, false);
getAllSids = () =>
this.skills.reduce((acc, skill) => {
skill.BTAction.forEach((action) =>
action.param.forEach((param) => {
return param;
})
);
return acc;
}, new Map<string, number>());
deleteSid(sid: string): SkillModel[] {
return this.skills.filter((skill) => !skill.sid?.isEqual(sid));
}
updateSkill = (skill: SkillModel) => {
this.skills = this.skills.map((el) => {
if (el.sid?.isEqual(skill.sid ?? "")) {
el = skill;
}
return el;
});
};
skillHasForm = (label: string): boolean => {
return true;
};
getSkillWidthAtLabel = (label: string): number =>
this.getSkillParams(label).reduce((acc, element) => {
if (element.type.length > acc) {
acc = element.type.length;
}
return acc;
}, 0);
skillIsDependencyFilled = (label: string): Result<boolean, boolean> =>
this.getSkill(label).fold(
(skill) => {
return Result.ok(skill.BTAction.find((el) => el.name.isEqual(label))?.param.isEmpty());
},
() => Result.error(false)
);
skillIsCondition = (label: string) =>
this.getSkill(label).fold(
(s) => s.BTAction.atR(0).map((el) => el.typeAction.isEqualR(BtAction.CONDITION)),
(e) => Result.error(undefined)
);
}

View file

@ -0,0 +1,70 @@
import { Object3D, Object3DEventMap, Quaternion, Vector3 } from "three";
import { SceneModelsTypes } from "./scene_models_type";
import { Instance } from "./scene_asset";
import { CoreThreeRepository } from "../repository/core_three_repository";
import { Type } from "class-transformer";
import { Parts } from "../../features/details/details_http_repository";
import { SceneItems } from "../../features/scene_builder/presentation/scene_builder_store";
export class SolidModel implements Instance {
icon: string = "Solid";
type = SceneModelsTypes.SOLID;
@Type(() => Vector3)
public position: Vector3;
public name: string;
constructor(
vector3: Vector3,
public orientation: number[],
name:string,
public solidType: string,
public mesh: string,
public collisionMesh: string,
public spawnType: string
) {
this.name = name;
this.position = vector3;
}
toSceneItems = (): SceneItems => {
return {
fn: () => {},
name: this.name,
isSelected: false,
icon: "Solid",
};
};
update = (coreThreeRepository: CoreThreeRepository) => {
const object = coreThreeRepository.getObjectsAtName(this.name) as Object3D<Object3DEventMap>;
object.position.copy(this.position)
object.quaternion.copy(new Quaternion().fromArray(this.orientation))
};
toWebGl = (coreThreeRepository: CoreThreeRepository) => {
this.spawnType.isEqualR("BoundBox").fold(
() =>
coreThreeRepository.loader(
this.mesh,
() => (
coreThreeRepository.raiseAnObjectAboveZeroVector(this.name),
(this.orientation = coreThreeRepository.scene.getObjectByName(this.name)!.quaternion.toArray()),
(this.position = coreThreeRepository.scene.getObjectByName(this.name)!.position)
),
this.name,
this.position
),
() => console.log("UNKNOWN SPAWN TYPE SOLID MODEL")
);
};
fromParts = (parts: Parts): SolidModel => {
const solidModel = SolidModel.empty();
solidModel.mesh = parts.daeUrl;
solidModel.name = parts.name;
solidModel.solidType = parts.solidType;
return solidModel;
};
static empty = () => new SolidModel(new Vector3(0, 0, 0), [], "", "", "", "", "");
}

View file

@ -0,0 +1,3 @@
export enum SpawnPositionTypes {
BoundBox = "BoundBox",
}

View file

@ -0,0 +1,3 @@
export interface IStyle{
style?: React.CSSProperties;
}

View file

@ -0,0 +1,9 @@
export interface Topics {
topics: Topic[];
}
export interface Topic {
sid: string;
name: string;
type: string;
}

View file

@ -1,12 +0,0 @@
import { DatabaseModel } from "./database_model";
export interface ITriggerModel extends DatabaseModel {
type: string;
description: string;
value: string[];
}
export enum TriggerType {
PROCESS = "PROCESS",
FILE = "FILE",
}

View file

@ -0,0 +1,29 @@
import { Result } from "../helper/result";
import { validate, ValidationError } from "class-validator";
export class ValidationModel {
valid = async <T>(): Promise<Result<string, T>> => {
const errors: ValidationError[] = await validate(this, {
skipMissingProperties: false,
whitelist: false,
forbidNonWhitelisted: true,
});
if (errors.isNotEmpty()) {
const message = errors.map((error: ValidationError) => {
let result = "";
if (error.children)
error.children.map((el) => {
if (el.constraints) {
result += Object.values(el.constraints).join(", ");
}
});
if (error.constraints) result += Object.values(error.constraints).join(", ");
return result;
});
return Result.error(message.join(", \n"));
} else {
return Result.ok(this as unknown as T);
}
};
}

View file

@ -0,0 +1,33 @@
import { Vector3 } from "three";
import { Result } from "../helper/result";
import { SceneModelsTypes } from "./scene_models_type";
import { Instance } from "./scene_asset";
import { CoreThreeRepository } from "../repository/core_three_repository";
import { Type } from "class-transformer";
export class ZoneModel implements Instance {
type = SceneModelsTypes.ZONE;
color: string = "#E91E63";
@Type(() => Vector3)
position: Vector3;
constructor(
vector3: Vector3,
public orientation: number[],
public name: string,
public width: number,
public height: number,
public length: number
) {
this.position = vector3;
}
update = (coreThreeRepository: CoreThreeRepository) => this.toWebGl(coreThreeRepository)
icon: string = "Zone";
toWebGl = (coreThreeRepository: CoreThreeRepository) => coreThreeRepository.makeZone(this);
isValid(): Result<string, ZoneModel> {
return Result.ok();
}
static empty() {
return new ZoneModel(new Vector3(0, 0, 0), [0, 0, 0, 0], "", 0, 0, 0);
}
}

View file

@ -1,11 +1,15 @@
import { ClassConstructor, plainToInstance } from "class-transformer";
import { Result } from "../helper/result";
import { Parts } from "../../features/details/details_http_repository";
import { UUID } from "../../features/all_projects/data/project_http_repository";
import { SceneModel } from "../../features/scene_manager/model/scene_model";
import { SceneAsset } from "../model/scene_asset";
export enum HttpMethod {
GET = "GET",
POST = "POST",
DELETE = "DELETE",
PUT = "PUT"
PUT = "PUT",
}
export class HttpError extends Error {
status: number;
@ -16,13 +20,9 @@ export class HttpError extends Error {
this.status = status;
}
}
export class HttpRepository {
private server = "http://localhost:4001";
public async _formDataRequest<T>(method: HttpMethod, url: string, data?: any): Promise<Result<HttpError, T>> {
let formData = new FormData();
formData.append("file", data);
public async _formDataRequest<T>(method: HttpMethod, url: string, formData: FormData): Promise<Result<HttpError, T>> {
const reqInit = {
body: formData,
method: method,
@ -71,6 +71,36 @@ export class HttpRepository {
}
return Result.ok(response.text as T);
}
public async _arrayJsonToClassInstanceRequest<T>(
method: HttpMethod,
url: string,
instance: ClassConstructor<T>,
data?: any
): Promise<Result<HttpError, T[]>> {
try {
const reqInit = {
body: data,
method: method,
headers: { "Content-Type": "application/json" },
};
if (data !== undefined) {
reqInit["body"] = JSON.stringify(data);
}
const response = await fetch(this.server + url, reqInit);
if (response.status !== 200) {
return Result.error(new HttpError(this.server + url, response.status));
}
const array: any[] = await response.json();
return Result.ok(
array.map((el) => {
return plainToInstance(instance, el) as T;
})
);
} catch (error) {
return Result.error(new HttpError(error, 0));
}
}
public async _jsonToClassInstanceRequest<T>(
method: HttpMethod,
url: string,
@ -97,3 +127,19 @@ export class HttpRepository {
}
}
}
export class CoreHttpRepository extends HttpRepository {
getAssetsActiveProject = async (): Promise<Result<HttpError, Parts[]>> => {
return this._jsonRequest<Parts[]>(HttpMethod.GET, "/projects/assets");
};
getSceneAsset = (id: string) =>
this._jsonToClassInstanceRequest(HttpMethod.GET, `/scenes/by_id?id=${id}`, SceneAsset) as Promise<
Result<HttpError, SceneAsset>
>;
async getActiveProjectId() {
return this._jsonRequest<UUID>(HttpMethod.GET, "/projects/get/active/project/id");
}
getAllScenes = () => this._jsonRequest<SceneModel[]>(HttpMethod.GET, "/scenes");
}

View file

@ -0,0 +1,34 @@
import { FoxgloveClient } from "@foxglove/ws-protocol"
import { MessageReader } from '@foxglove/rosmsg2-serialization';
import { parse as parseMessageDefinition } from '@foxglove/rosmsg';
import { TypedEvent } from "../helper/typed_event";
export class CoreRosWsBridgeRepository<T> extends TypedEvent<T> {
client?: FoxgloveClient;
connect(topic: string) {
const client = new FoxgloveClient({
ws: new WebSocket(`ws://localhost:8765`, [FoxgloveClient.SUPPORTED_SUBPROTOCOL]),
});
const deserializers = new Map();
client.on("advertise", (channels) => {
for (const channel of channels) {
if (channel.topic.isEqual(topic)) {
const subId = client.subscribe(channel.id);
deserializers.set(subId, (data: any) => {
return {
data: data,
channel: channel
}
});
}
}
});
client.on("message", ({ subscriptionId, timestamp, data }) => {
const channel = deserializers.get(subscriptionId)(data);
this.emit(new MessageReader(parseMessageDefinition(channel.channel?.schema!, {
ros2: true,
})).readMessage(channel.data))
});
}
}

View file

@ -8,12 +8,10 @@ export class SocketRepository extends TypedEvent<any> {
async connect():Promise<Result<boolean, boolean>> {
const socket = io(this.serverURL);
this.socket = socket;
socket.connect();
socket.on('realtime', (d) =>{
console.log("D")
console.log(d)
this.emit({
event:"realtime",
payload:d

View file

@ -20,25 +20,34 @@ import {
CameraHelper,
Quaternion,
MeshBasicMaterial,
PlaneGeometry,
BoxGeometry
BoxGeometry,
MeshStandardMaterial,
LoadingManager,
} from "three";
import URDFLoader, { URDFLink } from "urdf-loader";
import { ColladaLoader } from "three/examples/jsm/loaders/ColladaLoader";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { TransformControls } from "three/examples/jsm/controls/TransformControls";
import { SceneMode } from "../../features/scene_manager/model/scene_view";
import { UrdfTransforms, coordsToQuaternion } from "../../features/simulations/tranforms_model";
import { SolidModel } from "../model/solid_model";
import { Instance, SceneAsset } from "../model/scene_asset";
import { ZoneModel } from "../model/zone_model";
import { TypedEvent } from "../helper/typed_event";
import { Result } from "../helper/result";
import { GLTFLoader, OrbitControls, TransformControls, OBJLoader, STLLoader, ColladaLoader } from "three-stdlib";
import {
BaseSceneItemModel,
CameraViewModel,
StaticAssetItemModel,
} from "../../features/scene_manager/model/scene_assets";
import { SceneMode } from "../../features/scene_manager/model/scene_view";
import { throttle } from "../helper/throttle";
import { Asset, InstanceRgbCamera, RobossemblerAssets, SceneSimpleObject } from "../model/robossembler_assets";
import { RobotModel } from "../model/robot_model";
import { SceneItems } from "../../features/scene_builder/presentation/scene_builder_store";
Object3D.DEFAULT_UP = new Vector3(0, 0, 1);
export enum UserData {
selectedObject = "selected_object",
cameraInitialization = "camera_initialization",
objectForMagnetism = "object_for_magnetism",
loadObject = "load_object",
}
interface IEventDraggingChange {
@ -51,8 +60,9 @@ interface IEmissiveCache {
status: boolean;
object3d: Object3D<Object3DEventMap>;
}
type SceneFrames = { [K in string]: URDFLink };
export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
export class CoreThreeRepository extends TypedEvent<any> {
scene = new Scene();
camera: PerspectiveCamera;
webGlRender: WebGLRenderer;
@ -62,10 +72,14 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
orbitControls: OrbitControls;
htmlSceneWidth: number;
htmlSceneHeight: number;
sceneFrame?: SceneFrames;
objLoader = new OBJLoader();
glbLoader = new GLTFLoader();
daeLoader = new ColladaLoader();
stlLoader = new STLLoader();
manager = new LoadingManager();
urdfLoader = new URDFLoader(this.manager);
watcherSceneEditorObject: Function;
constructor(htmlCanvasRef: HTMLCanvasElement, watcherSceneEditorObject: Function) {
@ -79,8 +93,8 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
alpha: true,
});
const aspectCamera = this.htmlSceneWidth / this.htmlSceneHeight;
this.camera = new PerspectiveCamera(800, aspectCamera, 0.1, 10000);
this.camera.position.set(60, 20, 10);
this.camera = new PerspectiveCamera(100, aspectCamera, 0.1, 2000);
this.camera.position.set(1, 1, 1);
this.webGlRender = renderer;
this.htmlCanvasRef = htmlCanvasRef;
@ -89,31 +103,63 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
this.scene.add(this.transformControls);
this.orbitControls = new OrbitControls(this.camera, this.htmlCanvasRef);
this.scene.background = new Color("black");
this.scene.background = new Color("white");
this.init();
}
updateInstance = (viewModel: Instance) => (
this.scene.remove(this.scene.getObjectByName(viewModel.name)!), viewModel.toWebGl(this)
);
deleteAllObjectsScene = () => {
this.getAllSceneNameModels().forEach((el) =>
this.scene.remove(this.scene.getObjectByName(el) as Object3D<Object3DEventMap>)
);
};
raiseAnObjectAboveZeroVector = (name: string) => {
const mesh = this.scene.getObjectByName(name) as Object3D;
mesh.position.sub(new Box3().setFromObject(mesh).min);
};
drawPoint(point: Vector3): Mesh<BoxGeometry, MeshBasicMaterial, Object3DEventMap> {
var cube = new Mesh(new BoxGeometry(0.5, 0.5, 0.5), new MeshBasicMaterial({ color: 0x0095dd }));
cube.position.add(point);
this.scene.add(cube);
return cube;
}
loadScene = (sceneAssets: SceneAsset) => sceneAssets.scene.forEach((el) => el.toWebGl(this));
getCenterPoint = (object: Object3D<Object3DEventMap>) =>
object.getWorldPosition(new Box3().setFromObject(object).getCenter(object.position));
makeZone = (zoneModel: ZoneModel) => {
const mesh = new Mesh(
new BoxGeometry(zoneModel.width, zoneModel.height, zoneModel.length),
new MeshBasicMaterial({
color: zoneModel.color,
transparent: true,
})
);
mesh.name = zoneModel.name;
mesh.userData[UserData.selectedObject] = "";
mesh.position.copy(zoneModel.position);
mesh.quaternion.copy(new Quaternion().fromArray(zoneModel.orientation));
this.scene.add(mesh);
};
makeCube(inc: number, vector?: Vector3, color?: string, size?: number) {
const cube = new Mesh(
const mesh = new Mesh(
new BoxGeometry(size ?? 10, size ?? 10, size ?? 10),
new MeshBasicMaterial({ color: color ?? 0x0095dd, transparent: true, opacity: 0.5 })
);
cube.userData[UserData.objectForMagnetism] = true;
cube.userData[UserData.selectedObject] = true;
cube.name = "cube" + String(inc);
mesh.userData[UserData.objectForMagnetism] = true;
mesh.userData[UserData.selectedObject] = true;
mesh.name = "cube" + String(inc);
if (vector) {
cube.position.copy(vector);
mesh.position.copy(vector);
}
this.scene.add(cube);
this.scene.add(mesh);
}
makePoint(vector?: Vector3, color?: string, size?: number) {
makePoint(name: string, vector?: Vector3, quaternion?: Quaternion, color?: string, size?: number) {
const cube = new Mesh(
new BoxGeometry(size ?? 10, size ?? 10, size ?? 10),
new MeshBasicMaterial({ color: color ?? 0x0095dd, transparent: true, opacity: 0.5 })
@ -124,39 +170,74 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
if (vector) {
cube.position.copy(vector);
}
if (quaternion) {
cube.quaternion.copy(quaternion);
}
cube.name = name;
this.scene.add(cube);
}
deleteSceneItem(item: BaseSceneItemModel) {
const updateScene = this.scene;
updateScene.children = item.deleteToScene(updateScene);
}
deleteSceneItemByName = (name: string) =>
this.scene.children.forEach((el) => el.name.isEqualR(name).map(() => this.scene.remove(el)));
loadInstances(robossemblerAssets: RobossemblerAssets) {
robossemblerAssets.instances.forEach(async (el) => {
if (el instanceof InstanceRgbCamera) {
const cameraModel = CameraViewModel.fromInstanceRgbCamera(el);
cameraModel.mapPerspectiveCamera(this.htmlSceneWidth, this.htmlSceneHeight).forEach((el) => this.scene.add(el));
this.emit(cameraModel);
deleteSceneItem = (item: SceneItems) =>
item.icon.isEqualR("Camera").fold(
() => this.deleteSceneItemByName(item.name + "camera_helper"),
() => this.deleteSceneItemByName(item.name)
);
loadUrdfRobot = (robotModel: RobotModel) =>
this.urdfLoader.load(robotModel.httpUrl, (robot) => {
robot.userData[UserData.selectedObject] = true;
robot.name = robotModel.name;
if (robotModel.position) robot.position.copy(robotModel.position);
if (robotModel.orientation) robot.quaternion.copy(new Quaternion().fromArray(robotModel.orientation));
if (robotModel.jointPosition.isNotEmpty()) {
robotModel.jointPosition.forEach((el) => {
robot.setJointValue(el.name, el.angle);
});
}
if (el instanceof SceneSimpleObject) {
const asset = robossemblerAssets.getAssetAtInstance(el.instanceAt as string);
this.loader(
asset.meshPath,
() => {},
asset.name,
new Vector3(el.position.x, el.position.y, el.position.z),
new Quaternion(el.quaternion[0], el.quaternion[1], el.quaternion[2], el.quaternion[3])
);
if (robotModel.jointPosition.isEmpty()) {
Object.entries(robot.joints).forEach(([name, uRDFJoint]) => {
if (uRDFJoint.jointType !== "fixed") {
robotModel.jointPosition.push({
angle: Number(uRDFJoint.angle),
limit: {
lower: Number(uRDFJoint.limit.lower),
upper: Number(uRDFJoint.limit.upper),
},
name: name,
});
}
});
}
this.scene.add(robot);
// @ts-expect-error
this.sceneFrame = robot.frames;
});
}
loadInstance(asset: Asset, loadCallback?: Function) {
loadUrdf = (urlPath: string, vector3?: Vector3, quaternion?: Quaternion) =>
this.urdfLoader.load(urlPath, (robot) => {
robot.userData[UserData.selectedObject] = true;
if (vector3) robot.position.copy(new Vector3(0, 0, 0));
if (quaternion) robot.quaternion.copy(quaternion);
Object.entries(robot.joints).forEach(([k, v]) => robot.setJointValue(k, 1));
this.scene.add(robot);
// @ts-expect-error
this.sceneFrame = robot.frames;
});
loadHttpAndPreview(path: string, name: string, loadCallback?: Function) {
this.loader(
asset.meshPath,
loadCallback ? loadCallback : () => {},
asset.name,
new Vector3(Number(asset.posX), Number(asset.posY), Number(asset.posZ)),
new Quaternion(0, 0, 0, 0)
path,
() => {
// this.fitCameraToCenteredObject([name])
// this.scene.getObjectByName(name)
},
name,
new Vector3(0, 0, 0)
);
}
@ -166,22 +247,22 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
this.transformControls.detach();
this.transformControls.dispose();
break;
case SceneMode.MOVING:
case SceneMode.Move:
this.transformControls.setMode("translate");
break;
case SceneMode.ROTATE:
case SceneMode.Rotate:
this.transformControls.setMode("rotate");
break;
}
}
addSceneCamera(cameraModel: CameraViewModel) {
cameraModel.mapPerspectiveCamera(this.htmlSceneWidth, this.htmlSceneHeight).forEach((el) => this.scene.add(el));
}
updateSolidBody = (solidBodyModel: SolidModel) => {
const mesh = this.scene.getObjectByName(solidBodyModel.name);
mesh?.position.copy(solidBodyModel.position);
mesh?.quaternion.copy(new Quaternion().fromArray(solidBodyModel.orientation));
};
disposeTransformControlsMode() {
this.transformControls.detach();
}
disposeTransformControlsMode = () => this.transformControls.detach();
setRayCastAndGetFirstObjectName(vector: Vector2): Result<void, string> {
this.scene.add(this.transformControls);
@ -223,7 +304,7 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
}
}
setRayCast(vector: Vector2): Result<void, Intersection<Object3D<Object3DEventMap>>[]> {
setRayCast = (vector: Vector2): Result<void, Intersection<Object3D<Object3DEventMap>>[]> => {
const raycaster = new Raycaster();
raycaster.setFromCamera(vector, this.camera);
const intersects = raycaster.intersectObjects(this.scene.children);
@ -232,16 +313,17 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
}
return Result.error(undefined);
}
};
setRayCastAndGetFirstObjectAndPointToObject(vector: Vector2): Result<void, Vector3> {
this.setRayCast(vector).map((intersects) => {
if (intersects.length > 0) {
return Result.ok(intersects[0].point);
}
});
return Result.error(undefined);
}
setRayCastAndGetFirstObjectAndPointToObject = (vector: Vector2): Result<void, Vector3> =>
this.setRayCast(vector).fold(
(intersects) => {
if (intersects.isNotEmpty()) {
return Result.ok(intersects[0].point);
}
},
() => Result.error(undefined)
) as Result<void, Vector3>;
light() {
const ambientLight = new AmbientLight(0xffffff, 0.7);
@ -269,34 +351,19 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
this.orbitControls.enabled = !e.value;
});
this.transformControls.addEventListener("objectChange", (event) => {
//@ts-expect-error
const sceneObject = event.target.object;
//TODO:(IDONTSUDO) Trotting doesn't work, need to figure out why
const fn = () => this.watcherSceneEditorObject(sceneObject);
const [throttleFn] = throttle(fn, 1000);
throttleFn();
this.watcherSceneEditorObject(event.target.object);
});
}
init() {
this.light();
this.addListeners();
const floor = new GridHelper(100, 100, 0x888888, 0x444444);
floor.userData = {};
floor.userData[UserData.cameraInitialization] = true;
this.scene.add(floor);
const gridHelper = new GridHelper(1000, 100, 0x888888, 0x444444);
gridHelper.userData = {};
gridHelper.userData[UserData.cameraInitialization] = true;
gridHelper.rotation.x = -Math.PI / 2;
const planeMesh = new Mesh(
new PlaneGeometry(10, 10, 10, 10),
new MeshBasicMaterial({ color: 0x808080, wireframe: true })
);
planeMesh.userData[UserData.selectedObject] = true;
planeMesh.userData[UserData.objectForMagnetism] = true;
planeMesh.rotation.x = -Math.PI / 2;
this.makeCube(1);
this.makeCube(2, new Vector3(20, 0, 10), "yellow");
this.scene.add(planeMesh);
this.scene.add(gridHelper);
}
render() {
@ -306,13 +373,6 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
});
}
getAllSceneModels(): BaseSceneItemModel[] {
return this.getAllSceneNameModels().map(
(name) =>
new StaticAssetItemModel(name, this.getObjectsAtName(name).position, this.getObjectsAtName(name).quaternion)
);
}
getAllSceneNameModels(): string[] {
return this.scene.children.filter((el) => el.name !== "").map((el) => el.name);
}
@ -323,13 +383,15 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
loader(url: string, callBack: Function, name: string, position?: Vector3, quaternion?: Quaternion) {
const ext = url.split(/\./g).pop()!.toLowerCase();
switch (ext) {
case "gltf":
case "glb":
this.glbLoader.load(
url,
(result) => {},
(result) => {
this.scene.add(result.scene);
callBack();
},
(err) => {}
);
break;
@ -345,8 +407,9 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
if (position) el.position.copy(position);
if (quaternion) el.quaternion.copy(quaternion);
this.emit(new StaticAssetItemModel(el.name, el.position, el.quaternion));
// this.emit(new StaticAssetItemModel(el.name, el.position, el.quaternion));
this.scene.add(el);
callBack();
});
},
(err) => {}
@ -355,14 +418,44 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
case "dae":
this.daeLoader.load(
url,
(result) => {},
(err) => {}
(result) => {
result.scene.children.forEach((el) => {
el.userData[UserData.selectedObject] = true;
el.name = name;
});
result.scene.name = name;
if (position) result.scene.position.copy(position);
if (quaternion) result.scene.quaternion.copy(quaternion);
this.scene.add(result.scene);
callBack();
},
(err) => console.log(err)
);
break;
case "stl":
this.stlLoader.load(
url,
(result) => {},
(result) => {
const mesh = new Mesh(
result,
new MeshStandardMaterial({
color: "red",
metalness: 0.35,
roughness: 1,
opacity: 1.0,
transparent: false,
})
);
mesh.name = name;
// var geometry = mesh.geometry;
// geometry.computeBoundingBox(); // Вычисляем ограничивающий параллелепипед для геометрии
if (position) mesh.position.copy(position);
this.scene.add(mesh);
callBack();
},
(err) => {}
);
@ -375,13 +468,13 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position)
);
var size = new Vector3();
boundingBox.getSize(size);
var vector3 = new Vector3();
boundingBox.getSize(vector3);
const fov = this.camera.fov * (Math.PI / 180);
const fovh = 2 * Math.atan(Math.tan(fov / 2) * this.camera.aspect);
let dx = size.z / 2 + Math.abs(size.x / 2 / Math.tan(fovh / 2));
let dy = size.z / 2 + Math.abs(size.y / 2 / Math.tan(fov / 2));
let dx = vector3.z / 2 + Math.abs(vector3.x / 2 / Math.tan(fovh / 2));
let dy = vector3.z / 2 + Math.abs(vector3.y / 2 / Math.tan(fov / 2));
let cameraZ = Math.max(dx, dy);
if (offset !== undefined && offset !== 0) cameraZ *= offset;
@ -428,8 +521,9 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
fitSelectedObjectToScreen(objects: string[]) {
//https://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
let boundBox = new Box3().setFromPoints(objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position));
let boundSphere = boundBox.getBoundingSphere(new Sphere());
let boundSphere = new Box3()
.setFromPoints(objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position))
.getBoundingSphere(new Sphere());
let vFoV = this.camera.getEffectiveFOV();
let hFoV = this.camera.fov * this.camera.aspect;
let FoV = Math.min(vFoV, hFoV);
@ -451,4 +545,13 @@ export class CoreThreeRepository extends TypedEvent<BaseSceneItemModel> {
this.camera.lookAt(bsWorld);
this.orbitControls = new OrbitControls(this.camera, this.htmlCanvasRef);
}
urdfTransforms = (urdfTransforms: UrdfTransforms) => {
urdfTransforms.transforms.forEach((transform) => {
if (this.sceneFrame) {
const currentLink = this.sceneFrame[transform?.child_frame_id ?? ""];
currentLink.quaternion.copy(coordsToQuaternion(transform.transform.rotation));
}
});
};
}

View file

@ -1,55 +1,55 @@
import { createBrowserRouter } from "react-router-dom";
import { AllProjectScreen, AllProjectScreenPath } from "../../features/all_projects/presentation/all_projects_screen";
import {
PipelineInstanceScreen,
PipelineInstanceScreenPath,
} from "../../features/pipeline_instance_main_screen/pipeline_instance_screen";
import { CreateProjectScreen, CreateProjectScreenPath } from "../../features/create_project/create_project_screen";
import { SceneManger, SceneManagerPath } from "../../features/scene_manager/presentation/scene_manager";
import {
BehaviorTreeBuilderPath,
BehaviorTreeBuilderScreen,
} from "../../features/behavior_tree_builder/presentation/behavior_tree_builder_screen";
import {
StickObjectsMarkingScreen,
StickObjectsMarkingScreenPath,
} from "../../features/_stick_objects_marking/stick_objects_marking_screen";
import { DataSetScreen, DatasetsScreenPath } from "../../features/dataset/dataset_screen";
import DetailsScreen, { DetailsScreenPath } from "../../features/details/details_screen";
import { AssemblesScreen, AssemblesScreenPath } from "../../features/assembles/assembles_screen";
import SimulationScreen, { SimulationScreenPath } from "../../features/simulations/simulations_screen";
import { SimulationScreen, SimulationScreenPath } from "../../features/simulations/simulations_screen";
import { EstimateScreen, EstimateScreenPath } from "../../features/estimate/estimate_screen";
import {
CalculationInstanceScreenPath,
CalculationInstanceScreen,
} from "../../features/calculation_instance/presentation/calculation_instance_screen";
import { DetailsScreenPath, DetailsScreen } from "../../features/details/details_screen";
import { DigitalTwinsScreen, DigitalTwinsScreenPath } from "../../features/digital_twins/digital_twins_screen";
import { TopicsScreen, TopicsScreenPath } from "../../features/topics/topics_screen";
import { SkillsScreen, SkillsScreenPath } from "../../features/skills/skills_screen";
import { CalculationsTemplateScreenPath } from "../../features/calculations_template/calculations_template_screen";
import { BehaviorTreeManagerScreen, BehaviorTreeManagerScreenPath } from "../../features/behavior_tree_manager/behavior_tree_manager_screen";
import { SceneBuilderScreenPath, SceneBuilderScreen } from "../../features/scene_builder/presentation/scene_builder_screen";
const idURL = ":id";
export const router = createBrowserRouter([
{
path: AllProjectScreenPath,
element: <AllProjectScreen />,
},
{
path: PipelineInstanceScreenPath + idURL,
element: <PipelineInstanceScreen />,
path:SceneBuilderScreenPath + idURL,
element:<SceneBuilderScreen/>
},
{
path: CreateProjectScreenPath,
element: <CreateProjectScreen />,
path: SceneManagerPath + idURL,
element: <SceneManger />,
},
{
path: SceneManagerPath,
element: <SceneManger />,
},
{
path: BehaviorTreeBuilderPath,
path: BehaviorTreeBuilderPath(idURL),
element: <BehaviorTreeBuilderScreen />,
},
{
path: StickObjectsMarkingScreenPath,
element: <StickObjectsMarkingScreen />,
path: BehaviorTreeBuilderPath(),
element: <BehaviorTreeBuilderScreen />,
},
{
path: BehaviorTreeBuilderPath + idURL,
element: <BehaviorTreeBuilderScreen />,
},
{
path: DatasetsScreenPath,
@ -67,9 +67,32 @@ export const router = createBrowserRouter([
path: SimulationScreenPath,
element: <SimulationScreen />,
},
{
path: EstimateScreenPath,
element: <EstimateScreen />,
},
{
path: CalculationInstanceScreenPath,
element: <CalculationInstanceScreen />,
},
{
path: DigitalTwinsScreenPath,
element: <DigitalTwinsScreen />,
},
{
path: TopicsScreenPath,
element: <TopicsScreen />,
},
{
path: SkillsScreenPath,
element: <SkillsScreen />,
},
{
path: CalculationsTemplateScreenPath,
element: <CalculationInstanceScreen />,
},
{
path: BehaviorTreeManagerScreenPath,
element: <BehaviorTreeManagerScreen />,
},
]);

View file

@ -1,10 +1,21 @@
import { NavigateFunction } from "react-router-dom";
import { Result } from "../helper/result";
import { UiBaseError } from "../model/ui_base_error";
import { HttpError } from "../repository/http_repository";
import { HttpError } from "../repository/core_http_repository";
import { message } from "antd";
import { ClassConstructor, plainToInstance } from "class-transformer";
export type CoreError = HttpError | Error;
export interface Drawer {
name: string;
status: boolean;
}
interface IMessage {
successMessage?: string;
errorMessage?: string;
}
export abstract class UiLoader {
isLoading = false;
async httpHelper<T>(callBack: Promise<Result<any, T>>) {
@ -23,13 +34,19 @@ export abstract class UiLoader {
abstract errorHandingStrategy: (error?: any) => void;
mapOk = async <T>(property: string, callBack: Promise<Result<CoreError, T>>) => {
return (
(await this.httpHelper(callBack))
// eslint-disable-next-line array-callback-return
.map((el) => {
// @ts-ignore
this[property] = el;
})
return (await this.httpHelper(callBack)).map((el) => {
// @ts-ignore
this[property] = el;
});
};
messageHttp = async <T>(callBack: Promise<Result<CoreError, T>>, report?: IMessage) => {
return (await this.httpHelper(callBack)).fold(
(_s) => {
if (report && report.successMessage) message.success(report.successMessage);
},
(_e) => {
if (report && report.errorMessage) message.error(report.errorMessage);
}
);
};
}
@ -39,10 +56,77 @@ export class SimpleErrorState extends UiLoader {
};
isError = false;
}
export class ModalStore extends SimpleErrorState {
isModalOpen: boolean = false;
modalShow = () => {
this.isModalOpen = true;
};
modalClickOk = () => {
this.isModalOpen = false;
};
modalCancel = () => {
this.isModalOpen = false;
};
}
export abstract class UiErrorState<T> extends UiLoader {
abstract errorHandingStrategy: (error: T) => void;
errorHandingStrategy = (error: T) => {
console.log(error);
};
abstract init(navigate?: NavigateFunction): Promise<any>;
dispose() {}
errors: UiBaseError[] = [];
}
export abstract class DrawerState<E> extends UiErrorState<E> {
titleDrawer: string = "";
drawers: Drawer[];
constructor(drawerEnum: Object) {
super();
this.drawers = Object.entries(drawerEnum).map((k, v) => {
return {
name: k.at(1) ?? "",
status: false,
};
});
}
editDrawer(drawerName: string, status: boolean): void {
this.titleDrawer = drawerName;
this.drawers = this.drawers.map((el) => {
if (el.name === drawerName) {
el.status = status;
}
return el;
});
}
}
export abstract class UiDrawerFormState<V, E> extends DrawerState<E> {
abstract viewModel: V;
updateForm(value: Partial<V>) {
//@ts-ignore
this.viewModel = Object.assign(this.viewModel, value);
}
loadDependency = (viewModel: V) => {
this.viewModel = viewModel;
};
loadClassInstance = (instance: ClassConstructor<V>, viewModel: V) => {
this.viewModel = plainToInstance(instance, viewModel);
};
}
export abstract class FormState<V, E> extends UiErrorState<E> {
abstract viewModel: V;
updateForm(value: Partial<V>) {
//@ts-ignore
this.viewModel = Object.assign(this.viewModel, value);
}
loadDependency = (viewModel: V | undefined) => {
if (viewModel) this.viewModel = viewModel;
};
loadClassInstance = (instance: ClassConstructor<V>, viewModel: V) => {
this.viewModel = plainToInstance(instance, viewModel);
};
}

View file

@ -0,0 +1,61 @@
import { makeAutoObservable } from "mobx";
export abstract class Theme {
abstract greenWhite: string;
abstract white: string;
abstract black: string;
abstract green: string;
abstract grau: string;
abstract grauLeft: string;
abstract redAccent: string;
abstract grau2: string;
abstract outlineVariantDark: string;
abstract outlineVariantLight: string;
abstract surfaceContainerLow: string;
abstract surfaceContainer: string;
abstract darkSurfaceVariant: string;
abstract outlineVariant: string;
abstract navBlue: string;
abstract grayLeft: string;
abstract onSurface: string;
abstract surfaceContainerHighest: string;
abstract darkSurface: string;
abstract fon: string;
abstract onSurfaceVariant: string;
abstract darkOnSurfaceVariant: string;
abstract error50: string;
}
export class BlackTheme implements Theme {
error50: string = "rgba(220, 54, 46, 1)";
darkOnSurfaceVariant: string = "rgba(202, 196, 208, 1)";
darkSurfaceVariant: string = "rgba(73, 69, 79, 1)";
navBlue: string = "rgba(202, 211, 216, 1)";
fon: string = "rgba(147, 147, 147, 0.4)";
darkSurface: string = "rgba(20, 18, 24, 1)";
surfaceContainerHighest: string = "rgba(54, 52, 59, 1)";
onSurface: string = "rgba(230, 224, 233, 1)";
grayLeft: string = "rgba(213, 220, 224, 1)";
surfaceContainer: string = "rgba(33, 31, 38, 1)";
surfaceContainerLow: string = "rgba(29, 27, 32, 1)";
outlineVariantLight: string = "rgba(202, 196, 208, 1)";
grau2: string = "rgba(147, 147, 147, 1)";
greenWhite: string = "rgba(233, 237, 201, 1)";
white: string = "rgba(255, 255, 255, 1)";
black: string = "rgba(0, 0, 0, 1)";
green: string = "rgba(56, 87, 72, 1)";
grau: string = "rgba(147, 147, 147, 1)";
grauLeft: string = "";
redAccent: string = "rgba(72, 23, 39, 1)";
outlineVariantDark: string = "rgba(73, 69, 79, 1)";
outlineVariant: string = "rgba(202, 196, 208, 1)";
onSurfaceVariant: string = "rgba(202, 196, 208, 1)";
}
export class ThemeStore {
theme: Theme = new BlackTheme();
constructor() {
makeAutoObservable(this);
}
changeTheme = (theme: Theme) => (this.theme = theme);
}

View file

@ -1,31 +1,36 @@
import * as React from "react";
import { CoreText, CoreTextType } from "../text/text";
import { IStyle } from "../../model/style";
export interface IButtonProps {
export interface IButtonProps extends IStyle {
block?: boolean;
filled?: boolean;
text?: string;
onClick?: any;
style?:React.CSSProperties
textStyle?:React.CSSProperties
}
export function CoreButton(props: IButtonProps) {
return (
<div
onClick={() => props.onClick?.call()}
style={Object.assign({
backgroundColor: props.filled ? "rgba(103, 80, 164, 1)" : "",
paddingRight: 20,
paddingLeft: 20,
paddingTop: 10,
paddingBottom: 10,
borderRadius: 24,
border: props.block ? "1px solid rgba(29, 27, 32, 0.12)" : props.filled ? "" : "1px solid black",
},props.style)}
style={Object.assign(
{
backgroundColor: props.filled ? "rgba(103, 80, 164, 1)" : "",
paddingRight: 20,
paddingLeft: 20,
paddingTop: 10,
paddingBottom: 10,
borderRadius: 24,
border: props.block ? "1px solid rgba(29, 27, 32, 0.12)" : props.filled ? "" : "1px solid black",
},
props.style
)}
>
<CoreText
text={props.text ?? ""}
type={CoreTextType.medium}
style={props.textStyle}
color={props.block ? "#1D1B20" : props.filled ? "white" : "rgba(103, 80, 164, 1)"}
/>
</div>

View file

@ -0,0 +1,64 @@
import { match } from "ts-pattern";
import { themeStore } from "../../..";
import { CoreText, CoreTextType } from "../text/text";
export enum ButtonV2Type {
default = "default",
empty = "empty",
}
export const ButtonV2 = ({
text,
textColor,
onClick,
style,
icon,
type,
}: {
text?: string;
textColor?: string;
onClick: () => void;
style?: React.CSSProperties;
icon?: React.ReactNode;
type?: ButtonV2Type;
}) => {
return (
<div
style={Object.assign(
{
cursor: "pointer",
backgroundColor: match(type)
.with(ButtonV2Type.empty, () => undefined)
.with(ButtonV2Type.default, () => themeStore.theme.greenWhite)
.otherwise(() => themeStore.theme.greenWhite),
color: textColor ?? themeStore.theme.black,
width: "max-content",
height: "max-content",
padding: match(type)
.with(ButtonV2Type.default, () => 15)
.otherwise(() => 5),
border: match(type)
.with(ButtonV2Type.empty, () => `1px solid ${themeStore.theme.greenWhite}`)
.with(ButtonV2Type.default, () => undefined)
.otherwise(() => undefined),
borderRadius: match(type)
.with(ButtonV2Type.default, () => 5)
.otherwise(() => 100),
display: "flex",
paddingRight: 10,
paddingLeft: 10,
},
style
)}
onClick={() => onClick()}
>
{icon}
<CoreText
color={textColor ?? themeStore.theme.black}
text={text}
type={match(type)
.with(ButtonV2Type.default, () => CoreTextType.smallv3)
.otherwise(() => CoreTextType.largeV2)}
/>
</div>
);
};

View file

@ -0,0 +1,88 @@
import { useRef, useEffect, useState } from "react";
import { themeStore } from "../../..";
import { ButtonV2 } from "../button/button_v2";
import { Icon } from "../icons/icons";
import { CoreText, CoreTextType } from "../text/text";
export const CoreCard: React.FC<{
clickDeleteIcon: Function;
clickGoToIcon: Function;
date: number;
descriptionBottom?: string;
descriptionTop: string;
descriptionMiddle?: string;
}> = ({ clickDeleteIcon, clickGoToIcon, descriptionBottom, descriptionTop, date, descriptionMiddle }) => {
const ref = useRef<HTMLDivElement>(null);
const [bgColor, setBgColor] = useState(themeStore.theme.surfaceContainerLow);
useEffect(() => {
ref.current?.addEventListener("mousemove", () => {
setBgColor(themeStore.theme.surfaceContainerHighest);
});
ref.current?.addEventListener("mouseleave", () => {
setBgColor(themeStore.theme.surfaceContainerLow);
});
}, []);
return (
<div
ref={ref}
style={{
backgroundColor: bgColor,
borderRadius: 12,
border: `1.5px solid ${themeStore.theme.outlineVariantDark}`,
padding: 10,
marginTop: 10,
marginRight: 10,
}}
>
<div
style={{ display: "flex", paddingTop: 10, paddingLeft: 20, paddingRight: 20, justifyContent: "space-between" }}
>
<div>
<CoreText type={CoreTextType.mediumV2} text={descriptionTop} color={themeStore.theme.white} />
<div style={{ height: 5 }} />
<CoreText
type={CoreTextType.mediumV2}
text={new Date().fromUnixDate(date).formatDate()}
color={themeStore.theme.outlineVariantLight}
/>
</div>
<div style={{ display: "flex" }}>
<ButtonV2 onClick={() => clickGoToIcon()} text="Перейти" />
<div style={{ width: 10 }} />
<Icon
width={10}
height={10}
type={"Bucket"}
onClick={() => clickDeleteIcon()}
style={{
border: `1px solid ${themeStore.theme.greenWhite}`,
height: 30,
width: 30,
textAlign: "center",
alignContent: "center",
borderRadius: 5,
cursor: "pointer",
}}
/>
</div>
</div>
<div
style={{
width: "calc(100% - 30px)",
height: 1,
backgroundColor: themeStore.theme.outlineVariantDark,
marginLeft: 15,
marginRight: 15,
}}
/>
<div style={{ paddingRight: 30, paddingLeft: 30 }}>
<div style={{ height: 20 }} />
<CoreText type={CoreTextType.largeV2} text={descriptionMiddle} color={themeStore.theme.white} />
<CoreText type={CoreTextType.mediumV2} text={descriptionBottom} color={themeStore.theme.white} />
<div style={{ height: 20 }} />
</div>
</div>
);
};

View file

@ -0,0 +1,56 @@
import React from "react";
import { themeStore } from "../../..";
import { CoreText, CoreTextType } from "../text/text";
export const DrawerV2: React.FC<{
isOpen?: boolean;
title?: string;
onClose: () => void;
children: React.ReactNode;
}> = ({ isOpen, onClose, children, title }) => {
return (
<div
style={{
position: "fixed",
top: 0,
right: isOpen ? 0 : -300,
width: 300,
height: "100%",
backgroundColor: themeStore.theme.darkSurface,
boxShadow: "-2px 0 5px rgba(0, 0, 0, 0.5)",
transition: "right 0.3s ease",
zIndex: 1000,
}}
>
<div style={{ height: "100%", width: "100%" }}>
<div style={{ padding: 20 }}>
<div
style={{
display: "flex",
width: "100%",
justifyContent: "space-between",
alignItems: "center",
}}
>
<CoreText type={CoreTextType.header} text={title} color={themeStore.theme.darkOnSurfaceVariant} />
<button
style={{
background: "none",
border: "none",
fontSize: 24,
cursor: "pointer",
color: themeStore.theme.darkOnSurfaceVariant,
}}
onClick={onClose}
>
&times;
</button>
</div>
<div style={{ width: "100%", height: 1, backgroundColor: themeStore.theme.outlineVariant }}></div>
</div>
{children}
</div>
</div>
);
};

View file

@ -1,12 +1,17 @@
import * as React from "react";
import { FormViewModel, InputBuilderViewModel, InputType } from "./form_view_model";
import {
FormViewModel,
InputBuilderViewModel,
InputType,
} from "./form_view_model";
import { observer } from "mobx-react-lite";
import { FormBuilderStore } from "./form_builder_store";
import { FormBuilderValidationModel } from "../../../features/dataset/dataset_model";
import { SelectCore } from "../select/select";
import { CoreSelect } from "../select/select";
import { CoreInput } from "../input/input";
import { Icon } from "../icons/icons";
import { CoreText, CoreTextType } from "../text/text";
import { getFormBuilderComponents } from "./forms/form_builder_components";
import { FormBuilderValidationModel } from "../../model/form_builder_validation_model";
export interface IFormBuilder {
formBuilder: FormBuilderValidationModel;
@ -37,22 +42,28 @@ export const FormBuilder = observer((props: IFormBuilder) => {
<>Error</>
) : (
<div>
{store.formViewModel?.inputs.map((element) => {
if (element.type.isEqual(InputType.ENUM)) {
{store.formViewModel?.inputs?.map((element, index) => {
if (element.type?.isEqual(InputType.ENUM)) {
const values = element.values as string[];
return (
<SelectCore
<CoreSelect
key={index}
items={values}
value={element.totalValue ?? element.defaultValue}
onChange={(value) => store.changeTotalValue(element.id, value)}
onChange={(value) =>
store.changeTotalValue(element.id, value)
}
label={element.name}
style={{ margin: 20 }}
/>
);
}
if (element.type.isEqual(InputType.ARRAY)) {
if (element.type?.isEqual(InputType.ARRAY)) {
return (
<div style={{ border: "1px black solid", margin: 20 }}>
<div
key={index}
style={{ border: "1px black solid", margin: 20 }}
>
<div
style={{
display: "flex",
@ -76,50 +87,91 @@ export const FormBuilder = observer((props: IFormBuilder) => {
return (
<div style={{ margin: 20 }}>
<div style={{ display: "flex" }}>
<CoreText text={(element.subType ?? "") + ` ${index}`} type={CoreTextType.medium} />
<CoreText
text={(element.subType ?? "") + ` ${index}`}
type={CoreTextType.medium}
/>
<Icon
style={{ paddingLeft: 20 }}
type="DeleteCircle"
onClick={() => store.deleteTotalValueSubItem(element.id, index)}
onClick={() =>
store.deleteTotalValueSubItem(
element.id,
index
)
}
/>
</div>
{subArray.map((subSubArrayItem: InputBuilderViewModel, subIndex: number) => {
if (subSubArrayItem.type.isEqual(InputType.ENUM)) {
return (
<>
<SelectCore
items={subSubArrayItem.values?.map((el) => String(el)) ?? []}
value={subSubArrayItem.totalValue ?? subSubArrayItem.defaultValue}
onChange={(value) => store.changeTotalSubValue(element.id, subIndex, value)}
label={element.name}
style={{ margin: 5 }}
/>
</>
);
}
if (subSubArrayItem.type.isEqualMany([InputType.NUMBER, InputType.STRING]))
return (
<div>
<CoreInput
style={{ margin: 5 }}
onChange={(e) => {
store.changeTotalSubValue(element.id, subIndex, e);
}}
validation={
subSubArrayItem.type.isEqual(InputType.NUMBER)
? (el) => Number().isValid(el)
: undefined
}
error="только числа"
value={subSubArrayItem.totalValue ?? subSubArrayItem.defaultValue}
label={subSubArrayItem.name}
/>
</div>
);
{subArray.map(
(
subSubArrayItem: InputBuilderViewModel,
subIndex: number
) => {
if (
subSubArrayItem.type.isEqual(
InputType.ENUM
)
) {
return (
<>
<CoreSelect
items={
subSubArrayItem.values?.map(
(el) => String(el)
) ?? []
}
value={
subSubArrayItem.totalValue ??
subSubArrayItem.defaultValue
}
onChange={(value) => console.log(subSubArrayItem.id)
}
label={element.name}
style={{ margin: 5 }}
/>
</>
);
}
if (
subSubArrayItem.type.isEqualMany([
InputType.NUMBER,
InputType.STRING,
])
)
return (
<div>
<CoreInput
isFormBuilder={true}
style={{ margin: 5 }}
onChange={(e) =>
store.changeTotalSubValue(
element.id,
subIndex,
e,
index
)
}
validation={
subSubArrayItem.type.isEqual(
InputType.NUMBER
)
? (el) => Number().isValid(el)
: undefined
}
error="только числа"
value={
subSubArrayItem.totalValue ??
subSubArrayItem.defaultValue
}
label={subSubArrayItem.name}
/>
</div>
);
return <>Error</>;
})}
return <>Error</>;
}
)}
</div>
);
})
@ -130,11 +182,16 @@ export const FormBuilder = observer((props: IFormBuilder) => {
);
}
if (element.type.isEqualMany([InputType.NUMBER, InputType.STRING]))
if (element.type?.isEqualMany([InputType.NUMBER, InputType.STRING]))
return (
<div>
<CoreInput
validation={element.type.isEqual(InputType.NUMBER) ? (el) => Number().isValid(el) : undefined}
isFormBuilder={true}
validation={
element.type.isEqual(InputType.NUMBER)
? (el) => Number().isValid(el)
: undefined
}
onChange={(e) => {
store.changeTotalValue(element.id, e);
}}
@ -145,8 +202,27 @@ export const FormBuilder = observer((props: IFormBuilder) => {
/>
</div>
);
return <>Error</>;
if (element.type?.isEqual(InputType.OBJECT))
return (
<>
{getFormBuilderComponents(
element.name
.replace(">", "")
.replace("<", "")
.replace("/", ""),
element.totalValue ?? element.defaultValue,
(text) => store.changeTotalValue(element.id, text)
).fold(
(s) => (
<>{s}</>
),
(error) => (
<>{error}</>
)
)}
</>
);
return <>Error {element}</>;
})}
</div>
)}

View file

@ -1,7 +1,8 @@
import clone from "just-clone";
import { makeAutoObservable } from "mobx";
import { FormViewModel } from "./form_view_model";
import { FormViewModel, InputBuilderViewModel } from "./form_view_model";
import { TypedEvent } from "../../helper/typed_event";
import { FormBuilderValidationModel } from "../../../features/dataset/dataset_model";
import { FormBuilderValidationModel } from "../../model/form_builder_validation_model";
export class ChangerForm extends TypedEvent<FormBuilderValidationModel | undefined> {}
@ -15,42 +16,41 @@ export class FormBuilderStore {
this.changerForm = new ChangerForm();
}
changeTotalSubValue(id: string, subIndex: number, value: string) {
changeTotalSubValue(id: string, subIndex: number, value: string, arrayIndex: number | undefined = undefined) {
if (this.formViewModel?.inputs) {
this.formViewModel.inputs = this.formViewModel?.inputs.map((el) => {
if (!el.id.isEqual(id)) {
return el;
} else {
if (el.totalValue instanceof Array) {
el.totalValue = el.totalValue.map((subElement) => {
if (subElement instanceof Array) {
subElement.map((subSubElement, i) => {
if (subIndex !== i) {
return subSubElement;
}
subSubElement.totalValue = value;
return subSubElement;
});
}
return subElement;
});
return el;
el.totalValue.atR(arrayIndex).map((array) =>
array.atR(subIndex).map((element: InputBuilderViewModel) => {
element.totalValue = value;
})
);
}
return el;
}
});
this.changerForm.emit(this.formViewModel?.fromFormBuilderValidationModel());
}
}
changeTotalValue(id: string, value: string) {
changeTotalValue(id: string, value: any) {
if (this.formViewModel?.inputs)
this.formViewModel.inputs = this.formViewModel?.inputs.map((el) => {
if (!el.id.isEqual(id)) {
return el;
}
el.totalValue = value;
if (typeof value === "string") {
el.totalValue = value;
} else {
el.totalValue = JSON.stringify(value);
}
return el;
});
this.changerForm.emit(this.formViewModel?.fromFormBuilderValidationModel());
@ -84,7 +84,7 @@ export class FormBuilderStore {
if (!(el.totalValue instanceof Array)) {
el.totalValue = [];
}
el.totalValue.push(el.values);
el.totalValue.push(clone(el.values as object));
return el;
});
this.changerForm.emit(this.formViewModel?.fromFormBuilderValidationModel());

View file

@ -1,13 +1,14 @@
import { makeAutoObservable, observable } from "mobx";
import { Result } from "../../helper/result";
import { v4 as uuidv4 } from "uuid";
import { FormBuilderValidationModel } from "../../../features/dataset/dataset_model";
import { FormBuilderValidationModel } from "../../model/form_builder_validation_model";
export enum InputType {
NUMBER = "number",
STRING = "string",
ARRAY = "Array",
ENUM = "Enum",
OBJECT = "OBJECT",
}
interface IOperation {
@ -29,6 +30,11 @@ export class InputBuilderViewModel {
) {
this.id = id ?? uuidv4();
}
isReactComponent = () => {
if (this.name.match(new RegExp(/<.*>/gm)) === null) {
throw new Error("Is dont react component" + this);
}
};
static fromJSON(json: any) {
try {
const value = JSON.parse(json);
@ -58,7 +64,8 @@ export class InputBuilderViewModel {
}
}
export const tagParse = new RegExp(/\${.*?}/gm);
export const tagParse = new RegExp(/\${.*?.*}/g);
export const objectParse = new RegExp(/OBJECT:.*}/gm);
export const bracketParser = new RegExp(/<.*?>/gm);
export const enumParser = new RegExp(/ENUM.*=..*?;/gm);
export const enumBodyParse = new RegExp(/".*?;/gm);
@ -66,7 +73,8 @@ export const enumNameParse = new RegExp(/^(.*?)=/gm);
export const typeBodyParse = new RegExp(/{.*?};/gms);
export const typeNameParse = new RegExp(/type .*?{/gms);
export const typeParse = new RegExp(/type.*?};/gms);
export const objectParseObj = new RegExp(/{.*}/gm);
export const multiTagParse = new RegExp(/\${.*?}/g);
export class FormViewModel {
@observable
inputs: InputBuilderViewModel[];
@ -82,7 +90,13 @@ export class FormViewModel {
return;
}
try {
return JSON.parse(result.replaceAll("\n", "").replaceAll("\\", "").replaceAll("/", ""));
return JSON.parse(
result
.replace(/[^\x00-\x7F]/g, "")
.replaceAll("\n", "")
.replaceAll("\\", "")
// .replaceAll("/", "")
);
} catch (error) {
console.log("ERROR: FormViewModel json() " + result);
}
@ -100,12 +114,16 @@ export class FormViewModel {
this.inputs.forEach((element) => {
let inputResult = element.totalValue ?? element.defaultValue;
if (element.type.isEqualMany([InputType.STRING, InputType.ENUM])) {
inputResult = `"${String(inputResult)}"`;
}
if (element.type.isEqual(InputType.NUMBER)) {
inputResult = Number(inputResult);
}
if (element.type.isEqual(InputType.OBJECT)) {
inputResult = element.totalValue ?? JSON.stringify(element.defaultValue);
}
if (element.type.isEqual(InputType.ARRAY)) {
if (element.totalValue === undefined) {
inputResult = "[]";
@ -115,10 +133,9 @@ export class FormViewModel {
element.totalValue.forEach((el) => {
const objectUnion = {};
let objectMapperResult = "";
if (el instanceof Array)
if (el instanceof Array) {
el.forEach((subElement) => {
let subResult = subElement.totalValue ?? subElement.defaultValue;
console.log(subResult);
if (subElement.type.isEqualMany([InputType.STRING, InputType.ENUM])) {
subResult = `"${String(subResult)}"`;
}
@ -129,6 +146,8 @@ export class FormViewModel {
// @ts-ignore
objectUnion[subElement.name] = subResult;
});
}
if (Object.keys(objectUnion).length !== 0) {
if (element.subType) {
objectMapperResult = this.getTypeBody(element.subType);
@ -140,14 +159,16 @@ export class FormViewModel {
}
if (objectMapperResult)
inputResult.push(
objectMapperResult.replaceAll("\n", "").replaceAll("\\", "").replaceAll("/", "").replaceAll(";", "")
objectMapperResult.replaceAll("\n", "").replaceAll("\\", "").replaceAll(";", "")
);
}
});
}
}
}
if (inputResult instanceof Array) inputResult = JSON.stringify(inputResult.map((el) => JSON.parse(el)));
if (inputResult instanceof Array)
inputResult = JSON.stringify(inputResult.map((el) => JSON.parse(el.replace(/[^\x00-\x7F]/g, ""))));
operations.push({ regExp: new RegExp("\\${" + element.name + ".*?}"), result: inputResult });
});
@ -171,6 +192,9 @@ export class FormViewModel {
}
static fromString(result: string, context: string): Result<void, FormViewModel> {
try {
if (result.isEmpty() && context.isEmpty()) {
return Result.error(undefined);
}
const enums = new Map<string, string[]>();
const types = new Map<string, InputBuilderViewModel[]>();
@ -205,37 +229,60 @@ export class FormViewModel {
return result
.match(tagParse)
?.map((el) => {
const inputArray = el.replaceMany(["$", "{", "}"], "").split(":");
if (el.includes(InputType.ENUM)) {
const enumName = inputArray.at(1)?.replaceMany([InputType.ENUM, "<", ">"], "");
if (enumName && enums.get(enumName)) {
return new InputBuilderViewModel(inputArray[0], InputType.ENUM, inputArray[2], enums.get(enumName));
}
const multiTag = el.match(multiTagParse);
const result = [];
if (multiTag?.length !== 0 && multiTag?.length !== 1) {
multiTag?.forEach((element) => {
result.push(this.tagParse(element, enums, types));
});
} else {
result.push(this.tagParse(el, enums, types));
}
if (el.includes(InputType.ARRAY) && types) {
const name = inputArray.at(1)?.replaceMany(["Array<", ">"], "");
if (name) {
return new InputBuilderViewModel(
inputArray[0],
InputType.ARRAY,
inputArray[2],
types.get(name),
undefined,
false,
name
);
}
}
if (el.includes(InputType.NUMBER)) {
return new InputBuilderViewModel(inputArray[0], InputType.NUMBER, inputArray[2]);
}
if (el.includes(InputType.STRING)) {
return new InputBuilderViewModel(inputArray[0], InputType.STRING, inputArray[2]);
}
return el;
return result;
})
.flat()
.filter((el) => el !== undefined) as InputBuilderViewModel[];
}
public static tagParse = (
el: string,
enums: Map<string, string[]>,
types: Map<string, InputBuilderViewModel[]> | undefined = undefined
) => {
const inputArray = el.replaceMany(["$", "{", "}"], "").split(":");
if (el.includes(InputType.ENUM)) {
const enumName = inputArray.at(1)?.replaceMany([InputType.ENUM, "<", ">"], "");
if (enumName && enums.get(enumName)) {
return new InputBuilderViewModel(inputArray[0], InputType.ENUM, inputArray[2], enums.get(enumName));
}
}
if (el.includes(InputType.ARRAY) && types) {
const name = inputArray.at(1)?.replaceMany(["Array<", ">"], "");
if (name) {
return new InputBuilderViewModel(
inputArray[0],
InputType.ARRAY,
inputArray[2],
types.get(name),
undefined,
false,
name
);
}
}
if (el.includes(InputType.OBJECT)) {
const objectParseResult = el.match(objectParse)?.at(0);
const result = objectParseResult?.slice(objectParseResult?.indexOf(":") + 1, objectParseResult.length) as string;
return new InputBuilderViewModel(inputArray[0], InputType.OBJECT, JSON.parse(result));
}
if (el.includes(InputType.NUMBER)) {
return new InputBuilderViewModel(inputArray[0], InputType.NUMBER, inputArray[2]);
}
if (el.includes(InputType.STRING)) {
return new InputBuilderViewModel(inputArray[0], InputType.STRING, inputArray[2]);
}
return el;
};
}

View file

@ -0,0 +1,25 @@
import { Result } from "../../../helper/result";
import { SelectDatasetScreen } from "./select_dataset/presentation/select_dataset_screen";
import { SelectDetail } from "./select_detail/presentation/select_detail_screen";
export enum FormBuilderComponents {
SelectDetail = "SelectDetail",
SelectDataset = "SelectDataset",
}
export interface IFormBuilderComponentsProps<T> {
dependency: T;
onChange: (obj: any) => void;
}
export const getFormBuilderComponents = (
name: string,
dependency: any,
onChange: (text: string) => void
): Result<string, React.ReactNode> => {
if (name.isEqual(FormBuilderComponents.SelectDetail)) {
return Result.ok(<SelectDetail dependency={dependency} onChange={onChange} />);
}
if (name.isEqual(FormBuilderComponents.SelectDataset)) {
return Result.ok(<SelectDatasetScreen dependency={dependency} onChange={onChange} />);
}
return Result.error(name);
};

View file

@ -0,0 +1,6 @@
import { observer } from "mobx-react-lite";
import { IFormBuilderComponentsProps } from "../../form_builder_components";
export const SelectDatasetScreen = observer((props:IFormBuilderComponentsProps<any>) => {
return <>SELECT DATASET</>;
});

View file

@ -0,0 +1,39 @@
import { makeAutoObservable } from "mobx";
import { Parts } from "../../../../../../features/details/details_http_repository";
export class SelectDetailViewModel {
details: Parts[];
constructor(parts: Parts[]) {
this.details = parts ?? [];
makeAutoObservable(this);
}
static empty = () => new SelectDetailViewModel([]);
isSelected = (name: string) => {
for (const el of this.details) {
if (el.name.isEqual(name)) {
return el.isSelect as boolean;
}
}
return false;
};
toDependency = () => {
this.details = this.details.filter((el) => el.isSelect === true);
return this;
};
select = (part: Parts) =>
this.details.indexOfR(part).fold(
() =>
this.details.forEach((el) =>
el.name.isEqualR(part.name).map(() => {
el.isSelect = !el.isSelect;
})
),
() => {
part.isSelect = true;
this.details.push(part);
}
);
}

View file

@ -0,0 +1,39 @@
// @ts-nocheck
import React from "react";
import { IFormBuilderComponentsProps } from "../../form_builder_components";
import { observer } from "mobx-react-lite";
import { ListItem } from "./ui/list_item";
import { SelectDetailStore } from "./select_detail_store";
import { SelectDetailViewModel } from "../model/select_details_model";
import { plainToInstance } from "class-transformer";
export const SelectDetail = observer((props: IFormBuilderComponentsProps<SelectDetailViewModel>) => {
const [store] = React.useState(() => new SelectDetailStore());
React.useEffect(() => {
console.log(props.dependency.details);
store.viewModel = new SelectDetailViewModel(props.dependency.details);
store.isLoading = false;
store.init();
}, []);
return (
<div>
{store.isLoading ? (
<></>
) : (
<>
{store.parts?.map((el) => {
return (
<ListItem
status={store.viewModel.isSelected(el.name)}
name={el.name}
imgURL={el.image}
onChange={() => (store.viewModel.select(el), props.onChange(store.viewModel.toDependency()))}
/>
);
})}
</>
)}
</div>
);
});

View file

@ -0,0 +1,31 @@
import makeAutoObservable from "mobx-store-inheritance";
import { DataSetHttpRepository } from "../../../../../../features/dataset/dataset_http_repository";
import { Parts } from "../../../../../../features/details/details_http_repository";
import { FormState } from "../../../../../store/base_store";
import { SelectDetailViewModel } from "../model/select_details_model";
export class SelectDetailStore extends FormState<SelectDetailViewModel, any> {
viewModel = SelectDetailViewModel.empty();
parts?: Parts[];
isLoading: boolean = true;
dataSetRepository: DataSetHttpRepository = new DataSetHttpRepository();
constructor() {
super();
makeAutoObservable(this);
}
isSelected = (name: string) => {
if (this.viewModel.details)
for (const el of this.viewModel.details) {
if (el.name.isEqual(name)) {
return el.isSelect as boolean;
}
}
return false;
};
init = async () => {
await this.mapOk("parts", this.dataSetRepository.getLocalParts());
};
updateCheckbox(el: Parts): void {
el.isSelect = true;
}
}

View file

@ -0,0 +1,33 @@
import { CoreSwitch } from "../../../../../switch/switch";
import { CoreText, CoreTextType } from "../../../../../text/text";
export interface IListItemProps {
name: string;
imgURL: string;
status: boolean;
onChange: (status: boolean, id: string) => void;
}
export const ListItem = (props: IListItemProps) => {
return (
<div
style={{
backgroundColor: "rgba(254, 247, 255, 1)",
border: "1px #6750a4 solid",
width: "100%",
height: 110,
display: "flex",
justifyContent: "space-between",
alignItems: "center",
borderRadius: 12,
marginTop: 10,
marginBottom: 10,
}}
>
<img style={{ height: 50, margin: 10 }} src={props.imgURL} alt="" />
<CoreText text={props.name} type={CoreTextType.large} />
<CoreSwitch isSelected={props.status} id={props.name} onChange={props.onChange} />
</div>
);
};

View file

@ -2,12 +2,8 @@
```
{
"scene":{
"objects": \${OBJECTS_SCENE:Array<OBJECTS_SCENE>:[]},
},
"camera_position":{
"center_shell": [\${CENTER_SHELL_1:string:0}, \${COLISION_SHAPE:Enum<L>:"BOX"}]
}
"name": \${NAME:string:""},
"scene":{<SelectScene/>:OBJECT:{"bricks":{"base_feature":{"path":"bricks/base_feature"},"form":{"path":"bricks/form"}}}}
}
```
@ -20,14 +16,12 @@ ENUM T = "ObjectDetection","PoseEstimation";
ENUM L = "POINT","SUN";
type MODELS = {
"id": \${IMAGE_FORMAT:Enum<L>:"jpg"},,
"name": \${NAME:string:""},
"model": \${MODEL:string:"models/1.fbx"}
};
type OBJECTS_SCENE = {
"name": \${NAME:string:""},
"collision_shape": \${COLISION_SHAPE:Enum<L>:"BOX"},
"loc_xyz": [\${TEST123:string:"test123"}, \${LOC_XYZ_2:number:0}, \${LOC_XYZ_3:number:0}],
};
```

View file

@ -0,0 +1,47 @@
import { Modal } from "antd";
import { observer } from "mobx-react-lite";
import { useState } from "react";
import { FormBuilderValidationModel } from "../../model/form_builder_validation_model";
import { ModalStore } from "../../store/base_store";
import { CoreButton } from "../button/button";
import { InputV2 } from "../input/input_v2";
import { FormBuilder } from "./form_builder";
import makeAutoObservable from "mobx-store-inheritance";
class FormBuilderTextStore extends ModalStore {
viewModel = FormBuilderValidationModel.empty();
constructor() {
super();
makeAutoObservable(this);
}
init = undefined;
}
export const FormBuildTest = observer(() => {
const [store] = useState(new FormBuilderTextStore());
return (
<div>
<InputV2 label={"result"} onChange={(text) => (store.viewModel.result = text)} />
<InputV2 label={"context"} onChange={(text) => (store.viewModel.context = text)} />
<CoreButton text="click" onClick={() => (store.isModalOpen = true)} />
<Modal
destroyOnClose={true}
open={store.isModalOpen}
footer={null}
closable={false}
closeIcon={null}
onCancel={() => {
store.isModalOpen = false;
}}
>
<FormBuilder
formBuilder={store.viewModel}
onChange={(e) => {
console.log(e.output);
// console.log(JSON.stringify(e.output))
}}
/>
</Modal>
</div>
);
});

View file

@ -10,7 +10,7 @@ const { Title } = Typography;
export interface IHeader {
largeText?: string;
minText?: string;
path?: string;
click?: Function;
needBackButton?: undefined | any;
}
@ -78,17 +78,17 @@ export const Header: React.FunctionComponent<IHeader> = (props: IHeader) => {
)}
</Row>
{props.minText !== undefined ? (
{/* {props.minText !== undefined ? (
<LinkTypography
style={{
marginBottom: "40px",
}}
path={props.path!}
path={props.click!}
text={props.minText}
/>
) : (
<></>
)}
)} */}
</Col>
);
};

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,22 @@
import * as React from "react";
import { CoreText, CoreTextType } from "../text/text";
interface IInputProps {
import { IStyle } from "../../model/style";
export enum CoreInputType {
small = "small",
default = "default",
big = "big",
}
interface IInputProps extends IStyle {
label: string;
value?: string;
subLabel?: React.ReactNode;
onChange?: (value: string) => void;
style?: React.CSSProperties;
validation?: (value: string) => boolean;
error?: string;
type?: CoreInputType;
trim?: boolean;
styleContentEditable?: React.CSSProperties;
isFormBuilder?: boolean;
}
export const CoreInput = (props: IInputProps) => {
@ -19,15 +28,24 @@ export const CoreInput = (props: IInputProps) => {
ref.current.innerText = value;
setAppendInnerText(false);
}
}, [ref, value, isAppendInnerText, setAppendInnerText]);
}, [ref, value, isAppendInnerText, setAppendInnerText, props]);
// React.useEffect(() => {
// if (props.isFormBuilder) {
// if (ref.current && props.value) {
// ref.current.innerText = value;
// setValue(props.value);
// console.log(props.value);
// }
// }
// }, [props.value]);
const isSmall = props.type !== undefined && props.type.isEqual(CoreInputType.small);
return (
<div
style={Object.assign(
{
backgroundColor: "rgba(230, 224, 233, 1)",
height: 58,
height: isSmall ? 40 : 58,
borderRadius: "4px 4px 0px 0px",
borderBottom: "solid 1px black",
padding: "10px 10px 10px 10px",
@ -35,25 +53,39 @@ export const CoreInput = (props: IInputProps) => {
props.style
)}
>
<CoreText type={CoreTextType.small} text={props.label} />
<CoreText
type={CoreTextType.small}
style={isSmall ? { fontSize: 8, position: "relative", top: -8 } : {}}
text={props.label}
/>
<input
defaultValue={props.value}
style={{
backgroundColor: "#00008000",
border: 1,
fontSize: 16,
fontFamily: "Roboto",
color: "#1D1B20",
height: 24,
width: "100%",
userSelect: 'none',
outline:'none'
}}
onChange={(e) => {
const val = e.target.value;
setValue(val)
<div
ref={ref}
contentEditable={true}
style={Object.assign(
{
backgroundColor: "#00008000",
border: 1,
fontSize: isSmall ? 12 : 16,
fontFamily: "Roboto",
color: "#1D1B20",
height: 24,
width: "100%",
userSelect: "none",
outline: "none",
position: isSmall ? "relative" : undefined,
top: isSmall ? -8 : undefined,
},
props.styleContentEditable
)}
onInput={(e) => {
let val = e.currentTarget.innerText;
setValue(val);
if (val) {
if (props.trim) {
val = val.trim().replaceAll("\n", "");
}
if (props.validation !== undefined && props.validation(val) && props.onChange) {
props.onChange(val);
return;

View file

@ -0,0 +1,58 @@
import { themeStore } from "../../..";
import { Icon } from "../icons/icons";
import { CoreText, CoreTextType, FontType } from "../text/text";
interface InputV2Props {
label: string;
value?: string;
trim?: boolean;
height?: number;
onChange?: (text: string) => void;
}
export const InputV2: React.FC<InputV2Props> = ({ label, height, value, onChange, trim }) => {
return (
<div
style={{
display: "flex",
height: "max-content",
minHeight: 56,
justifyContent: "space-between",
backgroundColor: themeStore.theme.surfaceContainerHighest,
}}
>
<div style={{ paddingLeft: 10 }}>
<CoreText
style={{ paddingTop: 5 }}
type={CoreTextType.smallV2}
color={themeStore.theme.greenWhite}
fontType={FontType.ubuntu}
text={label}
/>
<CoreText
onChange={(text) => {
let result = text;
if (trim) result = result.trim().replaceAll("\n", "");
if (onChange) onChange(result);
}}
style={{
backgroundColor: themeStore.theme.surfaceContainerHighest,
paddingTop: 5,
borderRadius: 5,
}}
contentEditable={true}
type={CoreTextType.largeV2}
color={themeStore.theme.white}
fontType={FontType.ubuntu}
text={value}
/>
</div>
<Icon
style={{ alignContent: "center", paddingRight: 10, position: "relative", display: "flex", paddingTop: 17 }}
type="Pencil"
color={themeStore.theme.greenWhite}
/>
</div>
);
};

View file

@ -0,0 +1,106 @@
import * as React from "react";
import { CoreText, CoreTextType } from "../text/text";
import { IStyle } from "../../model/style";
import { Icon } from "../icons/icons";
interface IInputProps extends IStyle {
label: string;
value?: number;
step: number;
max: number;
min: number;
subLabel?: React.ReactNode;
onChange?: (value: number) => void;
validation?: (value: number) => boolean;
error?: string;
}
export const CoreInputNumber = (props: IInputProps) => {
const [value, setValue] = React.useState<number>(() => props.value ?? 0);
const ref = React.useRef<HTMLDivElement>(null);
const [isAppendInnerText, setAppendInnerText] = React.useState(true);
React.useEffect(() => {
if (ref.current && isAppendInnerText) {
ref.current.innerText = String(value);
setAppendInnerText(false);
}
}, [ref, value, isAppendInnerText, setAppendInnerText, props]);
React.useEffect(() => {
if (props.value) setValue(props.value);
}, [props.value]);
return (
<>
<div
style={Object.assign(
{
backgroundColor: "rgba(230, 224, 233, 1)",
height: 58,
borderRadius: "4px 4px 0px 0px",
borderBottom: "solid 1px black",
padding: "10px 10px 10px 10px",
},
props.style
)}
>
<div>
<CoreText type={CoreTextType.small} text={props.label} />
</div>
<div style={{ width: "100%", display: "flex", justifyContent: "flex-end" }}>
<input
style={{
backgroundColor: "#00008000",
border: 1,
fontSize: 16,
fontFamily: "Roboto",
color: "#1D1B20",
height: 24,
width: "100%",
userSelect: "none",
outline: "none",
}}
value={value}
onChange={(e) => {
const val = e.target.value;
setValue(Number(val));
if (val) {
if (props.validation !== undefined && props.validation(Number(val)) && props.onChange) {
props.onChange(Number(val));
return;
}
if (props.onChange && props.validation === undefined) {
props.onChange(Number(val));
return;
}
}
}}
/>
{value ? (
props.validation ? (
props.validation(value) ? null : (
<div style={{ color: "#ff1d0c" }}>{props.error ? props.error : "error"}</div>
)
) : null
) : null}
<Icon
type="Plus"
onClick={() => {
setValue(value + props.step);
if (props.onChange) props.onChange(value);
}}
/>
<Icon
type="Minus"
onClick={() => {
setValue(value - props.step);
if (props.onChange) props.onChange(value);
}}
/>
</div>
</div>
</>
);
};

View file

@ -1,13 +1,13 @@
import * as React from "react";
import { Typography } from "antd";
import { useNavigate } from "react-router-dom";
import { IStyle } from "../../model/style";
const { Link } = Typography;
export interface ILinkTypography {
export interface ILinkTypography extends IStyle {
path: string;
text: string;
style?: React.CSSProperties;
}
export const LinkTypography: React.FunctionComponent<ILinkTypography> = (

View file

@ -0,0 +1,49 @@
import React from "react";
import { themeStore } from "../../..";
import { CoreText, CoreTextType } from "../text/text";
import { InputV2 } from "../input/input_v2";
import { CoreButton } from "../button/button";
import { ButtonV2, ButtonV2Type } from "../button/button_v2";
interface ModalProps {
isOpen: boolean;
onClose: () => void;
children: React.ReactNode;
style?: React.CSSProperties;
}
export const CoreModal: React.FC<ModalProps> = ({ isOpen, onClose, children, style }) => {
if (!isOpen) return null;
return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: themeStore.theme.fon,
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
onClick={() => onClose()}
>
<div
onClick={(event) => event.stopPropagation()}
style={{
backgroundColor: themeStore.theme.black,
border: `1px solid ${themeStore.theme.greenWhite}`,
padding: 20,
borderRadius: 5,
width: 400,
position: "relative",
}}
>
{children}
</div>
</div>
);
};

View file

@ -17,7 +17,7 @@ export const LoadPage: React.FunctionComponent<ILoadPage> = observer(
return (
<>
<Header
path={props.path}
click={props.click}
largeText={props.largeText}
minText={props.minText}
needBackButton={props.needBackButton}

View file

@ -1,15 +1,17 @@
import React from "react";
import { DatasetsScreenPath } from "../../../features/dataset/dataset_screen";
import { Icon } from "../icons/icons";
import { useNavigate } from "react-router-dom";
import { Spin } from "antd";
import { LoadingOutlined } from "@ant-design/icons";
import React from "react";
import { SceneManagerPath } from "../../../features/scene_manager/presentation/scene_manager";
import { AssemblesScreenPath } from "../../../features/assembles/assembles_screen";
import { DetailsScreenPath } from "../../../features/details/details_screen";
import { SimulationScreenPath } from "../../../features/simulations/simulations_screen";
import { EstimateScreenPath } from "../../../features/estimate/estimate_screen";
import { BehaviorTreeBuilderPath } from "../../../features/behavior_tree_builder/presentation/behavior_tree_builder_screen";
import { CalculationInstanceScreenPath as SkillScreenPath } from "../../../features/calculation_instance/presentation/calculation_instance_screen";
import { UiBaseError } from "../../model/ui_base_error";
import { DetailsScreenPath } from "../../../features/details/details_screen";
import { BehaviorTreeManagerScreenPath } from "../../../features/behavior_tree_manager/behavior_tree_manager_screen";
export interface IBlockProps {
name: string;
isActive: boolean;
@ -33,7 +35,11 @@ const Block = (props: IBlockProps) => {
alignContent: "center",
borderRadius: 12,
}
: { textAlignLast: "center", alignContent: "center" }
: {
textAlignLast: "center",
alignContent: "center",
height: 32,
}
}
>
<Icon type={props.icon ?? ""} />
@ -45,15 +51,21 @@ const Block = (props: IBlockProps) => {
export interface IMainPageProps {
page: string;
bodyChildren?: JSX.Element;
panelChildren?: JSX.Element;
panelStyle?: React.CSSProperties;
isLoading?: boolean;
maskLoader?: boolean;
error?: UiBaseError[];
}
export const MainPage = (props: IMainPageProps) => {
const blocksNames = [
{ name: "Детали", path: DetailsScreenPath, icon: "Setting" },
{ name: "Сборки", path: AssemblesScreenPath, icon: "Assembly" },
{ name: "Датасеты", path: DatasetsScreenPath, icon: "Datasets" },
{ name: "Сцена", path: SceneManagerPath, icon: "Layers" },
{ name: "Навыки", path: BehaviorTreeBuilderPath, icon: "Rocket" },
{ name: "Сцена", path: SceneManagerPath, icon: "Scene" },
{ name: "Вычисления", path: SkillScreenPath, icon: "Layers" },
{ name: "Поведение", path: BehaviorTreeManagerScreenPath, icon: "Rocket" },
{ name: "Симуляция", path: SimulationScreenPath, icon: "Simulation" },
{ name: "Оценка", path: EstimateScreenPath, icon: "Grade" },
];
@ -104,8 +116,8 @@ export const MainPage = (props: IMainPageProps) => {
</div>
</div>
<div>
{blocks.map((el) => (
<Block isActive={el.isActive} name={el.name} path={el.path} icon={el.icon} />
{blocks.map((el, index) => (
<Block isActive={el.isActive} name={el.name} path={el.path} icon={el.icon} key={index} />
))}
</div>
<div style={{ paddingBottom: 10 }}>
@ -118,8 +130,41 @@ export const MainPage = (props: IMainPageProps) => {
</div>
) : (
<>
<div style={{ width: 241, height: window.innerHeight, backgroundColor: "#F7F2FA", borderRadius: 16 }}> </div>
{props.bodyChildren}
{props.error?.isNotEmpty() ? (
<>
{props.error.map((el) => (
<div>{el.text}</div>
))}
</>
) : (
<>
{props.maskLoader ? (
<div
style={{
width: "100vw",
left: 241,
height: "100vh",
backgroundColor: "#000000b0",
position: "absolute",
zIndex: 100,
alignContent: "center",
textAlignLast: "center",
}}
>
<Spin />
</div>
) : null}
<div
style={Object.assign(
{ width: 241, height: window.innerHeight, backgroundColor: "#F7F2FA", borderRadius: 16 },
props.panelStyle
)}
>
{props.panelChildren}
</div>
{props.bodyChildren}
</>
)}
</>
)}
</div>

View file

@ -0,0 +1,230 @@
import React, { useEffect } from "react";
import { themeStore } from "../../..";
import { Icon } from "../icons/icons";
import { observer } from "mobx-react-lite";
import { makeAutoObservable } from "mobx";
import { RefDiv } from "../ref/ref_div";
export enum CycleState {
isMoving = "isMoving",
stop = "stop",
}
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
class MainPageStore {
cycleState = CycleState.stop;
isFirstClick: boolean = true;
stepMs = { 1: 15, 2: 20, 3: 25, 4: 30, 5: 35, 6: 40, 7: 45, 8: 50 };
icons: {
icon: string;
isActive: boolean;
style: React.CSSProperties;
left: number;
top: number;
}[] = [
{ icon: "Home", isActive: true, style: {}, left: 0, top: 0 },
{ icon: "ComposeSquares", isActive: false, style: {}, left: 0, top: 0 },
{ icon: "BottomSquare", isActive: false, style: {}, left: 0, top: 0 },
{ icon: "RobotARM", isActive: false, style: {}, left: 0, top: 0 },
{ icon: "Storage", isActive: false, style: {}, left: 0, top: 0 },
{ icon: "Graph", isActive: false, style: {}, left: 0, top: 0 },
{ icon: "PlayComputer", isActive: false, style: {}, left: 0, top: 0 },
{ icon: "DashBoard", isActive: false, style: {}, left: 0, top: 0 },
];
paddingLeft: number = 0;
offsetLeftCircle: number = 0;
leftCircle = 0;
constructor() {
makeAutoObservable(this);
}
init = (paddingLeft: number) => {
this.leftCircle = Math.abs(this.offsetLeftCircle - this.getActive()!.left) - paddingLeft;
this.paddingLeft = paddingLeft;
};
getActive = () => this.icons.filter((el) => el.isActive).at(0);
getActiveIndex = () => this.icons.findIndex((el) => el.isActive);
selectIndex = async (i: number) => {
const prevActive = this.getActive();
const prevActiveIndex = this.getActiveIndex();
this.icons
.map((element) => {
element.isActive = false;
return element;
})
.map((element, index) =>
i.isEqualR(index).fold(
() => {
element.isActive = true;
},
() => element
)
);
const nextActive = this.getActive();
const nextActiveIndex = this.getActiveIndex();
this.cycleState = CycleState.isMoving;
let distance = 0;
distance = nextActive!.left - this.offsetLeftCircle;
const step = distance / 10;
for await (const num of Array.from({ length: 10 }, (_, i) => i + 1)) {
// @ts-ignore
await delay(this.stepMs[Math.abs(nextActiveIndex - prevActiveIndex)]);
nextActive!.top = nextActive!.top + 3;
if (!this.isFirstClick) prevActive!.top = prevActive!.top - 3;
let offset = 0;
if (num === 10) {
offset = 6;
}
this.leftCircle = this.leftCircle + step - offset;
}
this.isFirstClick = false;
};
updateLeftStyle = (left: number, index: number) => this.icons.replacePropIndex({ left: left }, index);
}
export const MainPageV2: React.FC<{
children: React.ReactNode;
bgColor: string;
style?: React.CSSProperties | void;
rightChild?: React.ReactNode;
}> = observer(({ children, bgColor, style, rightChild }) => {
const [store] = React.useState(() => new MainPageStore());
useEffect(() => {
store.init(4);
}, []);
return (
<div
style={{
display: "flex",
flexDirection: "column",
backgroundColor: bgColor,
width: "100%",
height: "100%",
overflow: "auto",
}}
>
<div
style={{
alignSelf: "center",
width: 645,
height: 60,
backgroundColor: themeStore.theme.navBlue,
paddingBottom: 60,
borderRadius: 20,
zIndex: 20,
}}
>
<RefDiv
style={{
display: "flex",
width: "100%",
height: 60,
justifyContent: "space-evenly",
alignItems: "center",
}}
children={
<>
{store.icons.map((el, i) => (
<div key={i} style={{ zIndex: 25 }}>
{el.isActive === true ? (
<>
<RefDiv
style={{ position: "relative", top: 30 }}
property="offsetLeft"
onChange={(text) => store.updateLeftStyle(Number(text), i)}
>
<Icon
type={el.icon}
style={{
margin: 10,
zIndex: 25,
position: "relative",
}}
onClick={() => {
store.icons
.map((element) => {
element.isActive = false;
return element;
})
.map((element, index) =>
i.isEqualR(index).fold(
() => {
element.isActive = true;
},
() => element
)
);
}}
/>
</RefDiv>
</>
) : (
<>
<RefDiv
style={{ position: "relative", top: el.top, zIndex: 23 }}
property="offsetLeft"
onChange={(text) => store.updateLeftStyle(Number(text), i)}
>
<Icon type={el.icon} style={{ margin: 10 }} onClick={() => store.selectIndex(i)} />
</RefDiv>
</>
)}
</div>
))}
</>
}
/>
<div style={{ width: 0, height: 0 }}>
<RefDiv
property="offsetLeft"
onChange={(text) => {
store.offsetLeftCircle = Number(text);
}}
style={{
position: "relative",
top: -30,
left: store.leftCircle,
backgroundColor: bgColor,
width: 60,
height: 60,
borderRadius: "50%",
paddingTop: 10,
zIndex: 21,
}}
>
<div
style={{
position: "relative",
top: -5,
left: 5,
backgroundColor: themeStore.theme.greenWhite,
width: 50,
height: 50,
borderRadius: 200,
}}
></div>
</RefDiv>
</div>
<div
style={{
position: "absolute",
right: 0,
top: 13,
width: 300,
height: 50,
}}
>
{rightChild}
</div>
</div>
<div style={Object.assign({ width: "100%" }, style)}>{children}</div>
</div>
);
});

View file

@ -0,0 +1,60 @@
import { IHeader } from "../header/header";
import { CoreText, CoreTextType } from "../text/text";
import { Spin } from "antd";
interface IPreviewPageProps extends IHeader {
isLoading: boolean;
isError: boolean;
children?: JSX.Element | JSX.Element[];
}
export const PreviewPage = (props: IPreviewPageProps) => {
return (
<div style={{ width: "100%", height: "100%" }}>
<div
style={{
width: "100%",
marginTop: 10,
textAlign: "center",
position: "absolute",
}}
>
<CoreText text={props.largeText ?? ""} type={CoreTextType.big} />
<CoreText
onClick={() => {
if (props.click) props.click();
}}
text={props.minText ?? ""}
type={CoreTextType.small}
style={{ color: "rgba(68, 142, 247, 1)", cursor: "pointer" }}
/>
</div>
<div
style={{
height: "100%",
width: "100%",
padding: 60,
paddingTop: 100,
}}
>
<div
style={{
borderRadius: 7,
border: "1px solid #CAC4D0",
background: "#FEF7FF",
height: "100%",
width: "100%",
overflowX: "auto",
}}
>
{props.isLoading ? (
<div style={{ width: "100%", height: "100%", justifyContent: "center", alignItems: "center" }}>
<Spin />
</div>
) : (
props.children
)}
</div>
</div>
</div>
);
};

Some files were not shown because too many files have changed in this diff Show more