diff --git a/.vscode/settings.json b/.vscode/settings.json index bb0a83f..1a82dcc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,9 +4,9 @@ "Ведите", "дерево", "Количество", + "модели", "может", "навык", - "модели", "Новое", "отрицательное", "принимать", @@ -18,6 +18,7 @@ "Collada", "Contolls", "GLTF", + "grau", "raycaster", "skils", "typedataset", diff --git a/server/src/features/behavior_trees/models/behavior_tree_database_model.ts b/server/src/features/behavior_trees/models/behavior_tree_database_model.ts index 8297b58..33a0acf 100644 --- a/server/src/features/behavior_trees/models/behavior_tree_database_model.ts +++ b/server/src/features/behavior_trees/models/behavior_tree_database_model.ts @@ -12,6 +12,7 @@ export const BehaviorTreeSchema = new Schema({ name: { type: String, }, + description: { type: String }, scene: { type: Schema.Types.Mixed, default: [], @@ -21,7 +22,6 @@ export const BehaviorTreeSchema = new Schema({ }, unixTime: { type: Number, - default: Date.now(), }, isFinished: { type: Boolean, diff --git a/server/src/features/behavior_trees/models/behavior_tree_validation_model.ts b/server/src/features/behavior_trees/models/behavior_tree_validation_model.ts index 9dfac34..e729b67 100644 --- a/server/src/features/behavior_trees/models/behavior_tree_validation_model.ts +++ b/server/src/features/behavior_trees/models/behavior_tree_validation_model.ts @@ -1,4 +1,4 @@ -import { IsMongoId, IsString } from "class-validator"; +import { IsMongoId, IsNumber, IsString } from "class-validator"; import { IBehaviorTreeModel } from "./behavior_tree_database_model"; export class BehaviorTreeValidationModel implements IBehaviorTreeModel { @@ -8,4 +8,6 @@ export class BehaviorTreeValidationModel implements IBehaviorTreeModel { public project:string; public skills:any; public xml:string; + @IsNumber() + public unixTime: number } diff --git a/ui/public/index.html b/ui/public/index.html index 583a521..c7814ac 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -1,133 +1,183 @@ + + + + + + + + + + + + + + + Robossembler + - - - - - - - - - Robossembler - + + +
+ - - -
+ + - + .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; - \ No newline at end of file + width: 100%; + height: 100%; + } + + diff --git a/ui/src/core/extensions/date.ts b/ui/src/core/extensions/date.ts index 09ab47d..1f5337b 100644 --- a/ui/src/core/extensions/date.ts +++ b/ui/src/core/extensions/date.ts @@ -6,35 +6,7 @@ export const DateExtensions = () => { } if (Date.prototype.formatDate === undefined) { Date.prototype.formatDate = function () { - const days = ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"]; - const monthNames = [ - "Января", - "Февраля", - "Марта", - "Апреля", - "Майя", - "Июня", - "Июля", - "Августа", - "Сентября", - "Октября", - "Ноября", - "Декабря", - ]; - - return ( - days[this.getDay()] + - ", " + - this.getDate() + - ", " + - monthNames[this.getMonth()] + - ", " + - this.getFullYear() + - "г.," + - this.getHours() + - ":" + - this.getMinutes() - ); + return `${this.getFullYear()}.${this.getMonth()}.${this.getDay()} ${this.getHours()}:${this.getMinutes()}`; }; } }; diff --git a/ui/src/core/model/skill_model.ts b/ui/src/core/model/skill_model.ts index 83c2219..5ed4485 100644 --- a/ui/src/core/model/skill_model.ts +++ b/ui/src/core/model/skill_model.ts @@ -37,7 +37,7 @@ export interface IDeviceDependency { export interface IParam { type: string; - dependency: Object; + dependency?: Object; } export class ParamViewModel implements IParam { type: string; @@ -190,6 +190,9 @@ export class SkillModel extends ValidationModel implements ISkill { super(); makeAutoObservable(this); } + + bgColor: string = `rgba(5, 26, 39, 1)`; + borderColor: string = "rgba(25, 130, 196, 1)"; @IsOptional() @IsString() sid?: string; @@ -239,13 +242,18 @@ export class Skills { @IsArray() @Type(() => SkillModel) skills: SkillModel[]; + static fromSkills = (skilsModel: SkillModel[]) => { + const skills = this.empty(); + skills.skills = skilsModel; + return skills; + }; validation = (): Result => { 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()) { + if (Object.keys(param?.dependency ?? {}).isEmpty()) { errors.push(param.type); } }); @@ -257,6 +265,16 @@ export class Skills { } return Result.ok(undefined); }; + getCssAtLabel = (label: string): React.CSSProperties => + this.skills.reduce((acc, el) => { + el.BTAction.rFind((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((acc, el) => { @@ -270,7 +288,7 @@ export class Skills { toSkillView = (): ISkillView[] => this.skills.map((el) => { return { - name: el.Module.name, + name: el.SkillPackage.name, children: el.BTAction.map((act) => { return { name: act.name, uuid: v4() }; }), @@ -346,7 +364,7 @@ export class Skills { skill.BTAction.map((action) => { action.param.map((param) => { if (param.type.isEqualR(skillType)) { - acc.push(param.dependency); + acc.push(param?.dependency ?? {}); } return param; }); @@ -375,7 +393,7 @@ export class Skills { skill.BTAction.forEach((action) => { action.param.forEach((param) => { if (param.type.isEqual(skillType)) { - acc = Object.keys(param.dependency).isNotEmpty(); + acc = Object.keys(param?.dependency ?? {}).isNotEmpty(); } }); }); @@ -399,7 +417,6 @@ export class Skills { return this.skills.filter((skill) => !skill.sid?.isEqual(sid)); } updateSkill = (skill: SkillModel) => { - console.log(skill); this.skills = this.skills.map((el) => { if (el.sid?.isEqual(skill.sid ?? "")) { el = skill; @@ -408,7 +425,13 @@ export class Skills { }); }; skillHasForm = (label: string): boolean => { - // TODO:NEED IMPLEMENTS return true; }; + getSkillWidthAtLabel = (label: string): number => + this.getSkillParams(label).reduce((acc, element) => { + if (element.type.length > acc) { + acc = element.type.length; + } + return acc; + }, 0); } diff --git a/ui/src/core/model/validation_model.ts b/ui/src/core/model/validation_model.ts index 81a94ba..7603fa6 100644 --- a/ui/src/core/model/validation_model.ts +++ b/ui/src/core/model/validation_model.ts @@ -14,9 +14,8 @@ export class ValidationModel { if (error.constraints) return Object.values(error.constraints).join(", "); return ""; }); - console.log(errors) - console.log(this) - return Result.error(message.join(",")); + + return Result.error(message.join(", \n")); } else { return Result.ok(this as unknown as T); } diff --git a/ui/src/core/routers/routers.tsx b/ui/src/core/routers/routers.tsx index f86090f..8887fc1 100644 --- a/ui/src/core/routers/routers.tsx +++ b/ui/src/core/routers/routers.tsx @@ -9,7 +9,10 @@ import { DataSetScreen, DatasetsScreenPath } from "../../features/dataset/datase import { AssemblesScreen, AssemblesScreenPath } from "../../features/assembles/assembles_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 { + 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"; @@ -17,7 +20,6 @@ import { SkillsScreen, SkillsScreenPath } from "../../features/skills/skills_scr import { CalculationsTemplateScreenPath } from "../../features/calculations_template/calculations_template_screen"; const idURL = ":id"; - export const router = createBrowserRouter([ { path: AllProjectScreenPath, @@ -32,7 +34,11 @@ export const router = createBrowserRouter([ element: , }, { - path: BehaviorTreeBuilderPath, + path: BehaviorTreeBuilderPath(idURL), + element: , + }, + { + path: BehaviorTreeBuilderPath(), element: , }, { diff --git a/ui/src/core/store/base_store.ts b/ui/src/core/store/base_store.ts index 43ce8d1..ebd7e65 100644 --- a/ui/src/core/store/base_store.ts +++ b/ui/src/core/store/base_store.ts @@ -58,15 +58,15 @@ export class SimpleErrorState extends UiLoader { } export class ModalStore extends SimpleErrorState { isModalOpen: boolean = false; - showModal = () => { + modalShow = () => { this.isModalOpen = true; }; - handleOk = () => { + modalClickOk = () => { this.isModalOpen = false; }; - handleCancel = () => { + modalCancel = () => { this.isModalOpen = false; }; } diff --git a/ui/src/core/store/theme_store.ts b/ui/src/core/store/theme_store.ts new file mode 100644 index 0000000..b0e0da8 --- /dev/null +++ b/ui/src/core/store/theme_store.ts @@ -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); +} diff --git a/ui/src/core/ui/button/button_v2.tsx b/ui/src/core/ui/button/button_v2.tsx new file mode 100644 index 0000000..80b72d9 --- /dev/null +++ b/ui/src/core/ui/button/button_v2.tsx @@ -0,0 +1,54 @@ +import { match } from "ts-pattern"; +import { themeStore } from "../../.."; +import { CoreText, CoreTextType } from "../text/text"; +export enum ButtonV2Type { + default = "default", +} +export const ButtonV2 = ({ + text, + textColor, + onClick, + style, + icon, + isFilled, + type, +}: { + text?: string; + textColor?: string; + onClick: () => void; + style?: React.CSSProperties; + icon?: React.ReactNode; + isFilled?: boolean; + type?: ButtonV2Type; +}) => { + return ( +
15) + .otherwise(() => 5), + borderRadius: match(type) + .with(ButtonV2Type.default, () => 5) + .otherwise(() => 100), + display: "flex", + paddingRight: 10, + paddingLeft: 10, + }} + onClick={() => onClick()} + > + {icon} + CoreTextType.smallv3) + .otherwise(() => CoreTextType.largeV2)} + /> +
+ ); +}; diff --git a/ui/src/core/ui/card/card.tsx b/ui/src/core/ui/card/card.tsx new file mode 100644 index 0000000..08284ca --- /dev/null +++ b/ui/src/core/ui/card/card.tsx @@ -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(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 ( +
+
+
+ +
+ +
+ +
+ clickGoToIcon()} text="Перейти" /> +
+ clickDeleteIcon()} + style={{ + border: `1px solid ${themeStore.theme.greenWhite}`, + height: 30, + width: 30, + textAlign: "center", + alignContent: "center", + borderRadius: 5, + cursor: "pointer", + }} + /> +
+
+
+
+
+ + + +
+
+
+ ); +}; diff --git a/ui/src/core/ui/form_builder/forms/select_detail/presentation/select_detail_screen.tsx b/ui/src/core/ui/form_builder/forms/select_detail/presentation/select_detail_screen.tsx index a5188f9..e5e5c9f 100644 --- a/ui/src/core/ui/form_builder/forms/select_detail/presentation/select_detail_screen.tsx +++ b/ui/src/core/ui/form_builder/forms/select_detail/presentation/select_detail_screen.tsx @@ -12,8 +12,6 @@ export const SelectDetail = observer((props: IFormBuilderComponentsProps { store.viewModel = new SelectDetailViewModel(props.dependency.details); store.isLoading = false; - console.log(store.viewModel); - store.init(); }, []); diff --git a/ui/src/core/ui/form_builder/forms/select_detail/presentation/select_detail_store.tsx b/ui/src/core/ui/form_builder/forms/select_detail/presentation/select_detail_store.tsx index 3bcb116..1ad6afc 100644 --- a/ui/src/core/ui/form_builder/forms/select_detail/presentation/select_detail_store.tsx +++ b/ui/src/core/ui/form_builder/forms/select_detail/presentation/select_detail_store.tsx @@ -5,8 +5,6 @@ import { FormState } from "../../../../../store/base_store"; import { SelectDetailViewModel } from "../model/select_details_model"; export class SelectDetailStore extends FormState { - - viewModel = SelectDetailViewModel.empty(); parts?: Parts[]; isLoading: boolean = true; @@ -16,7 +14,6 @@ export class SelectDetailStore extends FormState { makeAutoObservable(this); } isSelected = (name: string) => { - console.log(this.viewModel.details); if (this.viewModel.details) for (const el of this.viewModel.details) { if (el.name.isEqual(name)) { diff --git a/ui/src/core/ui/icons/icons.tsx b/ui/src/core/ui/icons/icons.tsx index 6fc4d97..dcca5b6 100644 --- a/ui/src/core/ui/icons/icons.tsx +++ b/ui/src/core/ui/icons/icons.tsx @@ -54,12 +54,52 @@ const getIconSvg = ( switch (type) { case "": return Result.ok(); + case "LeftIcon": + return Result.ok( + + + + ); + case "SettingGear": + return Result.ok( + + + + ); + case "Bucket": + return Result.ok( + + + + ); case "Error": return Result.ok( @@ -71,6 +111,173 @@ const getIconSvg = ( /> ); + case "BottomSquare": + return Result.ok( + + + + ); + case "DashBoard": + return Result.ok( + + + + + ); + case "PlayComputer": + return Result.ok( + + + + + ); + case "Graph": + return Result.ok( + + + + + + + ); + case "Storage": + return Result.ok( + + + + + + + + + ); + case "RobotARM": + return Result.ok( + + + + + + ); + case "Home": + return Result.ok( + + + + + ); + case "ComposeSquares": + return Result.ok( + + + + + ); case "Log": return Result.ok( ); + case "Plus": + return Result.ok( + + + + ); case "PlusCircle": return Result.ok( @@ -482,7 +695,7 @@ const getIconSvg = ( fillRule="evenodd" clipRule="evenodd" d="M16.06 0.589883L17.41 1.93988C18.2 2.71988 18.2 3.98988 17.41 4.76988L4.18 17.9999H0V13.8199L10.4 3.40988L13.23 0.589883C14.01 -0.190117 15.28 -0.190117 16.06 0.589883ZM2 15.9999L3.41 16.0599L13.23 6.22988L11.82 4.81988L2 14.6399V15.9999Z" - fill="#31111D" + fill={color ?? "#49454F"} /> ); diff --git a/ui/src/core/ui/input/input_v2.tsx b/ui/src/core/ui/input/input_v2.tsx new file mode 100644 index 0000000..488e86f --- /dev/null +++ b/ui/src/core/ui/input/input_v2.tsx @@ -0,0 +1,54 @@ +import { themeStore } from "../../.."; +import { Icon } from "../icons/icons"; +import { CoreText, CoreTextType, FontType } from "../text/text"; + +interface InputV2Props { + label: string; + value?: string; + height?: number; + onChange?: (text: string) => void; +} +export const InputV2: React.FC = ({ label, height, value, onChange }) => { + return ( +
+
+ + { + if (onChange) onChange(text); + }} + style={{ + backgroundColor: themeStore.theme.surfaceContainerHighest, + paddingTop: 5, + borderRadius: 5, + }} + contentEditable={true} + type={CoreTextType.largeV2} + color={themeStore.theme.white} + fontType={FontType.ubuntu} + text={value} + /> +
+ + +
+ ); +}; diff --git a/ui/src/core/ui/modal/modal.tsx b/ui/src/core/ui/modal/modal.tsx new file mode 100644 index 0000000..7b6c8a5 --- /dev/null +++ b/ui/src/core/ui/modal/modal.tsx @@ -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 = ({ isOpen, onClose, children, style }) => { + if (!isOpen) return null; + + return ( +
onClose()} + > +
event.stopPropagation()} + style={{ + backgroundColor: themeStore.theme.black, + border: `1px solid ${themeStore.theme.greenWhite}`, + padding: 20, + borderRadius: 5, + width: 400, + position: "relative", + }} + > + {children} +
+
+ ); +}; + \ No newline at end of file diff --git a/ui/src/core/ui/pages/main_page.tsx b/ui/src/core/ui/pages/main_page.tsx index 296fe1f..fe60d70 100644 --- a/ui/src/core/ui/pages/main_page.tsx +++ b/ui/src/core/ui/pages/main_page.tsx @@ -27,17 +27,19 @@ const Block = (props: IBlockProps) => { style={ props.isActive ? { - textAlignLast: "center", - height: 32, - backgroundColor: "rgba(232, 222, 248, 1)", - marginLeft: 5, - marginRight: 5, - alignContent: "center", - borderRadius: 12, - } + textAlignLast: "center", + height: 32, + backgroundColor: "rgba(232, 222, 248, 1)", + marginLeft: 5, + marginRight: 5, + alignContent: "center", + borderRadius: 12, + } : { - textAlignLast: "center", alignContent: "center", height: 32, - } + textAlignLast: "center", + alignContent: "center", + height: 32, + } } > @@ -57,7 +59,6 @@ export interface IMainPageProps { error?: UiBaseError[]; } - export const MainPage = (props: IMainPageProps) => { const blocksNames = [ { name: "Детали", path: DetailsScreenPath, icon: "Setting" }, @@ -65,7 +66,7 @@ export const MainPage = (props: IMainPageProps) => { { name: "Датасеты", path: DatasetsScreenPath, icon: "Datasets" }, { name: "Сцена", path: SceneManagerPath, icon: "Scene" }, { name: "Вычисления", path: SkillScreenPath, icon: "Layers" }, - { name: "Поведение", path: BehaviorTreeBuilderPath, icon: "Rocket" }, + { name: "Поведение", path: BehaviorTreeBuilderPath(), icon: "Rocket" }, { name: "Симуляция", path: SimulationScreenPath, icon: "Simulation" }, { name: "Оценка", path: EstimateScreenPath, icon: "Grade" }, ]; diff --git a/ui/src/core/ui/pages/main_page_v2.tsx b/ui/src/core/ui/pages/main_page_v2.tsx new file mode 100644 index 0000000..b096e1e --- /dev/null +++ b/ui/src/core/ui/pages/main_page_v2.tsx @@ -0,0 +1,213 @@ +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 }> = + observer(({ children, bgColor, style }) => { + const [store] = React.useState(() => new MainPageStore()); + useEffect(() => { + store.init(4); + }, []); + return ( +
+
+ + {store.icons.map((el, i) => ( +
+ {el.isActive === true ? ( + <> + store.updateLeftStyle(Number(text), i)} + > + { + store.icons + .map((element) => { + element.isActive = false; + return element; + }) + .map((element, index) => + i.isEqualR(index).fold( + () => { + element.isActive = true; + }, + () => element + ) + ); + }} + /> + + + ) : ( + <> + store.updateLeftStyle(Number(text), i)} + > + store.selectIndex(i)} /> + + + )} +
+ ))} + + } + /> +
+ { + store.offsetLeftCircle = Number(text); + }} + style={{ + position: "relative", + top: -30, + left: store.leftCircle, + backgroundColor: bgColor, + width: 60, + height: 60, + borderRadius: "50%", + paddingTop: 10, + zIndex: 21, + }} + > +
+
+
+
+
{children}
+
+ ); + }); diff --git a/ui/src/core/ui/pages/preview_page.tsx b/ui/src/core/ui/pages/preview_page.tsx index d3f765e..58fe9a1 100644 --- a/ui/src/core/ui/pages/preview_page.tsx +++ b/ui/src/core/ui/pages/preview_page.tsx @@ -21,7 +21,6 @@ export const PreviewPage = (props: IPreviewPageProps) => { { - console.log(200); if (props.click) props.click(); }} text={props.minText ?? ""} diff --git a/ui/src/core/ui/ref/ref_div.tsx b/ui/src/core/ui/ref/ref_div.tsx new file mode 100644 index 0000000..3ee29b0 --- /dev/null +++ b/ui/src/core/ui/ref/ref_div.tsx @@ -0,0 +1,21 @@ +import { useEffect, useRef } from "react"; + +export const RefDiv: React.FC<{ + property?: string; + onChange?: (property: string) => void; + style?: React.CSSProperties; + children?: React.ReactNode; +}> = ({ property, onChange, style, children }) => { + const ref = useRef(null); + useEffect(() => { + if (ref.current && onChange) { + // @ts-ignore + onChange(String(ref.current[property])); + } + } ); + return ( +
+ {children} +
+ ); +}; diff --git a/ui/src/core/ui/select/select_v2.tsx b/ui/src/core/ui/select/select_v2.tsx new file mode 100644 index 0000000..f42e65e --- /dev/null +++ b/ui/src/core/ui/select/select_v2.tsx @@ -0,0 +1,96 @@ +import React from "react"; +import { CoreText, CoreTextType } from "../text/text"; +import { IStyle } from "../../model/style"; +import { themeStore } from "../../.."; + +interface ISelectV2Props extends IStyle { + items: { name: string; value: string }[]; + initialValue: string; + label: string; + onChange: (value: string) => void; +} +export const SelectV2: React.FC = ({ items, initialValue, label, onChange, style }) => { + const ref = React.useRef(null); + const [cursorIsCorses, setCursorIsCorses] = React.useState(false); + const [width, setWidth] = React.useState(0); + const [value, setValue] = React.useState(initialValue); + React.useEffect(() => { + ref.current?.addEventListener("mousemove", () => { + setCursorIsCorses(true); + }); + ref.current?.addEventListener("mouseleave", () => { + setCursorIsCorses(false); + }); + + setWidth(Number(ref.current?.clientWidth)); + }, [ref, setCursorIsCorses]); + + return ( +
+
+ +
+ {value} +
+
+
+
+ {cursorIsCorses + ? items.map((el, i) => ( + { + setValue(el.name); + onChange(el.value); + }} + style={{ + padding: 10, + alignContent: "center", + cursor: "pointer", + }} + /> + )) + : null} +
+
+
+ ); +}; diff --git a/ui/src/core/ui/text/text.tsx b/ui/src/core/ui/text/text.tsx index 6503f3f..85aef71 100644 --- a/ui/src/core/ui/text/text.tsx +++ b/ui/src/core/ui/text/text.tsx @@ -2,63 +2,106 @@ import * as React from "react"; import { IStyle } from "../../model/style"; export enum CoreTextType { - header = 'header', - medium = 'medium', - large = 'large', - small = 'small', - big = 'big' + header = "header", + medium = "medium", + mediumV2 = "mediumV2", + largeV2 = "largeV2", + large = "large", + small = "small", + smallV2 = "smallV2", + smallv3 = "smallv3", + big = "big", } +export enum FontType { + ubuntu = "'Ubuntu'", + roboto = "'Roboto'", +} export interface ITextProps extends IStyle { - text: string; + text?: string; type: CoreTextType; + fontType?: FontType; color?: string; onClick?: Function; + onChange?: (text: string) => void; + contentEditable?: boolean; } const getStyle = (type: CoreTextType, color: string | undefined) => { - if (type.isEqual(CoreTextType.small)) return { - color: color ?? "rgba(73, 69, 79, 1)", - fontSize: 12, - fontFamily: "Roboto", - fontWeight: 400, - fontSizeAdjust: 14, - textOverflow: "ellipsis", - } + if (type.isEqual(CoreTextType.smallv3)) + return { + fontWeight: "400", + fontSize: "16px;", + lineHeight: "19px", + fontStyle: "normal", + }; + if (type.isEqual(CoreTextType.smallV2)) + return { + fontStyle: "normal", + fontWeight: "400", + fontSize: 14, + color: color ?? "#1D1B20", + }; + if (type.isEqual(CoreTextType.mediumV2)) + return { + color: color ?? "rgba(73, 69, 79, 1)", + fontStyle: "normal", + fontWeight: "500", + fontSize: "16px", + lineHeight: "24px", + letterSpacing: "0.15px", + }; + if (type.isEqual(CoreTextType.largeV2)) + return { + color: color ?? "rgba(73, 69, 79, 1)", - if (type.isEqual(CoreTextType.large)) return { - color: color ?? "#1D1B20", - fontSize: 16, - fontFamily: "Roboto", - fontWeight: 400, - fontSizeAdjust: 14, - textOverflow: "ellipsis", - } - if (type.isEqual(CoreTextType.medium)) return { - color: color ?? "#1D1B20", - fontSize: 14, - fontFamily: "Roboto", - fontWeight: 400, - textOverflow: "ellipsis", - fontSizeAdjust: 14, - } - if (type.isEqual(CoreTextType.header)) return { - color: color ?? "#1D1B20", - fontSize: 20, - fontFamily: "Roboto", - fontWeight: 500, - textOverflow: "ellipsis", - fontSizeAdjust: 16, - } - if (type.isEqual(CoreTextType.big)) return { - color: color ?? "#1D1B20", - fontSize: 40, - fontFamily: "Roboto", - fontWeight: 500, - textOverflow: "ellipsis", + alignContent: "center", + fontWeight: 500, + fontSize: 14, + lineHeight: "20px", + }; + if (type.isEqual(CoreTextType.small)) + return { + color: color ?? "rgba(73, 69, 79, 1)", + fontSize: 12, + fontWeight: 400, + fontSizeAdjust: 14, + textOverflow: "ellipsis", + }; - fontSizeAdjust: 16, - } + if (type.isEqual(CoreTextType.large)) + return { + color: color ?? "#1D1B20", + fontSize: 16, + fontWeight: 400, + fontSizeAdjust: 14, + textOverflow: "ellipsis", + }; + if (type.isEqual(CoreTextType.medium)) + return { + color: color ?? "#1D1B20", + fontSize: 14, + fontWeight: 400, + textOverflow: "ellipsis", + fontSizeAdjust: 14, + }; + if (type.isEqual(CoreTextType.header)) + return { + color: color ?? "#1D1B20", + fontSize: 20, + fontWeight: 500, + textOverflow: "ellipsis", + fontSizeAdjust: 16, + }; + if (type.isEqual(CoreTextType.big)) + return { + color: color ?? "#1D1B20", + fontSize: 40, + fontWeight: 500, + textOverflow: "ellipsis", + + fontSizeAdjust: 16, + }; return { color: color ?? "rgba(73, 69, 79, 1)", fontSize: 12, @@ -66,21 +109,37 @@ const getStyle = (type: CoreTextType, color: string | undefined) => { fontWeight: 400, fontSizeAdjust: 14, textOverflow: "ellipsis", + }; +}; + +const appendStyle = ( + type: CoreTextType, + color: string | undefined, + style: React.CSSProperties | undefined, + fontType: FontType | undefined +) => { + let font = "Roboto"; + if (fontType !== undefined) { + font = fontType; } -} -const appendStyle = (type: CoreTextType, color: string | undefined, style: React.CSSProperties | undefined) => { - return Object.assign(getStyle(type, color), style) -} + return Object.assign(Object.assign(getStyle(type, color), style), { fontFamily: font }); +}; export function CoreText(props: ITextProps) { return (
{ - if (props.onClick) props.onClick() + if (props.onClick) props.onClick(); + }} + style={appendStyle(props.type, props.color, props.style, props.fontType)} + onInput={(event) => { + if (props.onChange) { + // @ts-expect-error + props.onChange(event.target.innerText); + } }} - style={appendStyle(props.type, props.color, props.style)} > {props.text}
); } - \ No newline at end of file diff --git a/ui/src/features/all_projects/presentation/all_projects_screen.tsx b/ui/src/features/all_projects/presentation/all_projects_screen.tsx index 0a045a7..cadc55a 100644 --- a/ui/src/features/all_projects/presentation/all_projects_screen.tsx +++ b/ui/src/features/all_projects/presentation/all_projects_screen.tsx @@ -27,7 +27,7 @@ export const AllProjectScreen: React.FunctionComponent = observer(() => { largeText={"Проекты"} needBackButton={false} minText="Создать новый проект?" - click={() => store.showModal()} + click={() => store.modalShow()} isError={false} isLoading={store.isLoading} children={ @@ -82,7 +82,7 @@ export const AllProjectScreen: React.FunctionComponent = observer(() => { footer={null} closable={false} closeIcon={null} - onCancel={store.handleCancel} + onCancel={store.modalCancel} > store.setDescriptionToNewProject(text)} /> @@ -102,7 +102,7 @@ export const AllProjectScreen: React.FunctionComponent = observer(() => { style={{ marginRight: 10 }} filled={true} /> - store.handleCancel()} style={{ marginRight: 10 }} /> + store.modalCancel()} style={{ marginRight: 10 }} />
diff --git a/ui/src/features/behavior_tree_builder/data/behavior_tree_builder_http_repository.ts b/ui/src/features/behavior_tree_builder/data/behavior_tree_builder_http_repository.ts index 3c26629..4ca160a 100644 --- a/ui/src/features/behavior_tree_builder/data/behavior_tree_builder_http_repository.ts +++ b/ui/src/features/behavior_tree_builder/data/behavior_tree_builder_http_repository.ts @@ -1,5 +1,5 @@ import { Result } from "../../../core/helper/result"; -import { Skills } from "../../../core/model/skill_model"; +import { SkillModel, Skills } from "../../../core/model/skill_model"; import { HttpError, HttpMethod, CoreHttpRepository } from "../../../core/repository/core_http_repository"; import { BehaviorTreeModel } from "../model/behavior_tree_model"; import { BehaviorTreeViewModel } from "../model/behavior_tree_view_model"; @@ -8,12 +8,12 @@ export class BehaviorTreeBuilderHttpRepository extends CoreHttpRepository { featureApi = `/behavior/trees`; getAllBtInstances = async () => this._jsonRequest(HttpMethod.GET, this.featureApi); - getBtSkills = async (): Promise> => { - return (await this._jsonToClassInstanceRequest( + getBtSkills = async (): Promise> => { + return (await this._arrayJsonToClassInstanceRequest( HttpMethod.GET, - `${this.featureApi}/templates`, - Skills - )) as unknown as Promise>; + `/skills`, + SkillModel + )) as unknown as Promise>; }; saveNewBt = async (model: BehaviorTreeViewModel) => this._jsonRequest(HttpMethod.POST, this.featureApi, model); getBtById = async (id: string): Promise> => @@ -22,7 +22,7 @@ export class BehaviorTreeBuilderHttpRepository extends CoreHttpRepository { `${this.featureApi}/by_id?id=${id}`, BehaviorTreeModel ) as unknown as Promise>; - + deleteBt = (id: string) => this._jsonRequest(HttpMethod.DELETE, this.featureApi); editBt = async (model: BehaviorTreeModel) => { await this._jsonRequest(HttpMethod.POST, `${this.featureApi}/fill/tree`, model); return await this._jsonRequest(HttpMethod.PUT, this.featureApi, model); diff --git a/ui/src/features/behavior_tree_builder/model/behavior_tree_model.ts b/ui/src/features/behavior_tree_builder/model/behavior_tree_model.ts index 74acb25..c564362 100644 --- a/ui/src/features/behavior_tree_builder/model/behavior_tree_model.ts +++ b/ui/src/features/behavior_tree_builder/model/behavior_tree_model.ts @@ -2,11 +2,14 @@ import { Type } from "class-transformer"; import { Result } from "../../../core/helper/result"; import { Skills } from "../../../core/model/skill_model"; import { NodeBehaviorTree } from "./node_behavior_tree"; -import { IsOptional, IsString } from "class-validator"; +import { IsNotEmpty, IsNumber, IsOptional, IsString } from "class-validator"; import { BehaviorTreeBuilderHttpRepository } from "../data/behavior_tree_builder_http_repository"; import { message } from "antd"; +import { ValidationModel } from "../../../core/model/validation_model"; -export class BehaviorTreeModel { +export class BehaviorTreeModel extends ValidationModel { + @IsNumber() + unixTime: number = Date.now(); @IsString() public sceneId: string; @IsOptional() @@ -14,10 +17,16 @@ export class BehaviorTreeModel { public skills?: Skills; public scene: NodeBehaviorTree[] = []; public xml: string; + @IsString() + @IsNotEmpty() + public description: string; + @IsString() + @IsNotEmpty() public name: string; public project: string; public _id: string; constructor(skills: Skills, scene: NodeBehaviorTree[], xml: string, name: string, project: string, _id: string) { + super(); this.skills = skills; this.scene = scene; this.xml = xml; @@ -32,11 +41,9 @@ export class BehaviorTreeModel { } getSceneDependency = async (behaviorTreeBuilderHttpRepository: BehaviorTreeBuilderHttpRepository) => (await behaviorTreeBuilderHttpRepository.getSceneAsset(this.sceneId)).fold( - (s) => { - - }, + (s) => {}, (e) => { - message.error('Get Scene Dependency error') + message.error("Get Scene Dependency error"); } ); diff --git a/ui/src/features/behavior_tree_builder/model/behavior_tree_view_model.ts b/ui/src/features/behavior_tree_builder/model/behavior_tree_view_model.ts index 0960242..5504970 100644 --- a/ui/src/features/behavior_tree_builder/model/behavior_tree_view_model.ts +++ b/ui/src/features/behavior_tree_builder/model/behavior_tree_view_model.ts @@ -1,18 +1,30 @@ +import { IsNotEmpty, IsNumber, IsString } from "class-validator"; import { Result } from "../../../core/helper/result"; +import { ValidationModel } from "../../../core/model/validation_model"; -export class BehaviorTreeViewModel { - constructor(public name: string, public project: string, public sceneId: string) {} - static empty() { - return new BehaviorTreeViewModel("", "", ""); +export class BehaviorTreeViewModel extends ValidationModel { + @IsNumber() + unixTime: number = Date.now(); + @IsNotEmpty() + @IsString() + public name: string; + @IsNotEmpty() + @IsString() + public description: string; + @IsNotEmpty() + @IsString() + public project: string; + @IsNotEmpty() + @IsString() + public sceneId: string; + constructor(name: string, description: string, project: string, sceneId: string) { + super(); + this.name = name; + this.description = description; + this.project = project; + this.sceneId = sceneId; } - - valid(): Result { - if (this.project.isEmpty()) { - return Result.error("project is empty"); - } - if (this.name.isEmpty()) { - return Result.error("name is empty"); - } - return Result.ok(this); + static empty() { + return new BehaviorTreeViewModel("", "", "", ""); } } diff --git a/ui/src/features/behavior_tree_builder/model/primitive_view_model.ts b/ui/src/features/behavior_tree_builder/model/primitive_view_model.ts index cf97fc4..29fceab 100644 --- a/ui/src/features/behavior_tree_builder/model/primitive_view_model.ts +++ b/ui/src/features/behavior_tree_builder/model/primitive_view_model.ts @@ -4,76 +4,81 @@ import { ISkillView } from "../presentation/ui/skill_tree/skill_tree"; extensions(); export enum SystemPrimitive { - Sequence = 'Sequence', - Fallback = "Fallback", - ReactiveSequence = "ReactiveSequence", - SequenceWithMemory = 'SequenceWithMemory', - ReactiveFallback = "ReactiveFallback", - Decorator = "Decorator", - Inverter = "Inverter", - ForceSuccess = "ForceSuccess", - ForceFailure = "ForceFailure", - Repeat = "Repeat", - RetryUntilSuccessful = "RetryUntilSuccessful", - KeepRunningUntilFailure = "KeepRunningUntilFailure", - Delay = "Delay", - RunOnce = "RunOnce", + Sequence = "Sequence", + Fallback = "Fallback", + ReactiveSequence = "ReactiveSequence", + SequenceWithMemory = "SequenceWithMemory", + ReactiveFallback = "ReactiveFallback", + Decorator = "Decorator", + Inverter = "Inverter", + ForceSuccess = "ForceSuccess", + ForceFailure = "ForceFailure", + Repeat = "Repeat", + RetryUntilSuccessful = "RetryUntilSuccessful", + KeepRunningUntilFailure = "KeepRunningUntilFailure", + Delay = "Delay", + RunOnce = "RunOnce", } interface ISystemPrimitive { - label: string; - group: string; - css?: React.CSSProperties; - input: boolean; - output: boolean; - + label: string; + group: string; + css?: React.CSSProperties; + input: boolean; + output: boolean; } - export class PrimitiveViewModel { - primitives: ISystemPrimitive[] = [ - { - label: SystemPrimitive.Inverter, - group: SystemPrimitive.Decorator, - input: true, - output: true, - css: { - border: "1px solid #003E61", - borderRadius: 33, - background: "#BDE8AE", - }, - }, - { - label: SystemPrimitive.ForceSuccess, - group: SystemPrimitive.Decorator, - input: true, - output: true, - css: { - border: "1px solid #003E61", - borderRadius: 33, - background: "#BDE8AE", - }, - }, - { label: SystemPrimitive.ForceFailure, group: SystemPrimitive.Decorator, input: true, output: true }, - { label: SystemPrimitive.Repeat, group: SystemPrimitive.Decorator, input: true, output: true }, - { label: SystemPrimitive.RetryUntilSuccessful, group: SystemPrimitive.Decorator, input: true, output: true }, - { label: SystemPrimitive.KeepRunningUntilFailure, group: SystemPrimitive.Decorator, input: true, output: true }, - { label: SystemPrimitive.Delay, group: SystemPrimitive.Decorator, input: true, output: true }, - { label: SystemPrimitive.RunOnce, group: SystemPrimitive.Decorator, input: true, output: true }, - { label: SystemPrimitive.ReactiveSequence, group: SystemPrimitive.Sequence, input: true, output: true }, - { label: SystemPrimitive.Sequence, group: SystemPrimitive.Sequence, input: true, output: true }, - { label: SystemPrimitive.SequenceWithMemory, group: SystemPrimitive.Sequence, input: true, output: true }, - { label: SystemPrimitive.ReactiveFallback, group: SystemPrimitive.Fallback, input: true, output: true }, - { label: SystemPrimitive.Fallback, group: SystemPrimitive.Fallback, input: true, output: true }, - - ]; - getPrimitiveAtLabel = (name: string) => { - return this.primitives.find((el) => el.label.isEqual(name)); - } - toSkillView = () => this.primitives.reduce((acc, primitive) => { - acc.rFind((el) => el.name.isEqual(primitive.group)).fold(() => { - acc.at(acc.findIndex((eeee) => eeee.name === primitive.group))?.children?.push({ name: primitive.label }) - }, () => { acc.push({ name: primitive.group, children: [{ name: primitive.label }] }) }) - return acc; - }, []) - -} \ No newline at end of file + primitives: ISystemPrimitive[] = [ + { + label: SystemPrimitive.Inverter, + group: SystemPrimitive.Decorator, + input: true, + output: true, + css: { + border: "1px solid #003E61", + borderRadius: 33, + background: "#BDE8AE", + }, + }, + { + label: SystemPrimitive.ForceSuccess, + group: SystemPrimitive.Decorator, + input: true, + output: true, + css: { + border: `1px solid white`, + borderRadius: 5, + color: "white", + background: "rgba(106, 76, 147, 1)", + }, + }, + { label: SystemPrimitive.ForceFailure, group: SystemPrimitive.Decorator, input: true, output: true }, + { label: SystemPrimitive.Repeat, group: SystemPrimitive.Decorator, input: true, output: true }, + { label: SystemPrimitive.RetryUntilSuccessful, group: SystemPrimitive.Decorator, input: true, output: true }, + { label: SystemPrimitive.KeepRunningUntilFailure, group: SystemPrimitive.Decorator, input: true, output: true }, + { label: SystemPrimitive.Delay, group: SystemPrimitive.Decorator, input: true, output: true }, + { label: SystemPrimitive.RunOnce, group: SystemPrimitive.Decorator, input: true, output: true }, + { label: SystemPrimitive.ReactiveSequence, group: SystemPrimitive.Sequence, input: true, output: true }, + { label: SystemPrimitive.Sequence, group: SystemPrimitive.Sequence, input: true, output: true }, + { label: SystemPrimitive.SequenceWithMemory, group: SystemPrimitive.Sequence, input: true, output: true }, + { label: SystemPrimitive.ReactiveFallback, group: SystemPrimitive.Fallback, input: true, output: true }, + { label: SystemPrimitive.Fallback, group: SystemPrimitive.Fallback, input: true, output: true }, + ]; + getPrimitiveAtLabel = (name: string) => { + return this.primitives.find((el) => el.label.isEqual(name)); + }; + toSkillView = () => + this.primitives.reduce((acc, primitive) => { + acc + .rFind((el) => el.name.isEqual(primitive.group)) + .fold( + () => { + acc.at(acc.findIndex((element) => element.name === primitive.group))?.children?.push({ name: primitive.label }); + }, + () => { + acc.push({ name: primitive.group, children: [{ name: primitive.label }] }); + } + ); + return acc; + }, []); +} diff --git a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx index a187043..14a41c1 100644 --- a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx @@ -8,12 +8,20 @@ import { CoreButton } from "../../../core/ui/button/button"; import { observer } from "mobx-react-lite"; import { match } from "ts-pattern"; import { Icon } from "../../../core/ui/icons/icons"; -import { Drawer } from "antd"; +import { Drawer, theme } from "antd"; import { CoreInput } from "../../../core/ui/input/input"; import { CoreText, CoreTextType } from "../../../core/ui/text/text"; import { useNavigate, useParams } from "react-router-dom"; import { IForms, forms } from "./ui/forms/forms"; import { CoreSelect } from "../../../core/ui/select/select"; +import { ButtonV2, ButtonV2Type } from "../../../core/ui/button/button_v2"; +import { CoreCard } from "../../../core/ui/card/card"; +import { themeStore } from "../../.."; +import { CoreModal } from "../../../core/ui/modal/modal"; +import { InputV2 } from "../../../core/ui/input/input_v2"; +import { SelectV2 } from "../../../core/ui/select/select_v2"; +import { BehaviorTreeModel } from "../model/behavior_tree_model"; +import { MainPageV2 } from "../../../core/ui/pages/main_page_v2"; export const behaviorTreeBuilderScreenPath = "behavior/tree/screen/path"; export interface DOMReact { @@ -28,8 +36,12 @@ export interface DOMReact { } export const behaviorTreeBuilderStore = new BehaviorTreeBuilderStore(); - -export const BehaviorTreeBuilderPath = "/behavior/tree/"; +export const BehaviorTreeBuilderPath = (id?: string) => { + if (id) { + return `/behavior/tree/${id}`; + } + return `/behavior/tree/`; +}; export const BehaviorTreeBuilderScreen = observer(() => { const navigate = useNavigate(); @@ -53,101 +65,128 @@ export const BehaviorTreeBuilderScreen = observer(() => { store.dispose(); }; }, [id, navigate, ref, store]); - return ( - {match(store.type) - .with(StoreUIType.SelectBehaviorTree, () => ( + .with(StoreUIType.ViewBehaviorTree, () => ( <>
- {store.btTreeModels?.map((el, index) => ( -
- -
- BehaviorTreeBuilderPath + navigate(el._id)} +
+
+ +
- ))} - store.editDrawer(DrawerState.newBehaviorTree, true)} /> + {store.skillTemplates ? : null} +
+
+ store.onClickSaveBehaviorTree()} text="Сохранить" /> +
+
)) - .with(StoreUIType.ViewBehaviorTree, () => ( -
-
-
- -
- {store.skillTemplates ? : null} + .with(StoreUIType.SelectBehaviorTree, () => ( + <> +
+ } + text="СОЗДАТЬ ДЕРЕВО ПОВЕДЕНИЯ" + onClick={() => store.modalShow()} + /> +
+ +
+ {store.btTreeModels?.repeat(100).map((el, index) => ( + store.deleteBt(el._id)} + clickGoToIcon={() => store.goToBt(el._id)} + descriptionMiddle={el.description} + descriptionTop={el.name} + date={el.unixTime} + /> + ))}
-
- store.onClickSaveBehaviorTree()} text="Сохранить" /> -
-
+ )) .otherwise(() => ( <> ))} - - } - bodyChildren={ - <> -
-
-
- store.editDrawer(DrawerState.newBehaviorTree, false)} - open={store.drawers.find((el) => el.name === DrawerState.newBehaviorTree)?.status} - > -
-
- store.updateForm({ name: text })} /> - el.name) ?? []} - value={""} - label={"Сцена"} - onChange={(text) => - store.updateForm({ sceneId: store.scenes?.filter((el) => el.name.isEqual(text)).at(0)?._id }) - } + store.modalCancel()} + children={ + <> +
+ -
-
- store.createNewBehaviorTree()} /> -
- store.editDrawer(DrawerState.newBehaviorTree, false)} /> -
-
- +
+
+
+ store.updateForm({ name: text })} /> +
+ store.updateForm({ description: text })} /> +
+ ({ name: el.name, value: el._id })) ?? []} + initialValue={""} + label={"Сцена"} + onChange={(value: string) => store.updateForm({ sceneId: value })} + /> +
+
+
+ store.saveNewBt()} type={ButtonV2Type.default} /> +
+ store.modalCancel()} type={ButtonV2Type.default} /> +
+ + } + /> ; areaPlugin?: AreaPlugin; nodeUpdateObserver?: NodeRerenderObserver; + isModalOpen: boolean = false; primitiveViewModel: PrimitiveViewModel; skillTree: ISkillView = { name: "", @@ -75,6 +75,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState, area: AreaPlugin) => { @@ -140,14 +144,14 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState { this.activeProject = el.id; + this.viewModel.project = this.activeProject; }); (await this.behaviorTreeBuilderHttpRepository.getBtSkills()).fold( (model) => { - - this.skillTemplates = model; + this.skillTemplates = Skills.fromSkills(model); this.skillTree.children = this.skillTree.children?.map((el) => { if (el.name === "Действия") { - el.children = model.toSkillView(); + el.children = this.skillTemplates.toSkillView(); } return el; }); @@ -157,29 +161,10 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState { - const keyCode = event.keyCode || event.which; - if (keyCode === 16) { - this.shiftIsPressed = true; - } - if (keyCode === 8) { - this.deleteIsPressed = true; - } - }); - document.addEventListener("keyup", (event) => { - const keyCode = event.keyCode || event.which; - if (keyCode === 16) { - this.shiftIsPressed = false; - } - if (keyCode === 8 || keyCode === 46) { - this.deleteIsPressed = false; - } - }); } initParam = async (id?: string) => { this.isLoading = true; - this.type = StoreUIType.ViewBehaviorTree; + if (id) { (await this.behaviorTreeBuilderHttpRepository.getBtById(id)).fold( async (model) => { @@ -197,7 +182,7 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState { this.viewModel.project = this.activeProject; - this.viewModel.valid().fold( - async (model) => { - await this.messageHttp(this.behaviorTreeBuilderHttpRepository.saveNewBt(model), { - successMessage: "Новое дерево создано", - }); - this.mapOk("btTreeModels", this.behaviorTreeBuilderHttpRepository.getAllBtInstances()); - }, - async (error) => message.error(error) - ); + // this.viewModel.valid().fold( + // async (model) => { + // await this.messageHttp(this.behaviorTreeBuilderHttpRepository.saveNewBt(model), { + // successMessage: "Новое дерево создано", + // }); + // this.mapOk("btTreeModels", this.behaviorTreeBuilderHttpRepository.getAllBtInstances()); + // }, + // async (error) => message.error(error) + // ); }; setSelected = (label: string, selected: boolean, sid: string) => { - this.selectedSid = sid; + // this.selectedSid = sid; + // this.selected = label; + + // if (!Object.keys(SystemPrimitive).includes(label) && this.skillTemplates.skillHasForm(label)) { + this.editDrawer(DrawerState.editThreadBehaviorTree, true); this.selected = label; - if ( - this.shiftIsPressed && - !Object.keys(SystemPrimitive).includes(label) && - this.skillTemplates.skillHasForm(label) - ) { - this.editDrawer(DrawerState.editThreadBehaviorTree, selected); - this.selected = label; - } - if (this.deleteIsPressed) { - this.nodeBehaviorTree = this.nodeBehaviorTree.filter((el) => !el.id.isEqual(sid)); - this.filledOutTemplates.skills = this.filledOutTemplates.deleteSid(sid); - this.nodeUpdateObserver?.emit({ type: UpdateEvent.DELETE, id: sid }); - } + // } + }; + deleteBtAction = (sid: string) => { + + this.nodeBehaviorTree = this.nodeBehaviorTree.filter((el) => !el.id.isEqual(sid)); + this.filledOutTemplates.skills = this.filledOutTemplates.deleteSid(sid); + this.nodeUpdateObserver?.emit({ type: UpdateEvent.DELETE, id: sid }); }; formUpdateDependency = (dependency: Object, formType: string) => { this.filledOutTemplates?.skillBySid(this.selectedSid ?? "").fold( @@ -304,14 +287,36 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState {this.skillTemplates?.getSkillParams(label).map((el, index) => (
-
IN
-
+
{el.type} - {behaviorTreeBuilderStore.isFilledInput(el.type, sid) ? "" : *}
+
))} - {this.skillTemplates?.getSkilsOut(label).map((el, index) => ( + + {/* {this.skillTemplates?.getSkilsOut(label).map((el, index) => (
- ))} + ))} */}
); } @@ -342,10 +347,6 @@ export class BehaviorTreeBuilderStore extends UiDrawerFormState {}; + modalShow = () => { + this.isModalOpen = true; + }; + + modalCancel = () => { + this.isModalOpen = false; + }; + deleteBt = (id: string) => this.behaviorTreeBuilderHttpRepository.deleteBt(id); + saveNewBt = async () => + (await this.viewModel.valid()).fold( + async (model) => { + await this.messageHttp(this.behaviorTreeBuilderHttpRepository.saveNewBt(model), { + successMessage: "Новое дерево создано", + }); + await this.mapOk("btTreeModels", this.behaviorTreeBuilderHttpRepository.getAllBtInstances()); + this.modalCancel(); + }, + async (error) => message.error(error) + ); + goToBt = (id: string) => { + if (this.navigate) this.navigate(BehaviorTreeBuilderPath(id)); + }; } diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/background.css b/ui/src/features/behavior_tree_builder/presentation/ui/editor/background.css index 374597d..5722416 100644 --- a/ui/src/features/behavior_tree_builder/presentation/ui/editor/background.css +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/background.css @@ -10,9 +10,4 @@ .background { background: white; - /* background: url(https://sport.mail.ru/img/_main/subnav.png); - opacity: 1; - background-image:linear-gradient(black 3.2px, transparent 3.2px), linear-gradient(90deg, black 3.2px, transparent 3.2px), linear-gradient(black 1.6px, transparent 1.6px), linear-gradient(90deg, black 1.6px, #101010ed 1.6px); - background-size: 80px 80px, 80px 80px, 16px 16px, 16px 16px; - background-position: -3.2px -3.2px, -3.2px -3.2px, -1.6px -1.6px, -1.6px -1.6px; */ } diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_connection.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_connection.tsx index 7c5b642..f4770f2 100644 --- a/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_connection.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_connection.tsx @@ -15,7 +15,7 @@ const Svg = styled.svg` const Path = styled.path<{ styles?: (props: any) => any }>` fill: none; strokeWidth: 3px; - stroke: black; + stroke: white; pointer-events: auto; ${(props) => props.styles && props.styles(props)} `; diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_socket.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_socket.tsx index 4f6e8ce..22fe480 100644 --- a/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_socket.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/custom_socket.tsx @@ -5,11 +5,11 @@ import styled from "styled-components"; const Styles = styled.div` display: inline-block; cursor: pointer; - border: 1px solid rgba(50, 50, 50, 1); + border: 1px solid rgb(255 255 255); width: 10px; height: 10px; vertical-align: middle; - background: rgba(50, 50, 50, 1); + background: white; z-index: 2; box-sizing: border-box; &:hover { diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx index e25f459..72e0d2a 100644 --- a/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx @@ -14,7 +14,6 @@ import { UpdateEvent, } from "../../../model/editor_view"; import { v4 } from "uuid"; -import constructWithOptions from "styled-components/dist/constructors/constructWithOptions"; export type Schemes = GetSchemes>; export type AreaExtra = ReactArea2D; @@ -126,7 +125,7 @@ export async function createEditor(container: HTMLElement) { target: el.id, }); } - + nodeUpdateObserver.on(async (event) => { if (event.type.isEqual(UpdateEvent.UPDATE)) { areaContainer.update("node", event.id); diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/nodes/controls_node.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/nodes/controls_node.tsx index 1a89dca..d6a054c 100644 --- a/ui/src/features/behavior_tree_builder/presentation/ui/editor/nodes/controls_node.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/nodes/controls_node.tsx @@ -3,6 +3,8 @@ import { ClassicScheme, RenderEmit, Presets } from "rete-react-plugin"; import styled, { css } from "styled-components"; import { $nodewidth, $socketmargin, $socketsize } from "./vars"; import { behaviorTreeBuilderStore } from "../../../behavior_tree_builder_screen"; +import { themeStore } from "../../../../../.."; +import { Icon } from "../../../../../../core/ui/icons/icons"; const { RefSocket, RefControl } = Presets.classic; type NodeExtraData = { width?: number; height?: number }; @@ -15,16 +17,12 @@ export const NodeStyles = styled.div (Number.isFinite(props.width) ? `${props.width}px` : `${$nodewidth}px`)}; height: ${(props) => (Number.isFinite(props.height) ? `${props.height}px` : "auto")}; - + color: white; user-select: none; &:hover { background: #333; } - ${(props) => - props.selected && - css` - border-color: red; - `} + .title { color: black; font-family: sans-serif; @@ -102,17 +100,20 @@ export function SequenceNode(props: Props) sortByIndex(inputs); sortByIndex(outputs); sortByIndex(controls); - behaviorTreeBuilderStore.setSelected(label, selected, id); + const refSelect = React.useRef(null); + const refDelete = React.useRef(null); + React.useEffect(() => { + refSelect.current?.addEventListener("mouseup", () => behaviorTreeBuilderStore.setSelected(label, selected, id)); + refDelete.current?.addEventListener("mouseup", () => behaviorTreeBuilderStore.deleteBtAction(id)); + }, []); return ( - <> +
@@ -147,14 +148,25 @@ export function SequenceNode(props: Props) ) )} -
{}} - className="title" - data-testid="title" - > -
{label}
-
{behaviorTreeBuilderStore.getBodyNode(label,id)}
+
{}} data-testid="title"> +
+
{label}
+
+
+ +
+
+ +
+ behaviorTreeBuilderStore.deleteBtAction(id)} + /> +
+
+
+
{behaviorTreeBuilderStore.getBodyNode(label, id)}
{outputs.map( ([key, output]) => @@ -179,6 +191,6 @@ export function SequenceNode(props: Props) {outputs.isEmpty() ?
: null}
- +
); } diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topic_dependency_view_model.ts b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topic_dependency_view_model.ts index 9afc488..f33753b 100644 --- a/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topic_dependency_view_model.ts +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topic_dependency_view_model.ts @@ -7,7 +7,7 @@ export class TopicDependencyViewModel extends ValidationModel { @IsNotEmpty() @IsString() type: string; - mode: StoreTopicType; + mode?: StoreTopicType; constructor(type: string, mode: StoreTopicType) { super(); makeAutoObservable(this); diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form.tsx index 08fb325..e9d5c6b 100644 --- a/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/ui/forms/topics_form/topics_form.tsx @@ -15,13 +15,12 @@ export const TopicsForm = observer((props: IPropsForm { store.init(); store.loadClassInstance(TopicDependencyViewModel, props.dependency as TopicDependencyViewModel); - console.log(store.viewModel); }, [props]); return (
- {match(store.viewModel.mode) + {match(store.viewModel?.mode ?? "") .with(StoreTopicType.btExecute, () => ( <> diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree.tsx index 6d5fdfd..3018063 100644 --- a/ui/src/features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree.tsx +++ b/ui/src/features/behavior_tree_builder/presentation/ui/skill_tree/skill_tree.tsx @@ -4,10 +4,12 @@ import { IFlatMetadata } from "react-accessible-treeview/dist/TreeView/utils"; import { CallBackEventTarget } from "../../../../../core/extensions/extensions"; import "./styles.css"; import { CoreText, CoreTextType } from "../../../../../core/ui/text/text"; +import { themeStore } from "../../../../.."; export interface ISkillView { name: string; children?: ISkillView[]; + isSystem?: boolean; id?: string; interface?: string; out?: string; @@ -54,26 +56,8 @@ export const RefListener = (props: IRefListerProps) => { style={{ color: "black", alignItems: "center", height: "max-content", display: "flex" }} draggable={props.isBranch ? undefined : "true"} > - {props.isBranch ? ( - - - - ) : ( - "" - )} - + {props.isBranch ? undefined : ""} +
); diff --git a/ui/src/features/skills/skills_screen.tsx b/ui/src/features/skills/skills_screen.tsx index 33362d8..f9fbd9e 100644 --- a/ui/src/features/skills/skills_screen.tsx +++ b/ui/src/features/skills/skills_screen.tsx @@ -152,7 +152,7 @@ export const SkillsScreen = observer(() => { onClick={() => store.addNewParam(index)} /> {el.param.map((param) => ( -
store.showModal()}> +
store.modalShow()}>
{param.type}
{JSON.stringify(param.dependency)}
diff --git a/ui/src/features/skills/skills_store.ts b/ui/src/features/skills/skills_store.ts index 8b7f64c..2035c2b 100644 --- a/ui/src/features/skills/skills_store.ts +++ b/ui/src/features/skills/skills_store.ts @@ -27,7 +27,7 @@ export class SkillsStore extends UiDrawerFormState { init = async (navigate?: NavigateFunction | undefined) => { this.mapOk("skills", this.skillsHttpRepository.getAllSkills()); }; - showModal = () => { + modalShow = () => { this.isModalOpen = true; }; @@ -40,7 +40,7 @@ export class SkillsStore extends UiDrawerFormState { }; addNewParam = (index: number) => { this.activeIndex = index; - this.showModal(); + this.modalShow(); }; onChangeBtDependency = (dependency: Object) => this.viewModel.BTAction.at(this.activeIndex ?? 0) diff --git a/ui/src/index.tsx b/ui/src/index.tsx index db91325..0a5d52b 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -6,6 +6,7 @@ import { SocketListener } from "./features/socket_listener/socket_listener"; import { RouterProvider } from "react-router-dom"; import { router } from "./core/routers/routers"; import { configure } from "mobx"; +import { ThemeStore } from "./core/store/theme_store"; configure({ enforceActions: "never", @@ -14,7 +15,7 @@ configure({ extensions(); const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); - +export const themeStore = new ThemeStore(); root.render( <> @@ -22,4 +23,3 @@ root.render( ); - \ No newline at end of file