Merge pull request 'Add label list component & panel & message component' (#17) from dev-mrkbear into master
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone Build is passing

Reviewed-on: http://git.mrkbear.com/MrKBear/living-together/pulls/17
This commit is contained in:
MrKBear 2022-03-09 22:01:31 +08:00
commit edb24b27fb
22 changed files with 423 additions and 61 deletions

View File

@ -24,6 +24,13 @@ class AttrInput extends Component<IAttrInputProps> {
private value: string = ""; private value: string = "";
private error: ReactNode; private error: ReactNode;
private numberTestReg = [/\.0*$/, /[1-9]+0+$/];
private numberTester(value: string) {
return isNaN((value as any) / 1) ||
this.numberTestReg[0].test(value) ||
this.numberTestReg[1].test(value);
}
private check(value: string): ReactNode { private check(value: string): ReactNode {
@ -37,7 +44,7 @@ class AttrInput extends Component<IAttrInputProps> {
const praseNumber = (value as any) / 1; const praseNumber = (value as any) / 1;
// 数字校验 // 数字校验
if (isNaN(praseNumber) || /\.0*$/.test(value)) { if (this.numberTester(value)) {
return <Localization i18nKey="Input.Error.Not.Number" /> return <Localization i18nKey="Input.Error.Not.Number" />
} }

View File

@ -37,6 +37,7 @@ div.color-input-root {
div.color-box { div.color-box {
width: 12px; width: 12px;
height: 12px; height: 12px;
border-radius: 3px;
} }
} }

View File

@ -58,7 +58,13 @@ class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixi
} }
})} })}
{this.getRenderButton({ iconName: "StepSharedAdd", i18NKey: "Command.Bar.Add.Behavior.Info" })} {this.getRenderButton({ iconName: "StepSharedAdd", i18NKey: "Command.Bar.Add.Behavior.Info" })}
{this.getRenderButton({ iconName: "Tag", i18NKey: "Command.Bar.Add.Tag.Info" })} {this.getRenderButton({
iconName: "Tag",
i18NKey: "Command.Bar.Add.Tag.Info",
click: () => {
this.props.status ? this.props.status.newLabel() : undefined;
}
})}
{this.getRenderButton({ iconName: "Camera", i18NKey: "Command.Bar.Camera.Info" })} {this.getRenderButton({ iconName: "Camera", i18NKey: "Command.Bar.Camera.Info" })}
</div> </div>
<div> <div>

View File

