diff --git a/source/Component/AttrInput/AttrInput.scss b/source/Component/AttrInput/AttrInput.scss new file mode 100644 index 0000000..727a96e --- /dev/null +++ b/source/Component/AttrInput/AttrInput.scss @@ -0,0 +1,103 @@ +@import "../Theme/Theme.scss"; + +$line-min-height: 24px; +$root-min-height: 26px; + +div.attr-input { + width: 100%; + display: flex; + min-height: $line-min-height; + padding: 5px 0; + + div.input-intro { + width: 50%; + height: 100%; + max-width: 220px; + display: flex; + align-items: center; + padding-right: 5px; + box-sizing: border-box; + } + + div.root-content { + width: 50%; + height: 100%; + max-width: 180px; + min-height: $root-min-height; + + div.input-content { + box-sizing: border-box; + border: 1px solid transparent; + border-radius: 3px; + overflow: hidden; + display: flex; + justify-content: space-between; + align-items: center; + min-height: $root-min-height; + + input { + width: 100%; + height: 100%; + border: none; + outline:none; + min-height: $line-min-height; + }; + + input:focus { + border: none; + } + + div.button-left, div.button-right { + min-height: $line-min-height; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + vertical-align: middle; + cursor: pointer; + user-select: none; + padding: 0 3px; + } + } + + div.input-content.error { + border: 1px solid $lt-red; + } + + div.input-content.focus { + border: 1px solid $lt-blue; + } + + div.err-message { + color: $lt-red; + padding-top: 5px; + min-height: $line-min-height; + } + } +} + +div.dark.attr-input { + + div.input-content, + div.input-content input { + background-color: $lt-bg-color-lvl3-dark; + color: $lt-font-color-normal-dark; + } + + div.button-left:hover, div.button-right:hover { + background-color: $lt-bg-color-lvl2-dark; + } +} + +div.light.attr-input { + + div.input-content, + div.input-content input { + background-color: $lt-bg-color-lvl3-light; + color: $lt-font-color-normal-light; + } + + div.button-left:hover, div.button-right:hover { + background-color: $lt-bg-color-lvl2-light; + } +} \ No newline at end of file diff --git a/source/Component/AttrInput/AttrInput.tsx b/source/Component/AttrInput/AttrInput.tsx new file mode 100644 index 0000000..d798072 --- /dev/null +++ b/source/Component/AttrInput/AttrInput.tsx @@ -0,0 +1,173 @@ +import { Component, ReactNode } from "react"; +import { FontLevel, Theme } from "@Component/Theme/Theme"; +import "./AttrInput.scss"; +import { Icon } from "@fluentui/react"; +import { Localization, AllI18nKeys } from "@Component/Localization/Localization"; +import { ObjectID } from "@Model/Renderer"; + +interface IAttrInputProps { + id?: ObjectID; + keyI18n: AllI18nKeys; + infoI18n?: AllI18nKeys; + value?: number | string; + isNumber?: boolean; + maxLength?: number; + max?: number; + min?: number; + step?: number; + disable?: boolean; + disableI18n?: AllI18nKeys; + valueChange?: (value: this["isNumber"] extends true ? number : string) => any; +} + +class AttrInput extends Component { + + private value: string = ""; + private error: ReactNode; + + private check(value: string): ReactNode { + + // 长度校验 + const maxLength = this.props.maxLength ?? 32; + if (value.length > maxLength) { + return + } + + if (this.props.isNumber) { + const praseNumber = (value as any) / 1; + + // 数字校验 + if (isNaN(praseNumber) || /\.0*$/.test(value)) { + return + } + + // 最大值校验 + if (this.props.max !== undefined && praseNumber > this.props.max) { + return + } + + // 最小值校验 + if (this.props.min !== undefined && praseNumber < this.props.min) { + return + } + + } + return undefined; + } + + private handelValueChange = () => { + if (!this.error && this.props.valueChange) { + if (this.props.isNumber) { + let numberVal = (this.value as any) * 10000; + this.value = (Math.round(numberVal) / 10000).toString(); + } + this.props.valueChange(this.value); + } + this.forceUpdate(); + } + + private changeValue = (direction: number) => { + if (this.error) { + return; + } else { + let newVal = (this.value as any / 1) + (this.props.step ?? 1) * direction; + + // 最大值校验 + if (this.props.max !== undefined && newVal > this.props.max) { + newVal = this.props.max; + } + + // 最小值校验 + if (this.props.min !== undefined && newVal < this.props.min) { + newVal = this.props.min; + } + + this.value = newVal.toString(); + this.handelValueChange() + } + } + + private renderInput() { + return <> +
+ { + this.props.isNumber ?
this.changeValue(-1)} + > + +
: null + } + { + this.value = e.target.value; + this.error = this.check(e.target.value); + this.handelValueChange(); + }} + > + { + this.props.isNumber ?
this.changeValue(1)} + > + +
: null + } +
+ { + this.error ? +
+ {this.error} +
: null + } + + } + + public shouldComponentUpdate(nextProps: IAttrInputProps) { + + // ID 都为空时更新 + if (!nextProps.id && !this.props.id) { + this.updateValueFromProps(nextProps.value); + } + + // ID 变换时更新 State 到最新的 Props + if (nextProps.id !== this.props.id) { + this.updateValueFromProps(nextProps.value); + } + return true; + } + + private updateValueFromProps(val: IAttrInputProps["value"]) { + const value = val ?? (this.props.isNumber ? "0" : ""); + this.value = value.toString(); + this.error = this.check(value.toString()); + } + + public render(): ReactNode { + + return +
+ +
+
+ { + this.props.disable ? + this.props.disableI18n ? + : +
{this.props.value}
: + this.renderInput() + } +
+
+ } +} + +export { AttrInput }; \ No newline at end of file diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index ff4d5f7..98e6a14 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -2,7 +2,7 @@ import { BackgroundLevel, Theme } from "@Component/Theme/Theme"; import { DirectionalHint, IconButton } from "@fluentui/react"; import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost"; import { useSetting, IMixinSettingProps } from "@Context/Setting"; -import { useStatus, IMixinStatusProps } from "@Context/Status"; +import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { AllI18nKeys } from "../Localization/Localization"; import { Component, ReactNode } from "react"; import { MouseMod } from "@GLRender/ClassicRenderer"; @@ -12,26 +12,10 @@ interface ICommandBarProps { width: number; } -@useStatus @useSetting +@useStatusWithEvent("mouseModChange") class CommandBar extends Component { - private handelChange = () => { - this.forceUpdate(); - } - - componentDidMount() { - if (this.props.status) { - this.props.status.on("mouseModChange", this.handelChange) - } - } - - componentWillUnmount() { - if (this.props.status) { - this.props.status.off("mouseModChange", this.handelChange) - } - } - render(): ReactNode { const mouseMod = this.props.status?.mouseMod ?? MouseMod.Drag; @@ -62,12 +46,16 @@ class CommandBar extends Component this.props.status ? this.props.status.newGroup() : undefined + click: () => { + this.props.status ? this.props.status.newGroup() : undefined; + } })} {this.getRenderButton({ iconName: "CubeShape", i18NKey: "Command.Bar.Add.Range.Info", - click: () => this.props.status ? this.props.status.newRange() : undefined + click: () => { + this.props.status ? this.props.status.newRange() : undefined; + } })} {this.getRenderButton({ iconName: "StepSharedAdd", i18NKey: "Command.Bar.Add.Behavior.Info" })} {this.getRenderButton({ iconName: "Tag", i18NKey: "Command.Bar.Add.Tag.Info" })} diff --git a/source/Component/Container/Container.scss b/source/Component/Container/Container.scss index 99442d6..a2d4287 100644 --- a/source/Component/Container/Container.scss +++ b/source/Component/Container/Container.scss @@ -27,7 +27,7 @@ div.app-container { } div:hover { - background-color: blue; + background-color: $lt-blue; } } @@ -83,7 +83,7 @@ div.app-container { } div.app-tab-header-item.active { - border: .8px solid blue; + border: .8px solid $lt-blue; transition: none; } @@ -141,7 +141,7 @@ div.app-container { } div.app-panel-root.active { - border: .8px solid blue !important; + border: .8px solid $lt-blue !important; } } diff --git a/source/Component/DetailsList/DetailsList.scss b/source/Component/DetailsList/DetailsList.scss index c48ebe9..c9a6d31 100644 --- a/source/Component/DetailsList/DetailsList.scss +++ b/source/Component/DetailsList/DetailsList.scss @@ -11,7 +11,7 @@ div.details-list { min-height: 30px; div.details-list-value { - padding: 5px 10px; + padding: 5px 5px; display: flex; justify-content: center; align-items: center; @@ -19,6 +19,7 @@ div.details-list { div.details-list-checkbox { display: flex; + flex-shrink: 0; width: 30px; align-items: center; justify-content: center; diff --git a/source/Component/HeaderBar/HeaderBar.tsx b/source/Component/HeaderBar/HeaderBar.tsx index 9453c85..5b51313 100644 --- a/source/Component/HeaderBar/HeaderBar.tsx +++ b/source/Component/HeaderBar/HeaderBar.tsx @@ -62,8 +62,8 @@ class HeaderBar extends Component< } if (status) { status.archive.on("save", this.changeListener); - status.model.on("loop", this.physicsFpsCalc); - status.renderer.on("loop", this.renderFpsCalc); + status.on("physicsLoop", this.physicsFpsCalc); + status.on("renderLoop", this.renderFpsCalc); } } @@ -74,8 +74,8 @@ class HeaderBar extends Component< } if (status) { status.archive.off("save", this.changeListener); - status.model.off("loop", this.physicsFpsCalc); - status.renderer.off("loop", this.renderFpsCalc); + status.off("physicsLoop", this.physicsFpsCalc); + status.off("renderLoop", this.renderFpsCalc); } } diff --git a/source/Component/Theme/Theme.scss b/source/Component/Theme/Theme.scss index 3f24727..8069a6d 100644 --- a/source/Component/Theme/Theme.scss +++ b/source/Component/Theme/Theme.scss @@ -1,5 +1,8 @@ @import "@fluentui/react/dist/sass/References"; +$lt-blue: rgb(81, 79, 235); +$lt-red: rgb(240, 94, 94); + $lt-font-size-normal: 13px; $lt-font-size-lvl3: $ms-font-size-16; $lt-font-size-lvl2: $ms-font-size-18; diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index cea98b7..81cbdcc 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -1,8 +1,8 @@ -import { createContext, Component, FunctionComponent } from "react"; +import { createContext, Component, FunctionComponent, useState, useEffect, ReactNode } from "react"; import { Emitter } from "@Model/Emitter"; import { Model, ObjectID } from "@Model/Model"; +import { Range } from "@Model/Range"; import { Archive } from "@Model/Archive"; -import { CtrlObject } from "@Model/CtrlObject"; import { AbstractRenderer } from "@Model/Renderer"; import { ClassicRenderer, MouseMod } from "@GLRender/ClassicRenderer"; import { Setting } from "./Setting"; @@ -16,10 +16,17 @@ function randomColor() { ] } -class Status extends Emitter<{ - mouseModChange: MouseMod, - focusObjectChange: Set -}> { +interface IStatusEvent { + renderLoop: number; + physicsLoop: number; + mouseModChange: void; + focusObjectChange: void; + objectChange: void; + labelChange: void; + rangeAttrChange: void; +} + +class Status extends Emitter { public setting: Setting = undefined as any; @@ -48,12 +55,47 @@ class Status extends Emitter<{ */ public focusObject: Set = new Set(); + public constructor() { + super(); + + // 循环事件 + this.model.on("loop", (t) => { this.emit("physicsLoop", t) }); + + // 对象变化事件 + this.model.on("objectChange", () => this.emit("objectChange")); + this.model.on("labelChange", () => this.emit("labelChange")); + + // 对象变换时执行渲染,更新渲染器数据 + this.on("objectChange", () => { + this.model.draw(); + }) + } + + public bindRenderer(renderer: AbstractRenderer) { + this.renderer = renderer; + this.renderer.on("loop", (t) => { this.emit("renderLoop", t) }); + this.model.bindRenderer(this.renderer); + } + /** * 更新焦点对象 */ public setFocusObject(focusObject: Set) { this.focusObject = focusObject; - this.emit("focusObjectChange", this.focusObject); + this.emit("focusObjectChange"); + } + + /** + * 修改范围属性 + */ + public changeRangeAttrib + (id: ObjectID, key: K, val: Range[K]) { + const range = this.model.getObjectById(id); + if (range && range instanceof Range) { + range[key] = val; + this.emit("rangeAttrChange"); + this.model.draw(); + } } /** @@ -87,7 +129,7 @@ class Status extends Emitter<{ this.renderer.mouseMod = mod; this.renderer.setMouseIcon(); } - this.emit("mouseModChange", mod); + this.emit("mouseModChange"); } } @@ -116,7 +158,55 @@ function useStatus(components: R): R { }) as any; } +function useStatusWithEvent(...events: Array) { + return (components: R): R => { + const C = components as any; + return class extends Component { + + private status: Status | undefined; + private isEventMount: boolean = false; + + private handelChange = () => { + this.forceUpdate(); + } + + private mountEvent() { + if (this.status && !this.isEventMount) { + this.isEventMount = true; + console.log("event mount"); + for (let i = 0; i < events.length; i++) { + this.status.on(events[i], this.handelChange); + } + } + } + + private unmountEvent() { + if (this.status) { + for (let i = 0; i < events.length; i++) { + this.status.off(events[i], this.handelChange); + } + } + } + + public render(): ReactNode { + return + {(status: Status) => { + this.status = status; + this.mountEvent(); + return ; + }} + + } + + public componentWillUnmount() { + this.unmountEvent(); + } + + } as any; + } +} + export { - Status, StatusContext, useStatus, + Status, StatusContext, useStatus, useStatusWithEvent, IMixinStatusProps, StatusProvider, StatusConsumer }; \ No newline at end of file diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index 1ec0799..6211a6d 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -19,6 +19,10 @@ const EN_US = { "Command.Bar.Add.Tag.Info": "Add label object", "Command.Bar.Camera.Info": "Renderer settings", "Command.Bar.Setting.Info": "Global Settings", + "Input.Error.Not.Number": "Please key in numbers", + "Input.Error.Max": "Enter value must be less than {num}", + "Input.Error.Min": "Enter value must be greater than {num}", + "Input.Error.Length": "The length of the input content must be less than {num}", "Object.List.New.Group": "Group object {id}", "Object.List.New.Range": "Range object {id}", "Object.List.No.Data": "There are no objects in the model, click the button to create it", @@ -28,6 +32,14 @@ const EN_US = { "Panel.Info.Render.View": "Live simulation results preview", "Panel.Title.Object.List.View": "Object list", "Panel.Info.Object.List.View": "Edit View All Object Properties", - + "Panel.Title.Range.Details.View": "Range attributes", + "Panel.Info.Range.Details.View": "Edit View Range attributes", + "Common.Attr.Key.Display.Name": "Display name", + "Common.Attr.Key.Position.X": "Position X", + "Common.Attr.Key.Position.Y": "Position Y", + "Common.Attr.Key.Position.Z": "Position Z", + "Common.Attr.Key.Error.Multiple": "Cannot edit multiple values", + "Panel.Info.Range.Details.Attr.Error.Not.Range": "The focus object is not a Range", + "Panel.Info.Range.Details.Attr.Error.Unspecified": "Unspecified range object", } export default EN_US; \ No newline at end of file diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index c9ef69d..bed66b3 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -19,6 +19,10 @@ const ZH_CN = { "Command.Bar.Add.Tag.Info": "添加标签对象", "Command.Bar.Camera.Info": "渲染器设置", "Command.Bar.Setting.Info": "全局设置", + "Input.Error.Not.Number": "请输入数字", + "Input.Error.Max": "输入数值须小于 {number}", + "Input.Error.Min": "输入数值须大于 {number}", + "Input.Error.Length": "输入内容长度须小于 {number}", "Object.List.New.Group": "组对象 {id}", "Object.List.New.Range": "范围对象 {id}", "Object.List.No.Data": "模型中没有任何对象,点击按钮以创建", @@ -28,5 +32,14 @@ const ZH_CN = { "Panel.Info.Render.View": "实时仿真结果预览", "Panel.Title.Object.List.View": "对象列表", "Panel.Info.Object.List.View": "编辑查看全部对象属性", + "Panel.Title.Range.Details.View": "范围属性", + "Panel.Info.Range.Details.View": "编辑查看范围属性", + "Common.Attr.Key.Display.Name": "显示名称", + "Common.Attr.Key.Position.X": "X 坐标", + "Common.Attr.Key.Position.Y": "Y 坐标", + "Common.Attr.Key.Position.Z": "Z 坐标", + "Common.Attr.Key.Error.Multiple": "无法编辑多重数值", + "Panel.Info.Range.Details.Attr.Error.Not.Range": "焦点对象不是一个范围", + "Panel.Info.Range.Details.Attr.Error.Unspecified": "未指定范围对象", } export default ZH_CN; \ No newline at end of file diff --git a/source/Model/Layout.ts b/source/Model/Layout.ts index 72002a0..4c14eba 100644 --- a/source/Model/Layout.ts +++ b/source/Model/Layout.ts @@ -74,7 +74,7 @@ class Layout extends Emitter { } public focus = (panelId: string) => { - if (panelId === "") { + if (panelId === "" && this.focusId !== "") { this.focusId = panelId; this.emit("switchTab", this); } @@ -89,10 +89,14 @@ class Layout extends Emitter { } } if (index >= 0) { - layout.focusPanel = panelId; - this.focusId = panelId; - this.emit("switchTab", this); - return true; + if (layout.focusPanel === panelId && this.focusId === panelId) { + return true; + } else { + layout.focusPanel = panelId; + this.focusId = panelId; + this.emit("switchTab", this); + return true; + } } } }) diff --git a/source/Model/Model.ts b/source/Model/Model.ts index 95fd3c8..cda7ccb 100644 --- a/source/Model/Model.ts +++ b/source/Model/Model.ts @@ -39,7 +39,7 @@ class Model extends Emitter { public getObjectById(id: ObjectID): CtrlObject | undefined { for (let i = 0; i < this.objectPool.length; i++) { - if (this.objectPool[i].id === id) { + if (this.objectPool[i].id.toString() === id.toString()) { return this.objectPool[i]; } } diff --git a/source/Model/Range.ts b/source/Model/Range.ts index 6177d68..5e7c1f3 100644 --- a/source/Model/Range.ts +++ b/source/Model/Range.ts @@ -8,7 +8,7 @@ class Range extends CtrlObject { /** * 坐标 */ - public position: number[] = []; + public position: number[] = [0, 0, 0]; /** * 半径 diff --git a/source/Page/SimulatorWeb/SimulatorWeb.tsx b/source/Page/SimulatorWeb/SimulatorWeb.tsx index 3c14aa1..ebc510e 100644 --- a/source/Page/SimulatorWeb/SimulatorWeb.tsx +++ b/source/Page/SimulatorWeb/SimulatorWeb.tsx @@ -35,7 +35,7 @@ class SimulatorWeb extends Component { // TODO: 这里要读取存档 this.status = new Status(); this.status.renderer = new ClassicRenderer({ className: "canvas" }).onLoad(); - this.status.model.bindRenderer(this.status.renderer); + this.status.bindRenderer(this.status.renderer); this.status.setting = this.setting; // 测试代码 @@ -75,8 +75,7 @@ class SimulatorWeb extends Component { items: [{ panles: ["ObjectList", "Test tab"] }, { - items: [{panles: ["Label e", "ee"]}, {panles: ["F"]}], - layout: LayoutDirection.Y + panles: ["RangeDetails", "Label e"] }], layout: LayoutDirection.Y } diff --git a/source/Panel/ObjectList/ObjectCommand.tsx b/source/Panel/ObjectList/ObjectCommand.tsx index 5af857b..71cbe42 100644 --- a/source/Panel/ObjectList/ObjectCommand.tsx +++ b/source/Panel/ObjectList/ObjectCommand.tsx @@ -41,7 +41,6 @@ class ObjectCommand extends Component { className="command-item" onClick={() => { this.props.status ? this.props.status.newGroup() : undefined; - this.props.status ? this.props.status.model.draw() : undefined; }} > @@ -50,7 +49,6 @@ class ObjectCommand extends Component { className="command-item" onClick={() => { this.props.status ? this.props.status.newRange() : undefined; - this.props.status ? this.props.status.model.draw() : undefined; }} > @@ -65,7 +63,6 @@ class ObjectCommand extends Component { }) this.props.status.model.deleteObject(deleteId); this.props.status.setFocusObject(new Set()); - this.props.status.model.draw(); } }} > diff --git a/source/Panel/ObjectList/ObjectList.scss b/source/Panel/ObjectList/ObjectList.scss index c82464d..e66398b 100644 --- a/source/Panel/ObjectList/ObjectList.scss +++ b/source/Panel/ObjectList/ObjectList.scss @@ -22,6 +22,7 @@ div.object-list-command-bar { width: 30px; height: 100%; display: flex; + flex-shrink: 0; justify-content: center; align-items: center; user-select: none; diff --git a/source/Panel/ObjectList/ObjectList.tsx b/source/Panel/ObjectList/ObjectList.tsx index cff972f..fbbee28 100644 --- a/source/Panel/ObjectList/ObjectList.tsx +++ b/source/Panel/ObjectList/ObjectList.tsx @@ -1,33 +1,15 @@ import { Component, ReactNode } from "react"; import { DetailsList } from "@Component/DetailsList/DetailsList"; -import { useStatus, IMixinStatusProps } from "@Context/Status"; +import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { useSetting, IMixinSettingProps } from "@Context/Setting"; import { Localization } from "@Component/Localization/Localization"; import { ObjectID } from "@Model/Renderer"; import "./ObjectList.scss"; @useSetting -@useStatus +@useStatusWithEvent("objectChange", "focusObjectChange", "rangeAttrChange") class ObjectList extends Component { - private handelChange = () => { - this.forceUpdate(); - } - - public componentDidMount(){ - if (this.props.status) { - this.props.status.model.on("objectChange", this.handelChange); - this.props.status.on("focusObjectChange", this.handelChange); - } - } - - public componentWillUnmount(){ - if (this.props.status) { - this.props.status.model.off("objectChange", this.handelChange); - this.props.status.off("focusObjectChange", this.handelChange); - } - } - private renderList() { const objList = this.props.status?.model.objectPool ?? []; diff --git a/source/Panel/Panel.tsx b/source/Panel/Panel.tsx index 47a684e..66b5c66 100644 --- a/source/Panel/Panel.tsx +++ b/source/Panel/Panel.tsx @@ -4,6 +4,7 @@ import { Localization } from "@Component/Localization/Localization"; import { RenderView } from "./RenderView/RenderView"; import { ObjectList } from "./ObjectList/ObjectList"; import { ObjectCommand } from "./ObjectList/ObjectCommand"; +import { RangeDetails } from "./RangeDetails/RangeDetails"; interface IPanelInfo { nameKey: string; @@ -19,6 +20,7 @@ interface IPanelInfo { type PanelId = "" | "RenderView" // 主渲染器 | "ObjectList" // 对象列表 +| "RangeDetails" // 范围属性 ; const PanelInfoMap = new Map(); @@ -30,6 +32,10 @@ PanelInfoMap.set("ObjectList", { nameKey: "Panel.Title.Object.List.View", introKay: "Panel.Info.Object.List.View", class: ObjectList, header: ObjectCommand, hidePadding: true }) +PanelInfoMap.set("RangeDetails", { + nameKey: "Panel.Title.Range.Details.View", introKay: "Panel.Info.Range.Details.View", + class: RangeDetails +}) function getPanelById(panelId: PanelId): ReactNode { switch (panelId) { diff --git a/source/Panel/RangeDetails/RangeDetails.scss b/source/Panel/RangeDetails/RangeDetails.scss new file mode 100644 index 0000000..e69de29 diff --git a/source/Panel/RangeDetails/RangeDetails.tsx b/source/Panel/RangeDetails/RangeDetails.tsx new file mode 100644 index 0000000..21c944d --- /dev/null +++ b/source/Panel/RangeDetails/RangeDetails.tsx @@ -0,0 +1,96 @@ +import { Component, ReactNode } from "react"; +import { AttrInput } from "@Component/AttrInput/AttrInput"; +import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; +import { AllI18nKeys } from "@Component/Localization/Localization"; +import { Range } from "@Model/Range"; +import { ObjectID } from "@Model/Renderer"; +import "./RangeDetails.scss"; + +@useStatusWithEvent("rangeAttrChange", "focusObjectChange") +class RangeDetails extends Component { + + private renderErrorFrom(error: AllI18nKeys) { + return <> + + + + + + } + + private renderFrom(range: Range) { + return <> + { + this.props.status ? this.props.status.changeRangeAttrib(range.id, "displayName", e) : null; + }} + /> + { + if (this.props.status) { + range.position[0] = (e as any) / 1; + this.props.status.changeRangeAttrib(range.id, "position", range.position); + } + }} + /> + { + if (this.props.status) { + range.position[1] = (e as any) / 1; + this.props.status.changeRangeAttrib(range.id, "position", range.position); + } + }} + /> + { + if (this.props.status) { + range.position[2] = (e as any) / 1; + this.props.status.changeRangeAttrib(range.id, "position", range.position); + } + }} + /> + + } + + public render(): ReactNode { + if (this.props.status) { + if (this.props.status.focusObject.size <= 0) { + return this.renderErrorFrom("Panel.Info.Range.Details.Attr.Error.Unspecified"); + } + if (this.props.status.focusObject.size > 1) { + return this.renderErrorFrom("Common.Attr.Key.Error.Multiple"); + } + let id: ObjectID = 0; + this.props.status.focusObject.forEach((cid => id = cid)); + + let range = this.props.status!.model.getObjectById(id); + + if (range instanceof Range) { + return this.renderFrom(range); + } else { + return this.renderErrorFrom("Panel.Info.Range.Details.Attr.Error.Not.Range"); + } + } + return this.renderErrorFrom("Panel.Info.Range.Details.Attr.Error.Unspecified"); + } +} + +export { RangeDetails }; \ No newline at end of file