MVP with Tensorboard
3
ui/.gitignore
vendored
|
@ -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
|
@ -0,0 +1 @@
|
|||
{"base_feature":"/Users/idontsudo/ozon/ui/bricks/base_feature","form":"/Users/idontsudo/webservice/ui/bricks/form"}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export class {{name.pascalCase()}}Repository {
|
||||
|
||||
}
|
|
@ -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 <></>;
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
import makeAutoObservable from "mobx-store-inheritance";
|
||||
|
||||
export class {{name.pascalCase()}}Store {
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
}
|
16
ui/bricks/base_feature/brick.yaml
Normal 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)
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 <></>;
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
import { HttpRepository } from "../../../../../../core/repository/http_repository";
|
||||
|
||||
export class {{name.pascalCase()}}HttpRepository extends HttpRepository {
|
||||
|
||||
}
|
|
@ -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
|
@ -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
|
@ -0,0 +1 @@
|
|||
{"bricks":{"base_feature":{"path":"bricks/base_feature"},"form":{"path":"bricks/form"}}}
|
5
ui/mason.yaml
Normal file
|
@ -0,0 +1,5 @@
|
|||
bricks:
|
||||
base_feature:
|
||||
path: base_feature
|
||||
form:
|
||||
path: form
|
18562
ui/package-lock.json
generated
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 3.8 KiB |
|
@ -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
After Width: | Height: | Size: 28 KiB |
BIN
ui/public/logo.png
Normal file
After Width: | Height: | Size: 818 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 9.4 KiB |
54
ui/readme.md
Normal 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/
|
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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 |
|
@ -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));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
12
ui/src/core/extensions/date.ts
Normal 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()}`;
|
||||
};
|
||||
}
|
||||
};
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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}]}
|
||||
|
|
|
@ -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)];
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -40,3 +40,5 @@ export class TypedEvent<T> {
|
|||
return this.on((e) => te.emit(e));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
31
ui/src/core/helper/use_store.tsx
Normal 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;
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
export function validateRequired(value: string) {
|
||||
return value ? undefined : "required";
|
||||
}
|
|
@ -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 [];
|
||||
};
|
53
ui/src/core/model/camera_model.ts
Normal 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);
|
||||
}
|
63
ui/src/core/model/cameras.ts
Normal 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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
export interface DatabaseModel {
|
||||
export interface DatabaseModelId {
|
||||
_id?: string;
|
||||
}
|
||||
|
|
17
ui/src/core/model/device_dependency_view_model.ts
Normal 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("");
|
||||
}
|
||||
}
|
8
ui/src/core/model/form.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export enum FormType {
|
||||
weights = "weights",
|
||||
robotName = "robot_name",
|
||||
cameraDeviceForm = "camera",
|
||||
topic = "topic",
|
||||
formBuilder = "formBuilder",
|
||||
moveToPose = "move_to_pose",
|
||||
}
|
144
ui/src/core/model/form_builder_validation_model.tsx
Normal 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>:[]}
|
||||
}`;
|
57
ui/src/core/model/light_model.ts
Normal 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();
|
||||
}
|
57
ui/src/core/model/point_model.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
74
ui/src/core/model/robot_model.ts
Normal 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, []);
|
||||
}
|
47
ui/src/core/model/robots.ts
Normal 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;
|
||||
}
|
65
ui/src/core/model/scene_asset.ts
Normal 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;
|
||||
};
|
||||
}
|
8
ui/src/core/model/scene_models_type.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export enum SceneModelsTypes {
|
||||
SOLID = "SOLID",
|
||||
ROBOT = "ROBOT",
|
||||
LIGHT = "LIGHT",
|
||||
CAMERA = "CAMERA",
|
||||
POINT = "POINT",
|
||||
ZONE = "ZONE",
|
||||
}
|
512
ui/src/core/model/skill_model.ts
Normal 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)
|
||||
);
|
||||
}
|
70
ui/src/core/model/solid_model.ts
Normal 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), [], "", "", "", "", "");
|
||||
}
|
3
ui/src/core/model/spawn_position_types.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export enum SpawnPositionTypes {
|
||||
BoundBox = "BoundBox",
|
||||
}
|
3
ui/src/core/model/style.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export interface IStyle{
|
||||
style?: React.CSSProperties;
|
||||
}
|
9
ui/src/core/model/topics.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export interface Topics {
|
||||
topics: Topic[];
|
||||
}
|
||||
|
||||
export interface Topic {
|
||||
sid: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
|
@ -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",
|
||||
}
|
29
ui/src/core/model/validation_model.ts
Normal 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);
|
||||
}
|
||||
};
|
||||
}
|
33
ui/src/core/model/zone_model.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
34
ui/src/core/repository/core_ros_ws_bridge_repository.ts
Normal 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))
|
||||
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 />,
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
61
ui/src/core/store/theme_store.ts
Normal 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);
|
||||
}
|
|
@ -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>
|
||||
|
|
64
ui/src/core/ui/button/button_v2.tsx
Normal 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>
|
||||
);
|
||||
};
|
88
ui/src/core/ui/card/card.tsx
Normal 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>
|
||||
);
|
||||
};
|
56
ui/src/core/ui/drawer/drawer.tsx
Normal 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}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ width: "100%", height: 1, backgroundColor: themeStore.theme.outlineVariant }}></div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -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</>;
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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}],
|
||||
};
|
||||
```
|
||||
|
|
47
ui/src/core/ui/form_builder/test.tsx
Normal 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>
|
||||
);
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
58
ui/src/core/ui/input/input_v2.tsx
Normal 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>
|
||||
);
|
||||
};
|
106
ui/src/core/ui/inputNumber/input_number.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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> = (
|
||||
|
|
49
ui/src/core/ui/modal/modal.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
230
ui/src/core/ui/pages/main_page_v2.tsx
Normal 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>
|
||||
);
|
||||
});
|
60
ui/src/core/ui/pages/preview_page.tsx
Normal 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>
|
||||
);
|
||||
};
|