@ -34,11 +34,11 @@ class Container extends Component<IContainerProps> {
/** /**
* Tab ELE * Tab ELE
*/ */
private renderPanel(panles: string[], showBar: boolean, focus?: string) { private renderPanel(panels: string[], showBar: boolean, focus?: string) {
const classList: string[] = []; const classList: string[] = [];
const theme: Themes = this.props.theme ?? Themes.dark; const theme: Themes = this.props.theme ?? Themes.dark;
const showPanelId = focus ?? panles[0]; const showPanelId = focus ?? panels[0];
const showPanelInfo = getPanelInfoById(showPanelId as any); const showPanelInfo = getPanelInfoById(showPanelId as any);
classList.push(theme === Themes.light ? "light" : "dark"); classList.push(theme === Themes.light ? "light" : "dark");
@ -46,14 +46,14 @@ class Container extends Component<IContainerProps> {
classList.push(`font-${FontLevel.Level3}`); classList.push(`font-${FontLevel.Level3}`);
classList.push("app-tab-header"); classList.push("app-tab-header");
const hasActivePanel = panles.some((id) => id === this.props.focusId); const hasActivePanel = panels.some((id) => id === this.props.focusId);
return <> return <>
{showBar ? {showBar ?
<div className={classList.join(" ")} onClick={() => { <div className={classList.join(" ")} onClick={() => {
this.props.onFocusTab ? this.props.onFocusTab("") : undefined this.props.onFocusTab ? this.props.onFocusTab("") : undefined
}}>{ }}>{
panles.map((panelId: string) => { panels.map((panelId: string) => {
const classList: string[] = ["app-tab-header-item"]; const classList: string[] = ["app-tab-header-item"];
if (panelId === this.props.focusId) classList.push("active"); if (panelId === this.props.focusId) classList.push("active");
@ -189,7 +189,7 @@ class Container extends Component<IContainerProps> {
const items: [IContainerProps, IContainerProps] | undefined = props.items; const items: [IContainerProps, IContainerProps] | undefined = props.items;
const showBar: boolean = props.showBar ?? true; const showBar: boolean = props.showBar ?? true;
const panles: string[] = props.panles ?? []; const panels: string[] = props.panels ?? [];
const layout: LayoutDirection = props.layout ?? LayoutDirection.Y; const layout: LayoutDirection = props.layout ?? LayoutDirection.Y;
const scale: number = props.scale ?? 50; const scale: number = props.scale ?? 50;
const isRoot: boolean = !!props.isRoot; const isRoot: boolean = !!props.isRoot;
@ -201,7 +201,7 @@ class Container extends Component<IContainerProps> {
classList.push(`background-${BackgroundLevel.Level4}`); classList.push(`background-${BackgroundLevel.Level4}`);
classList.push(`font-${FontLevel.normal}`); classList.push(`font-${FontLevel.normal}`);
classList.push("app-container"); classList.push("app-container");
if (panles.length > 0 && !items) classList.push("end-containe"); if (panels.length > 0 && !items) classList.push("end-containe");
return <div return <div
className={classList.join(" ")} className={classList.join(" ")}
@ -216,7 +216,7 @@ class Container extends Component<IContainerProps> {
onMouseUp={isRoot ? () => this.focusEdgeId = undefined : undefined} onMouseUp={isRoot ? () => this.focusEdgeId = undefined : undefined}
> >
{/* 渲染 Panel */} {/* 渲染 Panel */}
{panles.length > 0 && !items ? this.renderPanel(panles, showBar, focusPanel) : null} {panels.length > 0 && !items ? this.renderPanel(panels, showBar, focusPanel) : null}
{/* 渲染第一部分 */} {/* 渲染第一部分 */}
{items && items[0] ? this.renderContainer(items[0], scale, layout) : null} {items && items[0] ? this.renderContainer(items[0], scale, layout) : null}

View File

@ -0,0 +1,3 @@
div.panel-error-message {
padding-top: 5px;
}

View File

@ -0,0 +1,16 @@
import { AllI18nKeys, Localization } from "@Component/Localization/Localization";
import { FunctionComponent } from "react";
import "./ErrorMessage.scss";
interface IErrorMessageProps {
i18nKey: AllI18nKeys;
options?: Record<string, string>;
}
const ErrorMessage: FunctionComponent<IErrorMessageProps> = (props) => {
return <div className="panel-error-message">
<Localization i18nKey={props.i18nKey} options={props.options}/>
</div>
}
export { ErrorMessage };

View File

@ -0,0 +1,64 @@
@import "../Theme/Theme.scss";
div.label {
width: auto;
height: auto;
display: inline-flex;
margin: 5px 5px;
justify-content: center;
vertical-align: middle;
align-items: stretch;
border-radius: 3px;
border: .5px solid transparent;
overflow: hidden;
user-select: none;
cursor: pointer;
div.label-color {
width: 3px;
margin-right: 2px;
border-radius: 3px;
flex-shrink: 0;
}
div.label-name {
padding: 2px 3px;
text-overflow: ellipsis;
overflow: hidden;
}
div.delete-button {
padding: 2px 3px;
border-radius: 3px;
display: flex;
align-items: center;
user-select: none;
cursor: pointer;
}
}
div.dark.label {
background-color: $lt-bg-color-lvl3-dark;
div.label-color {
color: $lt-bg-color-lvl3-dark;
}
div.delete-button:hover {
color: $lt-font-color-lvl2-dark;
background-color: $lt-bg-color-lvl2-dark;
}
}
div.light.label {
background-color: $lt-bg-color-lvl3-light;
div.label-color {
color: $lt-bg-color-lvl3-light;
}
div.delete-button:hover {
color: $lt-font-color-lvl2-light;
background-color: $lt-bg-color-lvl2-light;
}
}

View File

@ -0,0 +1,56 @@
import { Component } from "react";
import { Label } from "@Model/Label";
import { Icon } from "@fluentui/react";
import { useSetting, IMixinSettingProps, Themes } from "@Context/Setting";
import "./LabelList.scss";
interface ILabelListProps {
labels: Label[];
canDelete?: boolean;
}
interface ILabelListState {
focusLabel?: Label;
}
@useSetting
class LabelList extends Component<ILabelListProps & IMixinSettingProps, ILabelListState> {
public state: Readonly<ILabelListState> = {
focusLabel: undefined
};
private renderLabel(label: Label) {
const theme = this.props.setting?.themes ?? Themes.dark;
const themeClassName = theme === Themes.dark ? "dark" : "light";
const colorCss = `rgb(${label.color.join(",")})`;
return <div className={`label ${themeClassName}`} key={label.id}>
<div className="label-color" style={{
backgroundColor: colorCss
}}/>
<div className="label-name">
{label.name}
</div>
{
this.props.canDelete ?
<div className="delete-button">
<Icon iconName="delete"></Icon>
</div> : null
}
</div>
}
public render() {
return <>
{
this.props.labels.map((label) => {
return this.renderLabel(label);
})
}
</>
}
}
export { LabelList };

View File

@ -0,0 +1,63 @@
@import "../Theme/Theme.scss";
$line-min-height: 26px;
div.toggles-input {
width: 100%;
display: flex;
min-height: $line-min-height;
padding: 5px 0;
div.toggles-intro {
width: 50%;
height: 100%;
max-width: 220px;
display: flex;
align-items: center;
padding-right: 5px;
box-sizing: border-box;
}
div.toggles-content {
width: 50%;
height: 100%;
max-width: 180px;
min-height: $line-min-height;
div.checkbox {
width: $line-min-height;
height: $line-min-height;
overflow: hidden;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
}
}
}
div.dark.toggles-input {
div.toggles-content div.checkbox {
background-color: $lt-bg-color-lvl3-dark;
color: $lt-font-color-normal-dark;
}
div.toggles-content div.checkbox:hover {
background-color: $lt-bg-color-lvl2-dark;
}
}
div.light.toggles-input {
div.toggles-content div.checkbox {
background-color: $lt-bg-color-lvl3-light;
color: $lt-font-color-normal-light;
}
div.toggles-content div.checkbox:hover {
background-color: $lt-bg-color-lvl2-light;
}
}

View File

@ -0,0 +1,45 @@
import { AllI18nKeys, Localization } from "@Component/Localization/Localization";
import { Theme } from "@Component/Theme/Theme";
import { Icon } from "@fluentui/react";
import { Component, ReactNode } from "react";
import "./TogglesInput.scss";
interface ITogglesInputProps {
keyI18n: AllI18nKeys;
infoI18n?: AllI18nKeys;
value?: boolean;
disable?: boolean;
valueChange?: (color: boolean) => any;
}
class TogglesInput extends Component<ITogglesInputProps> {
public render(): ReactNode {
return <Theme className="toggles-input">
<div className="toggles-intro">
<Localization i18nKey={this.props.keyI18n}/>
</div>
<div className="toggles-content">
<div
className="checkbox"
style={{
cursor: this.props.disable ? "not-allowed" : "pointer"
}}
onClick={(() => {
if (this.props.disable) {
return;
}
if (this.props.valueChange) {
this.props.valueChange(!this.props.value);
}
})}
>
<Icon iconName="CheckMark" style={{
display: this.props.value ? "inline-block" : "none"
}}></Icon>
</div>
</div>
</Theme>
}
}
export { TogglesInput };

View File

@ -8,12 +8,18 @@ import { ClassicRenderer, MouseMod } from "@GLRender/ClassicRenderer";
import { Setting } from "./Setting"; import { Setting } from "./Setting";
import { I18N } from "@Component/Localization/Localization"; import { I18N } from "@Component/Localization/Localization";
function randomColor() { function randomColor(unNormal: boolean = false) {
return [ const color = [
Math.random() * .8 + .2, Math.random() * .8 + .2,
Math.random() * .8 + .2, Math.random() * .8 + .2,
Math.random() * .8 + .2, 1 Math.random() * .8 + .2, 1
] ]
if (unNormal) {
color[0] = Math.round(color[0] * 255),
color[1] = Math.round(color[1] * 255),
color[2] = Math.round(color[2] * 255)
}
return color;
} }
interface IStatusEvent { interface IStatusEvent {
@ -34,6 +40,7 @@ class Status extends Emitter<IStatusEvent> {
* *
*/ */
public objectNameIndex = 1; public objectNameIndex = 1;
public labelNameIndex = 1;
/** /**
* *
@ -123,6 +130,17 @@ class Status extends Emitter<IStatusEvent> {
return range; return range;
} }
public newLabel() {
const label = this.model.addLabel(
I18N(this.setting.language, "Object.List.New.Label", {
id: this.labelNameIndex.toString()
})
);
label.color = randomColor(true);
this.labelNameIndex ++;
return label;
}
public setMouseMod(mod: MouseMod) { public setMouseMod(mod: MouseMod) {
this.mouseMod = mod; this.mouseMod = mod;
if (this.renderer instanceof ClassicRenderer) { if (this.renderer instanceof ClassicRenderer) {

View File

@ -25,15 +25,18 @@ const EN_US = {
"Input.Error.Length": "The length of the input content must be less 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.Group": "Group object {id}",
"Object.List.New.Range": "Range object {id}", "Object.List.New.Range": "Range object {id}",
"Object.List.New.Label": "Label {id}",
"Object.List.No.Data": "There are no objects in the model, click the button to create it", "Object.List.No.Data": "There are no objects in the model, click the button to create it",
"Panel.Title.Notfound": "{id}", "Panel.Title.Notfound": "{id}",
"Panel.Info.Notfound": "This panel with id {id} can not found!", "Panel.Info.Notfound": "This panel with id {id} can not found!",
"Panel.Title.Render.View": "Live preview", "Panel.Title.Render.View": "Live preview",
"Panel.Info.Render.View": "Live simulation results preview", "Panel.Info.Render.View": "Live simulation results preview",
"Panel.Title.Object.List.View": "Object list", "Panel.Title.Object.List.View": "Object list",
"Panel.Info.Object.List.View": "Edit View All Object Properties", "Panel.Info.Object.List.View": "Edit view all Object Properties",
"Panel.Title.Range.Details.View": "Range attributes", "Panel.Title.Range.Details.View": "Range attributes",
"Panel.Info.Range.Details.View": "Edit View Range attributes", "Panel.Info.Range.Details.View": "Edit view range attributes",
"Panel.Title.Label.List.View": "Label list",
"Panel.Info.Label.List.View": "Edit view label attributes",
"Common.Attr.Key.Display.Name": "Display name", "Common.Attr.Key.Display.Name": "Display name",
"Common.Attr.Key.Position.X": "Position X", "Common.Attr.Key.Position.X": "Position X",
"Common.Attr.Key.Position.Y": "Position Y", "Common.Attr.Key.Position.Y": "Position Y",
@ -42,6 +45,8 @@ const EN_US = {
"Common.Attr.Key.Radius.Y": "Radius Y", "Common.Attr.Key.Radius.Y": "Radius Y",
"Common.Attr.Key.Radius.Z": "Radius Z", "Common.Attr.Key.Radius.Z": "Radius Z",
"Common.Attr.Key.Color": "Color", "Common.Attr.Key.Color": "Color",
"Common.Attr.Key.Display": "Display",
"Common.Attr.Key.Update": "Update",
"Common.Attr.Key.Error.Multiple": "Multiple values", "Common.Attr.Key.Error.Multiple": "Multiple values",
"Panel.Info.Range.Details.Attr.Error.Not.Range": "Object is not a Range", "Panel.Info.Range.Details.Attr.Error.Not.Range": "Object is not a Range",
"Panel.Info.Range.Details.Attr.Error.Unspecified": "Unspecified range object", "Panel.Info.Range.Details.Attr.Error.Unspecified": "Unspecified range object",

View File

@ -25,8 +25,9 @@ const ZH_CN = {
"Input.Error.Length": "输入内容长度须小于 {number}", "Input.Error.Length": "输入内容长度须小于 {number}",
"Object.List.New.Group": "组对象 {id}", "Object.List.New.Group": "组对象 {id}",
"Object.List.New.Range": "范围对象 {id}", "Object.List.New.Range": "范围对象 {id}",
"Object.List.New.Label": "标签 {id}",
"Object.List.No.Data": "模型中没有任何对象,点击按钮以创建", "Object.List.No.Data": "模型中没有任何对象,点击按钮以创建",
"Panel.Title.Notfound": "找不到面板: {id}", "Panel.Title.Notfound": "{id}",
"Panel.Info.Notfound": "这个编号为 {id} 的面板无法找到!", "Panel.Info.Notfound": "这个编号为 {id} 的面板无法找到!",
"Panel.Title.Render.View": "实时预览", "Panel.Title.Render.View": "实时预览",
"Panel.Info.Render.View": "实时仿真结果预览", "Panel.Info.Render.View": "实时仿真结果预览",
@ -34,6 +35,8 @@ const ZH_CN = {
"Panel.Info.Object.List.View": "编辑查看全部对象属性", "Panel.Info.Object.List.View": "编辑查看全部对象属性",
"Panel.Title.Range.Details.View": "范围属性", "Panel.Title.Range.Details.View": "范围属性",
"Panel.Info.Range.Details.View": "编辑查看范围属性", "Panel.Info.Range.Details.View": "编辑查看范围属性",
"Panel.Title.Label.List.View": "标签列表",
"Panel.Info.Label.List.View": "编辑查看标签属性",
"Common.Attr.Key.Display.Name": "显示名称", "Common.Attr.Key.Display.Name": "显示名称",
"Common.Attr.Key.Position.X": "X 坐标", "Common.Attr.Key.Position.X": "X 坐标",
"Common.Attr.Key.Position.Y": "Y 坐标", "Common.Attr.Key.Position.Y": "Y 坐标",
@ -42,6 +45,8 @@ const ZH_CN = {
"Common.Attr.Key.Radius.Y": "Y 半径", "Common.Attr.Key.Radius.Y": "Y 半径",
"Common.Attr.Key.Radius.Z": "Z 半径", "Common.Attr.Key.Radius.Z": "Z 半径",
"Common.Attr.Key.Color": "颜色", "Common.Attr.Key.Color": "颜色",
"Common.Attr.Key.Display": "显示",
"Common.Attr.Key.Update": "更新",
"Common.Attr.Key.Error.Multiple": "多重数值", "Common.Attr.Key.Error.Multiple": "多重数值",
"Panel.Info.Range.Details.Attr.Error.Not.Range": "对象不是一个范围", "Panel.Info.Range.Details.Attr.Error.Not.Range": "对象不是一个范围",
"Panel.Info.Range.Details.Attr.Error.Unspecified": "未指定范围对象", "Panel.Info.Range.Details.Attr.Error.Unspecified": "未指定范围对象",

View File

@ -14,12 +14,12 @@ class Label {
/** /**
* *
*/ */
public name?: string; public name: string = "";
/** /**
* CSS * CSS
*/ */
public color?: string; public color: number[] = [0, 0, 0];
/** /**
* *
@ -31,10 +31,10 @@ class Label {
* @param id ID * @param id ID
* @param name * @param name
*/ */
public constructor(model:Model, id: ObjectID, name?: string) { public constructor(model: Model, id: ObjectID, name?: string) {
this.model = model; this.model = model;
this.id = id; this.id = id;
this.name = name; this.name = name ?? this.name;
} }
/** /**

View File

@ -7,7 +7,7 @@ enum LayoutDirection {
class ILayout { class ILayout {
items?: [ILayout, ILayout]; items?: [ILayout, ILayout];
panles?: string[]; panels?: string[];
focusPanel?: string; focusPanel?: string;
layout?: LayoutDirection; layout?: LayoutDirection;
scale?: number; scale?: number;
@ -51,8 +51,8 @@ class Layout extends Emitter<ILayoutEvent> {
this.id = 0; this.id = 0;
this.map((layout) => { this.map((layout) => {
layout.id = this.id; layout.id = this.id;
if (!layout.focusPanel && layout.panles && layout.panles.length > 0) { if (!layout.focusPanel && layout.panels && layout.panels.length > 0) {
layout.focusPanel = layout.panles[0] layout.focusPanel = layout.panels[0]
} }
this.id ++; this.id ++;
}); });
@ -80,10 +80,10 @@ class Layout extends Emitter<ILayoutEvent> {
} }
this.map((layout) => { this.map((layout) => {
if (layout.panles && layout.panles.length > 0) { if (layout.panels && layout.panels.length > 0) {
let index = -1; let index = -1;
for (let i = 0; i < layout.panles.length; i++) { for (let i = 0; i < layout.panels.length; i++) {
if (layout.panles[i] === panelId) { if (layout.panels[i] === panelId) {
index = i; index = i;
break; break;
} }

View File

@ -28,8 +28,8 @@ class Model extends Emitter<ModelEvent> {
* ID * ID
*/ */
private idIndex: number = 1; private idIndex: number = 1;
public get nextId(): number { public nextId(label: string = "U"): string {
return this.idIndex ++; return `${label}-${this.idIndex ++}`;
} }
/** /**
@ -55,7 +55,7 @@ class Model extends Emitter<ModelEvent> {
*/ */
public addLabel(name: string): Label { public addLabel(name: string): Label {
console.log(`Model: Creat label with id ${this.idIndex}`); console.log(`Model: Creat label with id ${this.idIndex}`);
let label = new Label(this, this.nextId, name); let label = new Label(this, this.nextId("L"), name);
this.labelPool.push(label); this.labelPool.push(label);
this.emit("labelAdd", label); this.emit("labelAdd", label);
this.emit("labelChange", this.labelPool); this.emit("labelChange", this.labelPool);
@ -95,7 +95,7 @@ class Model extends Emitter<ModelEvent> {
*/ */
public addGroup(): Group { public addGroup(): Group {
console.log(`Model: Creat group with id ${this.idIndex}`); console.log(`Model: Creat group with id ${this.idIndex}`);
let group = new Group(this, this.nextId); let group = new Group(this, this.nextId("G"));
this.objectPool.push(group); this.objectPool.push(group);
this.emit("groupAdd", group); this.emit("groupAdd", group);
this.emit("objectAdd", group); this.emit("objectAdd", group);
@ -108,7 +108,7 @@ class Model extends Emitter<ModelEvent> {
*/ */
public addRange(): Range { public addRange(): Range {
console.log(`Model: Creat range with id ${this.idIndex}`); console.log(`Model: Creat range with id ${this.idIndex}`);
let range = new Range(this, this.nextId); let range = new Range(this, this.nextId("R"));
this.objectPool.push(range); this.objectPool.push(range);
this.emit("rangeAdd", range); this.emit("rangeAdd", range);
this.emit("objectAdd", range); this.emit("objectAdd", range);

View File

@ -35,7 +35,7 @@ interface ICommonParam {
/** /**
* *
*/ */
type ObjectID = Symbol | string | number; type ObjectID = string;
/** /**
* *

View File

@ -51,6 +51,8 @@ class SimulatorWeb extends Component {
individual.position[2] = (Math.random() - .5) * 2; individual.position[2] = (Math.random() - .5) * 2;
}) })
this.status.model.update(0); this.status.model.update(0);
this.status.newLabel().name = "New Label";
this.status.newLabel().name = "Test Label 01";
} }
(window as any).s = this; (window as any).s = this;
@ -61,9 +63,9 @@ class SimulatorWeb extends Component {
items: [ items: [
{ {
items: [ items: [
{panles: ["RenderView", "Label Aa Bb", "Label aaa"]}, {panels: ["RenderView", "Label Aa Bb", "Label aaa"]},
{ {
items: [{panles: ["Label b", "Label bbb"]}, {panles: ["C"]}], items: [{panels: ["Label b", "Label bbb"]}, {panels: ["LabelList"]}],
scale: 80, scale: 80,
layout: LayoutDirection.X layout: LayoutDirection.X
} }
@ -73,9 +75,9 @@ class SimulatorWeb extends Component {
}, },
{ {
items: [{ items: [{
panles: ["ObjectList", "Test tab"] panels: ["ObjectList", "Test tab"]
}, { }, {
panles: ["RangeDetails", "Label e"] panels: ["RangeDetails", "Label e"]
}], }],
layout: LayoutDirection.Y layout: LayoutDirection.Y
} }

View File

@ -0,0 +1,33 @@
@import "../../Component/Theme/Theme.scss";
div.label-list-command-bar {
width: 100%;
height: 30px;
flex-shrink: 0;
display: flex;
div.command-item {
width: 30px;
height: 100%;
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
user-select: none;
cursor: pointer;
}
}
div.dark.label-list-command-bar {
div.command-item:hover {
background-color: $lt-bg-color-lvl3-dark;
}
}
div.light.label-list-command-bar {
div.command-item:hover {
background-color: $lt-bg-color-lvl3-light;
}
}

View File

@ -0,0 +1,24 @@
import { Theme } from "@Component/Theme/Theme";
import { LabelList as LabelListComponent } from "@Component/LabelList/LabelList";
import { Component } from "react";
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status";
import { Label } from "@Model/Label";
import "./LabelList.scss";
interface ILabelListProps {
}
@useStatusWithEvent("labelChange")
class LabelList extends Component<ILabelListProps & IMixinStatusProps> {
public render() {
let labels: Label[] = [];
if (this.props.status) {
labels = this.props.status.model.labelPool.concat([]);
}
return <LabelListComponent labels={labels} canDelete/>
}
}
export { LabelList };

View File

@ -1,10 +1,11 @@
import { ReactNode, Component, FunctionComponent } from "react"; import { ReactNode, Component, FunctionComponent } from "react";
import { Theme } from "@Component/Theme/Theme"; import { Theme } from "@Component/Theme/Theme";
import { Localization } from "@Component/Localization/Localization"; import { ErrorMessage } from "@Component/ErrorMessage/ErrorMessage";
import { RenderView } from "./RenderView/RenderView"; import { RenderView } from "./RenderView/RenderView";
import { ObjectList } from "./ObjectList/ObjectList"; import { ObjectList } from "./ObjectList/ObjectList";
import { ObjectCommand } from "./ObjectList/ObjectCommand"; import { ObjectCommand } from "./ObjectList/ObjectCommand";
import { RangeDetails } from "./RangeDetails/RangeDetails"; import { RangeDetails } from "./RangeDetails/RangeDetails";
import { LabelList } from "./LabelList/LabelList";
interface IPanelInfo { interface IPanelInfo {
nameKey: string; nameKey: string;
@ -21,6 +22,7 @@ type PanelId = ""
| "RenderView" // 主渲染器 | "RenderView" // 主渲染器
| "ObjectList" // 对象列表 | "ObjectList" // 对象列表
| "RangeDetails" // 范围属性 | "RangeDetails" // 范围属性
| "LabelList" // 标签列表
; ;
const PanelInfoMap = new Map<PanelId, IPanelInfo>(); const PanelInfoMap = new Map<PanelId, IPanelInfo>();
@ -36,6 +38,10 @@ PanelInfoMap.set("RangeDetails", {
nameKey: "Panel.Title.Range.Details.View", introKay: "Panel.Info.Range.Details.View", nameKey: "Panel.Title.Range.Details.View", introKay: "Panel.Info.Range.Details.View",
class: RangeDetails class: RangeDetails
}) })
PanelInfoMap.set("LabelList", {
nameKey: "Panel.Title.Label.List.View", introKay: "Panel.Info.Label.List.View",
class: LabelList
})
function getPanelById(panelId: PanelId): ReactNode { function getPanelById(panelId: PanelId): ReactNode {
switch (panelId) { switch (panelId) {
@ -45,9 +51,7 @@ function getPanelById(panelId: PanelId): ReactNode {
const C = info.class; const C = info.class;
return <C></C> return <C></C>
} else return <Theme> } else return <Theme>
<Localization i18nKey={"Panel.Info.Notfound"} options={{ <ErrorMessage i18nKey={"Panel.Info.Notfound"} options={{ id: panelId }}/>
id: panelId
}}/>
</Theme> </Theme>
} }
} }

View File

@ -1,10 +1,12 @@
import { Component, ReactNode } from "react"; import { Component, ReactNode } from "react";
import { AttrInput } from "@Component/AttrInput/AttrInput"; import { AttrInput } from "@Component/AttrInput/AttrInput";
import { useStatusWithEvent, IMixinStatusProps, Status } from "@Context/Status"; import { useStatusWithEvent, IMixinStatusProps, Status } from "@Context/Status";
import { AllI18nKeys } from "@Component/Localization/Localization"; import { AllI18nKeys, Localization } from "@Component/Localization/Localization";
import { ErrorMessage } from "@Component/ErrorMessage/ErrorMessage";
import { Range } from "@Model/Range"; import { Range } from "@Model/Range";
import { ObjectID } from "@Model/Renderer"; import { ObjectID } from "@Model/Renderer";
import { ColorInput } from "@Component/ColorInput/ColorInput"; import { ColorInput } from "@Component/ColorInput/ColorInput";
import { TogglesInput } from "@Component/TogglesInput/TogglesInput";
import "./RangeDetails.scss"; import "./RangeDetails.scss";
@useStatusWithEvent("rangeAttrChange", "focusObjectChange") @useStatusWithEvent("rangeAttrChange", "focusObjectChange")
@ -12,6 +14,8 @@ class RangeDetails extends Component<IMixinStatusProps> {
public readonly AttrI18nKey: AllI18nKeys[] = [ public readonly AttrI18nKey: AllI18nKeys[] = [
"Common.Attr.Key.Display.Name", "Common.Attr.Key.Display.Name",
"Common.Attr.Key.Display",
"Common.Attr.Key.Update",
"Common.Attr.Key.Color", "Common.Attr.Key.Color",
"Common.Attr.Key.Position.X", "Common.Attr.Key.Position.X",
"Common.Attr.Key.Position.Y", "Common.Attr.Key.Position.Y",
@ -21,14 +25,6 @@ class RangeDetails extends Component<IMixinStatusProps> {
"Common.Attr.Key.Radius.Z" "Common.Attr.Key.Radius.Z"
] ]
private renderErrorFrom(error: AllI18nKeys) {
return <>
{this.AttrI18nKey.map((key, index) => {
return <AttrInput key={index} keyI18n={key} disable disableI18n={error}/>
})}
</>
}
private renderAttrInput( private renderAttrInput(
id: ObjectID, key: number, val: string | number | undefined, id: ObjectID, key: number, val: string | number | undefined,
change: (val: string, status: Status) => any, change: (val: string, status: Status) => any,
@ -55,40 +51,54 @@ class RangeDetails extends Component<IMixinStatusProps> {
} }
private renderFrom(range: Range) { private renderFrom(range: Range) {
// console.log(range);
let keyIndex = 0;
return <> return <>
{this.renderAttrInput(range.id, 0, range.displayName, (val, status) => { {this.renderAttrInput(range.id, keyIndex ++, range.displayName, (val, status) => {
status.changeRangeAttrib(range.id, "displayName", val); status.changeRangeAttrib(range.id, "displayName", val);
})} })}
<ColorInput keyI18n="Common.Attr.Key.Color" value={range.color} normal valueChange={(color) => { <TogglesInput keyI18n={this.AttrI18nKey[keyIndex ++]} value={range.display} valueChange={(val) => {
if (this.props.status) {
this.props.status.changeRangeAttrib(range.id, "display", val);
}
}}/>
<TogglesInput keyI18n={this.AttrI18nKey[keyIndex ++]} value={range.update} valueChange={(val) => {
if (this.props.status) {
this.props.status.changeRangeAttrib(range.id, "update", val);
}
}}/>
<ColorInput keyI18n={this.AttrI18nKey[keyIndex ++]} value={range.color} normal valueChange={(color) => {
if (this.props.status) { if (this.props.status) {
this.props.status.changeRangeAttrib(range.id, "color", color); this.props.status.changeRangeAttrib(range.id, "color", color);
} }
}}/> }}/>
{this.renderAttrInput(range.id, 2, range.position[0], (val, status) => { {this.renderAttrInput(range.id, keyIndex ++, range.position[0], (val, status) => {
range.position[0] = (val as any) / 1; range.position[0] = (val as any) / 1;
status.changeRangeAttrib(range.id, "position", range.position); status.changeRangeAttrib(range.id, "position", range.position);
}, .1)} }, .1)}
{this.renderAttrInput(range.id, 3, range.position[1], (val, status) => { {this.renderAttrInput(range.id, keyIndex ++, range.position[1], (val, status) => {
range.position[1] = (val as any) / 1; range.position[1] = (val as any) / 1;
status.changeRangeAttrib(range.id, "position", range.position); status.changeRangeAttrib(range.id, "position", range.position);
}, .1)} }, .1)}
{this.renderAttrInput(range.id, 4, range.position[2], (val, status) => { {this.renderAttrInput(range.id, keyIndex ++, range.position[2], (val, status) => {
range.position[2] = (val as any) / 1; range.position[2] = (val as any) / 1;
status.changeRangeAttrib(range.id, "position", range.position); status.changeRangeAttrib(range.id, "position", range.position);
}, .1)} }, .1)}
{this.renderAttrInput(range.id, 5, range.radius[0], (val, status) => { {this.renderAttrInput(range.id, keyIndex ++, range.radius[0], (val, status) => {
range.radius[0] = (val as any) / 1; range.radius[0] = (val as any) / 1;
status.changeRangeAttrib(range.id, "radius", range.radius); status.changeRangeAttrib(range.id, "radius", range.radius);
}, .1, undefined, 0)} }, .1, undefined, 0)}
{this.renderAttrInput(range.id, 6, range.radius[1], (val, status) => { {this.renderAttrInput(range.id, keyIndex ++, range.radius[1], (val, status) => {
range.radius[1] = (val as any) / 1; range.radius[1] = (val as any) / 1;
status.changeRangeAttrib(range.id, "radius", range.radius); status.changeRangeAttrib(range.id, "radius", range.radius);
}, .1, undefined, 0)} }, .1, undefined, 0)}
{this.renderAttrInput(range.id, 7, range.radius[2], (val, status) => { {this.renderAttrInput(range.id, keyIndex ++, range.radius[2], (val, status) => {
range.radius[2] = (val as any) / 1; range.radius[2] = (val as any) / 1;
status.changeRangeAttrib(range.id, "radius", range.radius); status.changeRangeAttrib(range.id, "radius", range.radius);
}, .1, undefined, 0)} }, .1, undefined, 0)}
@ -98,12 +108,12 @@ class RangeDetails extends Component<IMixinStatusProps> {
public render(): ReactNode { public render(): ReactNode {
if (this.props.status) { if (this.props.status) {
if (this.props.status.focusObject.size <= 0) { if (this.props.status.focusObject.size <= 0) {
return this.renderErrorFrom("Panel.Info.Range.Details.Attr.Error.Unspecified"); return <ErrorMessage i18nKey="Panel.Info.Range.Details.Attr.Error.Unspecified"/>;
} }
if (this.props.status.focusObject.size > 1) { if (this.props.status.focusObject.size > 1) {
return this.renderErrorFrom("Common.Attr.Key.Error.Multiple"); return <ErrorMessage i18nKey="Common.Attr.Key.Error.Multiple"/>;
} }
let id: ObjectID = 0; let id: ObjectID = "";
this.props.status.focusObject.forEach((cid => id = cid)); this.props.status.focusObject.forEach((cid => id = cid));
let range = this.props.status!.model.getObjectById(id); let range = this.props.status!.model.getObjectById(id);
@ -111,10 +121,10 @@ class RangeDetails extends Component<IMixinStatusProps> {
if (range instanceof Range) { if (range instanceof Range) {
return this.renderFrom(range); return this.renderFrom(range);
} else { } else {
return this.renderErrorFrom("Panel.Info.Range.Details.Attr.Error.Not.Range"); return <ErrorMessage i18nKey="Panel.Info.Range.Details.Attr.Error.Not.Range"/>;
} }
} }
return this.renderErrorFrom("Panel.Info.Range.Details.Attr.Error.Unspecified"); return <ErrorMessage i18nKey="Panel.Info.Range.Details.Attr.Error.Unspecified"/>;
} }
} }