diff --git a/source/Component/AttrInput/AttrInput.scss b/source/Component/AttrInput/AttrInput.scss index a680181..e16f206 100644 --- a/source/Component/AttrInput/AttrInput.scss +++ b/source/Component/AttrInput/AttrInput.scss @@ -1,120 +1,59 @@ @import "../Theme/Theme.scss"; -$line-min-height: 26px; +$line-min-height: 24px; -div.attr-input { - width: 100%; - display: flex; - min-height: $line-min-height; - padding: 5px 0; +div.attr-input-root { + display: flex; + justify-content: space-between; + align-items: center; - div.input-intro { - width: 50%; - height: 100%; - min-height: $line-min-height; - max-width: 220px; - display: flex; - align-items: center; - padding-right: 5px; - box-sizing: border-box; - } - - div.root-content { - width: 50%; + input { + display: block; + width: 100%; height: 100%; - max-width: 180px; + border: none; + outline: none; + background-color: transparent; min-height: $line-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; - height: $line-min-height; - - input { - width: 100%; - height: 100%; - border: none; - outline:none; - }; - - 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; - } + input:focus { + border: none; + background-color: transparent; + } - div.input-content.focus { - border: 1px solid $lt-blue; - } - - div.err-message { - color: $lt-red; - padding-top: 5px; - min-height: $line-min-height; - } - - div.error-view { - border-radius: 3px; - overflow: hidden; - display: flex; - align-items: center; - height: $line-min-height; - text-overflow: ellipsis; - word-wrap: none; - word-break: keep-all; - white-space: nowrap; - cursor:not-allowed; - - span { - padding-left: 8px; - } - } + 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.dark.attr-input { +div.dark.text-field-root { - div.input-content, div.error-view, - div.input-content input { + input { background-color: $lt-bg-color-lvl3-dark; color: $lt-font-color-normal-dark; } - div.error-view:hover, div.button-left:hover, div.button-right:hover { background-color: $lt-bg-color-lvl2-dark; } } -div.light.attr-input { +div.light.text-field-root { - div.input-content, div.error-view, - div.input-content input { + input { background-color: $lt-bg-color-lvl3-light; color: $lt-font-color-normal-light; } - div.error-view:hover, div.button-left:hover, div.button-right:hover { background-color: $lt-bg-color-lvl2-light; } diff --git a/source/Component/AttrInput/AttrInput.tsx b/source/Component/AttrInput/AttrInput.tsx index 7b9941a..eff6791 100644 --- a/source/Component/AttrInput/AttrInput.tsx +++ b/source/Component/AttrInput/AttrInput.tsx @@ -1,14 +1,12 @@ 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"; +import { TextField, ITextFieldProps } from "../TextField/TextField"; +import "./AttrInput.scss"; -interface IAttrInputProps { +interface IAttrInputProps extends ITextFieldProps { id?: ObjectID; - keyI18n: AllI18nKeys; - infoI18n?: AllI18nKeys; value?: number | string; isNumber?: boolean; maxLength?: number; @@ -16,15 +14,14 @@ interface IAttrInputProps { 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 error?: AllI18nKeys; + private errorOption?: Record; private numberTestReg = [/\.0*$/, /\.\d*[1-9]+0+$/]; private numberTester(value: string) { @@ -33,17 +30,21 @@ class AttrInput extends Component { this.numberTestReg[1].test(value); } - private check(value: string): ReactNode { + private check(value: string): AllI18nKeys | undefined { // 长度校验 const maxLength = this.props.maxLength ?? 32; if (value.length > maxLength) { - return + this.error = "Input.Error.Length"; + this.errorOption = { num: maxLength.toString() }; + return this.error; } const minLength = this.props.minLength ?? 1; if (value.length < minLength) { - return + this.error = "Input.Error.Length.Less"; + this.errorOption = { num: minLength.toString() }; + return this.error; } if (this.props.isNumber) { @@ -51,17 +52,22 @@ class AttrInput extends Component { // 数字校验 if (this.numberTester(value)) { - return + this.error = "Input.Error.Not.Number"; + return this.error; } // 最大值校验 if (this.props.max !== undefined && praseNumber > this.props.max) { - return + this.error = "Input.Error.Max"; + this.errorOption = { num: this.props.max.toString() }; + return this.error; } // 最小值校验 if (this.props.min !== undefined && praseNumber < this.props.min) { - return + this.error = "Input.Error.Min"; + this.errorOption = { num: this.props.min.toString() }; + return this.error; } } @@ -102,41 +108,33 @@ class AttrInput extends Component { 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 + { + 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 } } @@ -166,33 +164,19 @@ class AttrInput extends Component { this.error = this.check(value.toString()); } - private renderErrorInput() { - return
- { - this.props.disableI18n ? - : - {this.props.value} - } -
- } - public render(): ReactNode { - return -
- -
-
- { - this.props.disable ? - this.renderErrorInput() : - this.renderInput() - } -
-
+ { + this.renderInput() + } + ; } } diff --git a/source/Component/ColorInput/ColorInput.scss b/source/Component/ColorInput/ColorInput.scss index 99193d3..b85eea1 100644 --- a/source/Component/ColorInput/ColorInput.scss +++ b/source/Component/ColorInput/ColorInput.scss @@ -1,89 +1,32 @@ -@import "../Theme/Theme.scss"; +$line-min-height: 24px; -$line-min-height: 26px; +div.color-input { -div.color-input-root { - width: 100%; - display: flex; - min-height: $line-min-height; - padding: 5px 0; - - div.input-intro { - width: 50%; - height: 100%; - max-width: 220px; - min-height: $line-min-height; - display: flex; - align-items: center; - padding-right: 5px; - box-sizing: border-box; - } - - div.root-content { - width: 50%; - height: 100%; - max-width: 180px; - min-height: $line-min-height; - border-radius: 3px; - overflow: hidden; + div.color-view { + width: $line-min-height; + max-width: $line-min-height; display: flex; + justify-content: center; + align-items: center; - div.color-view { - width: $line-min-height; - max-width: $line-min-height; - display: flex; - justify-content: center; - align-items: center; - - div.color-box { - width: 12px; - height: 12px; - border-radius: 3px; - } - } - - div.value-view { - width: 100%; - height: 100%; - min-height: $line-min-height; - display: flex; - align-items: center; - - div.text-box { - padding-left: 1px; - } - } - - div.error-box { - display: flex; - align-items: center; - padding-left: 8px; + div.color-box { + width: 12px; + height: 12px; + border-radius: 3px; } } -} -div.dark.color-input-root { + div.value-view { + width: 100%; + height: 100%; + min-height: $line-min-height; + display: flex; + align-items: center; - div.root-content { - background-color: $lt-bg-color-lvl3-dark; - color: $lt-font-color-normal-dark; - } - - div.root-content:hover { - background-color: $lt-bg-color-lvl2-dark; - } -} - -div.light.color-input-root { - - div.root-content { - background-color: $lt-bg-color-lvl3-light; - color: $lt-font-color-normal-light; - } - - div.root-content:hover { - background-color: $lt-bg-color-lvl2-light; - } + div.text-box { + padding-left: 1px; + } + } } div.color-picker-root { diff --git a/source/Component/ColorInput/ColorInput.tsx b/source/Component/ColorInput/ColorInput.tsx index febf0fb..8328d04 100644 --- a/source/Component/ColorInput/ColorInput.tsx +++ b/source/Component/ColorInput/ColorInput.tsx @@ -1,16 +1,11 @@ import { Component, createRef, ReactNode } from "react"; -import { FontLevel, Theme } from "@Component/Theme/Theme"; +import { TextField, ITextFieldProps } from "@Component/TextField/TextField"; import { Callout, ColorPicker, DirectionalHint } from "@fluentui/react"; -import { AllI18nKeys, Localization } from "@Component/Localization/Localization"; import "./ColorInput.scss"; -interface IColorInputProps { - keyI18n: AllI18nKeys; - infoI18n?: AllI18nKeys; +interface IColorInputProps extends ITextFieldProps { value?: number[]; normal?: boolean; - disable?: boolean; - disableI18n?: AllI18nKeys; valueChange?: (color: number[]) => any; } @@ -65,16 +60,6 @@ class ColorInput extends Component { } - private renderErrorInput() { - return
- { - this.props.disableI18n ? - : - {this.props.value} - } -
; - } - private renderColorInput() { return <>
@@ -104,29 +89,21 @@ class ColorInput extends Component { public render(): ReactNode { return <> - -
- -
-
{ - this.setState({ - isPickerVisible: !this.props.disable - }) - }} - > - { this.props.disable ? null : this.renderColorInput() } - { this.props.disable ? this.renderErrorInput() : null } -
-
- + { + this.setState({ + isPickerVisible: !this.props.disableI18n + }) + }} + > + { this.renderColorInput() } + {this.state.isPickerVisible ? this.renderPicker(): null} - + ; } } diff --git a/source/Component/ComboInput/ComboInput.scss b/source/Component/ComboInput/ComboInput.scss index a073e7e..3bc1d8c 100644 --- a/source/Component/ComboInput/ComboInput.scss +++ b/source/Component/ComboInput/ComboInput.scss @@ -1,87 +1,38 @@ @import "../Theme/Theme.scss"; -$line-min-height: 26px; +$line-min-height: 24px; -div.combo-input-root { - width: 100%; - display: flex; - min-height: $line-min-height; - padding: 5px 0; - - div.input-intro { - width: 50%; - height: 100%; - max-width: 220px; - min-height: $line-min-height; - display: flex; - align-items: center; - padding-right: 5px; - box-sizing: border-box; - } - - div.root-content { - width: 50%; +div.combo-input { + + div.value-view { + width: 100%; height: 100%; - max-width: 180px; min-height: $line-min-height; - border-radius: 3px; - overflow: hidden; + max-width: calc( 100% - 32px ); display: flex; - cursor: pointer; + align-items: center; + padding-left: 8px; + white-space: nowrap; + word-break: keep-all; + text-overflow: ellipsis; + overflow: hidden; - div.value-view { - width: 100%; - height: 100%; - min-height: $line-min-height; - display: flex; - align-items: center; - padding-left: 8px; - white-space: nowrap; + span { word-break: keep-all; - text-overflow: ellipsis; overflow: hidden; - - span { - display: block; - } - } - - div.list-button { - width: $line-min-height; - height: $line-min-height; - flex-shrink: 0; - display: flex; - justify-content: center; - align-items: center; + white-space: nowrap; + text-overflow: ellipsis; + display: block; + max-width: 100%; } } -} -div.dark.combo-input-root { - - div.root-content { - background-color: $lt-bg-color-lvl3-dark; - color: $lt-font-color-normal-dark; - } - - div.root-content:hover { - background-color: $lt-bg-color-lvl2-dark; - } -} - -div.light.combo-input-root { - - div.root-content { - background-color: $lt-bg-color-lvl3-light; - color: $lt-font-color-normal-light; - } - - div.root-content:hover { - background-color: $lt-bg-color-lvl2-light; - } -} - -div.combo-picker-root { - width: 300px; - height: 340px; + div.list-button { + width: $line-min-height; + height: $line-min-height; + flex-shrink: 0; + display: flex; + justify-content: center; + align-items: center; + } } \ No newline at end of file diff --git a/source/Component/ComboInput/ComboInput.tsx b/source/Component/ComboInput/ComboInput.tsx index 55e2c27..baeb5e2 100644 --- a/source/Component/ComboInput/ComboInput.tsx +++ b/source/Component/ComboInput/ComboInput.tsx @@ -1,13 +1,10 @@ import { Component, createRef, ReactNode } from "react"; -import { FontLevel, Theme } from "@Component/Theme/Theme"; import { PickerList, IDisplayItem } from "../PickerList/PickerList"; -import { AllI18nKeys, Localization } from "@Component/Localization/Localization"; +import { TextField, ITextFieldProps } from "../TextField/TextField"; import { Icon } from "@fluentui/react"; +import { Localization } from "@Component/Localization/Localization"; import "./ComboInput.scss"; - -interface IComboInputProps { - keyI18n: AllI18nKeys; - infoI18n?: AllI18nKeys; +interface IComboInputProps extends ITextFieldProps { allOption?: IDisplayItem[]; value?: IDisplayItem; valueChange?: (value: IDisplayItem) => any; @@ -53,31 +50,28 @@ class ComboInput extends Component { public render(): ReactNode { return <> - -
- + { + this.setState({ + isPickerVisible: true + }) + }} + > +
+ { + this.props.value ? + : + null + }
-
{ - this.setState({ - isPickerVisible: true - }) - }} - > -
- { - this.props.value ? - : - null - } -
-
- -
+
+
- + {this.state.isPickerVisible ? this.renderPicker(): null} diff --git a/source/Component/LabelPicker/LabelPicker.scss b/source/Component/LabelPicker/LabelPicker.scss index a8a7220..6071264 100644 --- a/source/Component/LabelPicker/LabelPicker.scss +++ b/source/Component/LabelPicker/LabelPicker.scss @@ -2,31 +2,6 @@ $line-min-height: 26px; -div.label-picker-root { - width: 100%; - display: flex; - flex-wrap: wrap; - min-height: $line-min-height; - padding: 5px 0; - - div.input-intro { - width: 50%; - height: 100%; - min-height: $line-min-height; - 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: $line-min-height; - border-radius: 3px; - overflow: hidden; - display: flex; - } +div.label-picker { + min-height: $line-min-height; } \ No newline at end of file diff --git a/source/Component/LabelPicker/LabelPicker.tsx b/source/Component/LabelPicker/LabelPicker.tsx index 94e2d82..8b238ec 100644 --- a/source/Component/LabelPicker/LabelPicker.tsx +++ b/source/Component/LabelPicker/LabelPicker.tsx @@ -1,14 +1,12 @@ -import { AllI18nKeys, Localization } from "@Component/Localization/Localization"; import { PickerList } from "../PickerList/PickerList"; import { Label } from "@Model/Label"; +import { TextField, ITextFieldProps } from "../TextField/TextField"; import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { Component, ReactNode, createRef } from "react"; import { LabelList } from "../LabelList/LabelList"; import "./LabelPicker.scss" -interface ILabelPickerProps { - keyI18n: AllI18nKeys; - infoI18n?: AllI18nKeys; +interface ILabelPickerProps extends ITextFieldProps { labels: Label[]; labelAdd?: (label: Label) => any; labelDelete?: (label: Label) => any; @@ -47,47 +45,50 @@ class LabelPicker extends Component { + this.setState({ + isPickerVisible: false + }); + }} + clickObjectItems={(label) => { + if (label instanceof Label && this.props.labelAdd) { + this.props.labelAdd(label) + } + this.setState({ + isPickerVisible: false + }); + }} + target={this.addButtonRef} + />; + } + public render(): ReactNode { - return
-
- -
-
- { - this.props.labelDelete ? this.props.labelDelete(label) : 0; - }} - addLabel={() => { - this.setState({ - isPickerVisible: true - }); - }} - /> - {this.state.isPickerVisible ? { - this.setState({ - isPickerVisible: false - }); - }} - clickObjectItems={(label) => { - if (label instanceof Label && this.props.labelAdd) { - this.props.labelAdd(label) - } - this.setState({ - isPickerVisible: false - }); - }} - target={this.addButtonRef} - /> : null} -
-
+ return + { + this.props.labelDelete ? this.props.labelDelete(label) : 0; + }} + addLabel={() => { + this.setState({ + isPickerVisible: true + }); + }} + /> + {this.state.isPickerVisible ? this.renderPicker(): null} + ; } } diff --git a/source/Component/ObjectPicker/ObjectPicker.scss b/source/Component/ObjectPicker/ObjectPicker.scss new file mode 100644 index 0000000..c2ae567 --- /dev/null +++ b/source/Component/ObjectPicker/ObjectPicker.scss @@ -0,0 +1,57 @@ +@import "../Theme/Theme.scss"; + +$line-min-height: 24px; + +div.object-picker { + + div.list-color { + width: 3px; + height: $line-min-height; + border-radius: 1000px; + flex-shrink: 0; + display: flex; + justify-content: center; + align-items: center; + } + + div.value-view { + flex-shrink: 1; + width: 100%; + height: 100%; + max-width: calc( 100% - 51px ); + min-height: $line-min-height; + display: flex; + align-items: center; + + span { + word-break: keep-all; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: block; + max-width: 100%; + } + } + + div.list-button { + width: $line-min-height; + height: $line-min-height; + flex-shrink: 0; + display: flex; + justify-content: center; + align-items: center; + } + + div.list-empty { + width: $line-min-height; + height: $line-min-height; + flex-shrink: 0; + display: flex; + justify-content: center; + align-items: center; + } + + div.list-empty:hover { + color: $lt-red; + } +} \ No newline at end of file diff --git a/source/Component/ObjectPicker/ObjectPicker.tsx b/source/Component/ObjectPicker/ObjectPicker.tsx index 63f649f..6da97ae 100644 --- a/source/Component/ObjectPicker/ObjectPicker.tsx +++ b/source/Component/ObjectPicker/ObjectPicker.tsx @@ -1,12 +1,169 @@ -import { Component, ReactNode } from "react"; +import { Component, createRef, ReactNode } from "react"; +import { Label } from "@Model/Label"; +import { Group } from "@Model/Group"; +import { Range } from "@Model/Range"; +import { TextField, ITextFieldProps } from "../TextField/TextField"; +import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; +import { PickerList, IDisplayItem, getObjectDisplayInfo, IDisplayInfo } from "../PickerList/PickerList"; +import { Localization } from "@Component/Localization/Localization"; +import { Icon } from "@fluentui/react"; +import { CtrlObject } from "@Model/CtrlObject"; +import "./ObjectPicker.scss"; -interface IObjectPickerProps {} +type IObjectType = Label | Group | Range | CtrlObject; -class ObjectPicker extends Component { - - public render(): ReactNode { - return
- } +interface IObjectPickerProps extends ITextFieldProps { + type: Array<"L" | "G" | "R">; + value?: IObjectType; + valueChange?: (value: IObjectType) => any; + cleanValue?: () => any; } -export { ObjectPicker } \ No newline at end of file +interface IObjectPickerState { + isPickerVisible: boolean; +} + +@useStatusWithEvent("objectChange", "labelChange") +class ObjectPicker extends Component { + + private getAllOption() { + let option: Array = []; + if (this.props.status) { + + for (let i = 0; i < this.props.type.length; i++) { + + if (this.props.type[i] === "L") { + for (let j = 0; j < this.props.status.model.labelPool.length; j++) { + option.push(this.props.status.model.labelPool[j]); + } + } + + if (this.props.type[i] === "R") { + for (let j = 0; j < this.props.status.model.objectPool.length; j++) { + if (this.props.status.model.objectPool[j] instanceof Range) { + option.push(this.props.status.model.objectPool[j]); + } + } + } + + if (this.props.type[i] === "G") { + for (let j = 0; j < this.props.status.model.objectPool.length; j++) { + if (this.props.status.model.objectPool[j] instanceof Group) { + option.push(this.props.status.model.objectPool[j]); + } + } + } + } + } + return option; + } + + private isClean: boolean = false; + + public constructor(props: IObjectPickerProps) { + super(props); + this.state = { + isPickerVisible: false + } + } + + private pickerTarget = createRef(); + + private renderPicker() { + return { + if (this.props.valueChange) { + this.props.valueChange(item); + } + this.setState({ + isPickerVisible: false + }) + })} + dismiss={() => { + this.setState({ + isPickerVisible: false + }) + }} + /> + } + + public render(): ReactNode { + + let disPlayInfo: IDisplayInfo; + let isDelete: boolean = false; + + if (this.props.value) { + disPlayInfo = getObjectDisplayInfo(this.props.value); + isDelete = this.props.value.isDeleted(); + } else { + disPlayInfo = { + name: "", + icon: "Label", + color: "rgba(0,0,0,0)" + } + } + + return <> + { + if (this.isClean) { + this.isClean = false; + } else { + this.setState({ + isPickerVisible: true + }) + } + }} + > +
+
+ +
+
+ { + disPlayInfo.name ? + {disPlayInfo.name} : + + } +
+
{ + if (this.props.cleanValue && disPlayInfo.name) { + this.isClean = true; + this.props.cleanValue(); + } + }} + > + { + disPlayInfo.name ? + : + null + } +
+ + + {this.state.isPickerVisible ? this.renderPicker(): null} + + } +} + +export { ObjectPicker, IDisplayItem }; \ No newline at end of file diff --git a/source/Component/PickerList/PickerList.scss b/source/Component/PickerList/PickerList.scss index 260e3f0..c961877 100644 --- a/source/Component/PickerList/PickerList.scss +++ b/source/Component/PickerList/PickerList.scss @@ -1,12 +1,13 @@ div.picker-list-root { - max-width: 200px; + min-width: 200px; height: 100%; padding: 0px; margin: 0px; overflow-y: auto; div.picker-list-item { - width: 200px; + min-width: 200px; + padding-right: 10px; height: 36px; display: flex; justify-content: center; @@ -37,6 +38,7 @@ div.picker-list-root { div.list-item-name { width: 100%; + white-space: nowrap; box-sizing: border-box; } } diff --git a/source/Component/PickerList/PickerList.tsx b/source/Component/PickerList/PickerList.tsx index 7ed5d71..85fd526 100644 --- a/source/Component/PickerList/PickerList.tsx +++ b/source/Component/PickerList/PickerList.tsx @@ -7,13 +7,45 @@ import { Range } from "@Model/Range"; import { Component, ReactNode, RefObject } from "react"; import "./PickerList.scss"; -type IPickerListItem = CtrlObject | Label; +type IDisplayInfo = Record<"color" | "icon" | "name", string>; +type IPickerListItem = CtrlObject | Label | Range | Group; type IDisplayItem = { nameKey: AllI18nKeys; key: string; mark?: boolean; } +function getObjectDisplayInfo(item: IPickerListItem): IDisplayInfo { + + let color: number[] = []; + let icon: string = "tag"; + let name: string = ""; + + if (item instanceof Range) { + icon = "CubeShape" + } + if (item instanceof Group) { + icon = "WebAppBuilderFragment" + } + if (item instanceof CtrlObject) { + color[0] = Math.round(item.color[0] * 255); + color[1] = Math.round(item.color[1] * 255); + color[2] = Math.round(item.color[2] * 255); + name = item.displayName; + } + if (item instanceof Label) { + icon = "tag"; + color = item.color.concat([]); + name = item.name; + } + + return { + color: `rgb(${color[0]},${color[1]},${color[2]})`, + icon: icon, + name: name + } +} + interface IPickerListProps { displayItems?: IDisplayItem[]; objectList?: IPickerListItem[]; @@ -27,28 +59,7 @@ interface IPickerListProps { class PickerList extends Component { private renderItem(item: IPickerListItem) { - - let color: number[] = []; - let icon: string = "tag"; - let name: string = ""; - - if (item instanceof Range) { - icon = "CubeShape" - } - if (item instanceof Group) { - icon = "WebAppBuilderFragment" - } - if (item instanceof CtrlObject) { - color[0] = Math.round(item.color[0] * 255); - color[1] = Math.round(item.color[1] * 255); - color[2] = Math.round(item.color[2] * 255); - name = item.displayName; - } - if (item instanceof Label) { - icon = "tag"; - color = item.color.concat([]); - name = item.name; - } + const displayInfo = getObjectDisplayInfo(item); return
{ >
- +
- {name} + {displayInfo.name}
; } @@ -124,4 +135,4 @@ class PickerList extends Component { } } -export { PickerList, IDisplayItem } \ No newline at end of file +export { PickerList, IDisplayItem, IDisplayInfo, getObjectDisplayInfo } \ No newline at end of file diff --git a/source/Component/TextField/TextField.scss b/source/Component/TextField/TextField.scss new file mode 100644 index 0000000..0f5cde7 --- /dev/null +++ b/source/Component/TextField/TextField.scss @@ -0,0 +1,84 @@ +@import "../Theme/Theme.scss"; + +$line-min-height: 26px; + +div.text-field-root { + min-height: $line-min-height; + width: 100%; + display: flex; + padding: 5px 0; + + div.text-field-intro { + min-height: $line-min-height; + width: 50%; + height: 100%; + max-width: 220px; + display: flex; + align-items: center; + padding-right: 5px; + box-sizing: border-box; + } + + div.text-field-container { + min-height: $line-min-height; + width: 50%; + height: 100%; + max-width: 180px; + + div.text-field-content { + min-height: $line-min-height; + width: 100%; + height: 100%; + } + + div.text-field-content-styled { + box-sizing: border-box; + border: 1px solid transparent; + border-radius: 3px; + overflow: hidden; + display: flex; + } + + div.text-field-content-disable { + align-items: center; + + span { + display: block; + padding-left: 8px; + } + } + + div.text-field-content-error { + border: 1px solid $lt-red; + } + + div.text-field-error-message { + padding-top: 5px; + color: $lt-red; + } + } +} + +div.dark.text-field-root { + + div.text-field-content-styled { + background-color: $lt-bg-color-lvl3-dark; + color: $lt-font-color-normal-dark; + } + + div.text-field-content-hover-styled:hover { + background-color: $lt-bg-color-lvl2-dark; + } +} + +div.light.text-field-root { + + div.text-field-content-styled { + background-color: $lt-bg-color-lvl3-light; + color: $lt-font-color-normal-light; + } + + div.text-field-content-hover-styled:hover { + background-color: $lt-bg-color-lvl2-light; + } +} \ No newline at end of file diff --git a/source/Component/TextField/TextField.tsx b/source/Component/TextField/TextField.tsx new file mode 100644 index 0000000..56508fb --- /dev/null +++ b/source/Component/TextField/TextField.tsx @@ -0,0 +1,104 @@ +import { Component, ReactNode, RefObject } from "react"; +import { FontLevel, Theme } from "@Component/Theme/Theme"; +import { AllI18nKeys, Localization } from "@Component/Localization/Localization"; +import "./TextField.scss"; + +interface ITextFieldProps { + className?: string; + keyI18n: AllI18nKeys; + infoI18n?: AllI18nKeys; + disableI18n?: AllI18nKeys; + disableI18nOption?: Record; + errorI18n?: AllI18nKeys; + errorI18nOption?: Record; + targetRef?: RefObject; + customStyle?: boolean; + customHoverStyle?: boolean; + onClick?: () => any; +} + +class TextField extends Component { + + private renderInput() { + + const classList: string[] = ["text-field-content"]; + if (this.props.className) { + classList.push(this.props.className); + } + if (!this.props.customStyle) { + classList.push("text-field-content-styled"); + } + if (!this.props.customHoverStyle) { + classList.push("text-field-content-hover-styled"); + } + if (this.props.errorI18n) { + classList.push("text-field-content-error"); + } + + return
+ { this.props.children } +
+ } + + private renderDisable() { + return
+ +
+ } + + private renderError() { + return
+ +
+ } + + public render(): ReactNode { + return <> + +
+ +
+
+ { + this.props.disableI18n ? + this.renderDisable() : + this.renderInput() + } + { + this.props.errorI18n ? + this.renderError() : + undefined + } +
+
+ + } +} + +export { TextField, ITextFieldProps }; \ No newline at end of file diff --git a/source/Component/TogglesInput/TogglesInput.scss b/source/Component/TogglesInput/TogglesInput.scss index 3a1c789..b89fa89 100644 --- a/source/Component/TogglesInput/TogglesInput.scss +++ b/source/Component/TogglesInput/TogglesInput.scss @@ -3,62 +3,40 @@ $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; - min-height: $line-min-height; - 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.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.dark.text-field-root { - div.toggles-content div.checkbox { + div.toggles-input div.checkbox { background-color: $lt-bg-color-lvl3-dark; color: $lt-font-color-normal-dark; } - div.toggles-content div.checkbox:hover { + div.toggles-input div.checkbox:hover { background-color: $lt-bg-color-lvl2-dark; } } -div.light.toggles-input { +div.light.text-field-root { - div.toggles-content div.checkbox { + div.toggles-input div.checkbox { background-color: $lt-bg-color-lvl3-light; color: $lt-font-color-normal-light; } - div.toggles-content div.checkbox:hover { + div.toggles-input div.checkbox:hover { background-color: $lt-bg-color-lvl2-light; } } \ No newline at end of file diff --git a/source/Component/TogglesInput/TogglesInput.tsx b/source/Component/TogglesInput/TogglesInput.tsx index 01a5b5a..a355ddb 100644 --- a/source/Component/TogglesInput/TogglesInput.tsx +++ b/source/Component/TogglesInput/TogglesInput.tsx @@ -1,14 +1,10 @@ -import { AllI18nKeys, Localization } from "@Component/Localization/Localization"; -import { Theme } from "@Component/Theme/Theme"; import { Icon } from "@fluentui/react"; import { Component, ReactNode } from "react"; +import { TextField, ITextFieldProps } from "../TextField/TextField"; import "./TogglesInput.scss"; -interface ITogglesInputProps { - keyI18n: AllI18nKeys; - infoI18n?: AllI18nKeys; +interface ITogglesInputProps extends ITextFieldProps { value?: boolean; - disable?: boolean; onIconName?: string; offIconName?: string; valueChange?: (value: boolean) => any; @@ -16,39 +12,40 @@ interface ITogglesInputProps { class TogglesInput extends Component { public render(): ReactNode { - return -
- -
-
-
+
{ + if (this.props.disableI18n) { + return; + } + if (this.props.valueChange) { + this.props.valueChange(!this.props.value); + } + })} + > + { - if (this.props.disable) { - return; - } - if (this.props.valueChange) { - this.props.valueChange(!this.props.value); - } - })} - > - -
-
- + /> +
+ ; } } diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index d341593..6f733f4 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -24,10 +24,12 @@ const EN_US = { "Input.Error.Min": "Enter value must be greater than {num}", "Input.Error.Length": "The length of the input content must be less than {num}", "Input.Error.Length.Less": "The length of the input content must be greater than {num}", + "Input.Error.Select": "Select object ...", "Object.List.New.Group": "Group 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.Picker.List.No.Data": "There is no model in the model for this option", "Panel.Title.Notfound": "{id}", "Panel.Info.Notfound": "This panel with id {id} can not found!", "Panel.Title.Render.View": "Live preview", @@ -43,6 +45,7 @@ const EN_US = { "Panel.Title.Group.Details.View": "Group", "Panel.Info.Group.Details.View": "Edit view group attributes", "Common.No.Data": "No Data", + "Common.No.Unknown.Error": "Unknown error", "Common.Attr.Title.Basic": "Basic properties", "Common.Attr.Title.Spatial": "Spatial property", "Common.Attr.Title.Individual.Generation": "Individual generation", @@ -65,6 +68,11 @@ const EN_US = { "Common.Attr.Key.Generation.Mod": "Generation model", "Common.Attr.Key.Generation.Mod.Point": "Point model", "Common.Attr.Key.Generation.Mod.Range": "Range model", + "Common.Attr.Key.Generation.Use.Range": "Generation range", + "Common.Attr.Key.Generation.Count": "Generation count", + "Common.Attr.Key.Generation.Point.X": "Generation Point X", + "Common.Attr.Key.Generation.Point.Y": "Generation Point Y", + "Common.Attr.Key.Generation.Point.Z": "Generation Point Z", "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.Group.Details.Attr.Error.Not.Group": "Object is not a Group", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 67c770c..241761e 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -24,10 +24,12 @@ const ZH_CN = { "Input.Error.Min": "输入数值须大于 {number}", "Input.Error.Length": "输入内容长度须小于 {number}", "Input.Error.Length.Less": "输入内容长度须大于 {number}", + "Input.Error.Select": "选择对象 ...", "Object.List.New.Group": "组对象 {id}", "Object.List.New.Range": "范围对象 {id}", "Object.List.New.Label": "标签 {id}", "Object.List.No.Data": "模型中没有任何对象,点击按钮以创建", + "Object.Picker.List.No.Data": "模型中没有合适此选项的模型", "Panel.Title.Notfound": "{id}", "Panel.Info.Notfound": "这个编号为 {id} 的面板无法找到!", "Panel.Title.Render.View": "实时预览", @@ -43,6 +45,7 @@ const ZH_CN = { "Panel.Title.Group.Details.View": "群", "Panel.Info.Group.Details.View": "编辑查看群属性", "Common.No.Data": "暂无数据", + "Common.No.Unknown.Error": "未知错误", "Common.Attr.Title.Basic": "基础属性", "Common.Attr.Title.Spatial": "空间属性", "Common.Attr.Title.Individual.Generation": "个体生成", @@ -65,6 +68,11 @@ const ZH_CN = { "Common.Attr.Key.Generation.Mod": "生成模式", "Common.Attr.Key.Generation.Mod.Point": "点生成", "Common.Attr.Key.Generation.Mod.Range": "范围生成", + "Common.Attr.Key.Generation.Use.Range": "生成范围", + "Common.Attr.Key.Generation.Count": "生成个数", + "Common.Attr.Key.Generation.Point.X": "生成位置 X 坐标", + "Common.Attr.Key.Generation.Point.Y": "生成位置 Y 坐标", + "Common.Attr.Key.Generation.Point.Z": "生成位置 Z 坐标", "Panel.Info.Range.Details.Attr.Error.Not.Range": "对象不是一个范围", "Panel.Info.Range.Details.Attr.Error.Unspecified": "未指定范围对象", "Panel.Info.Group.Details.Attr.Error.Not.Group": "对象不是一个群", diff --git a/source/Model/CtrlObject.ts b/source/Model/CtrlObject.ts index 47857dc..982d20d 100644 --- a/source/Model/CtrlObject.ts +++ b/source/Model/CtrlObject.ts @@ -52,6 +52,31 @@ class CtrlObject extends LabelObject { public delete() { this.model.deleteObject([this]); } + + /** + * 判断是否为相同对象 + */ + public equal(obj: CtrlObject): boolean { + return this === obj || this.id === obj.id; + } + + + /** + * 删除标记 + */ + private deleteFlag: boolean = false; + + /** + * 是否被删除 + */ + public isDeleted(): boolean { + if (this.deleteFlag) return true; + for (let i = 0; i < this.model.objectPool.length; i++) { + if (this.model.objectPool[i].equal(this)) return false; + } + this.deleteFlag = true; + return true; + } } export default CtrlObject; diff --git a/source/Model/Group.ts b/source/Model/Group.ts index db0087e..0e427f4 100644 --- a/source/Model/Group.ts +++ b/source/Model/Group.ts @@ -1,7 +1,7 @@ import { Individual } from "./Individual"; import { CtrlObject } from "./CtrlObject"; import type { Behavior } from "./Behavior"; -import type { Model } from "./Model"; +import type { Label } from "./Label"; enum GenMod { Point = "p", @@ -23,6 +23,21 @@ class Group extends CtrlObject { */ public genMethod: GenMod = GenMod.Point; + /** + * 生成位置坐标 + */ + public genPoint: number[] = [0, 0, 0]; + + /** + * 生成范围 + */ + public genRange?: CtrlObject | Label; + + /** + * 生成个数 + */ + public genCount?: number = 0; + /** * 创建个体 * @param count 创建数量 diff --git a/source/Model/Label.ts b/source/Model/Label.ts index e9748c8..7e3010c 100644 --- a/source/Model/Label.ts +++ b/source/Model/Label.ts @@ -44,13 +44,20 @@ class Label { return this === label || this.id === label.id; } + /** + * 删除标记 + */ + private deleteFlag: boolean = false; + /** * 是否被删除 */ public isDeleted(): boolean { + if (this.deleteFlag) return true; for (let i = 0; i < this.model.labelPool.length; i++) { if (this.model.labelPool[i].equal(this)) return false; } + this.deleteFlag = true; return true; } } diff --git a/source/Model/Model.ts b/source/Model/Model.ts index 3448165..24179b6 100644 --- a/source/Model/Model.ts +++ b/source/Model/Model.ts @@ -90,6 +90,31 @@ class Model extends Emitter { } } + /** + * 通过标签获取指定类型的对象 + * @param label 标签 + * @param type 筛选类型 + */ + public getObjectByLabel( + label: Label, type?: + (new (...p: any) => Range) | + (new (...p: any) => Group) + ): CtrlObject[] { + const res: CtrlObject[] = []; + for (let i = 0; i < this.objectPool.length; i++) { + if (this.objectPool[i].hasLabel(label)) { + if (type) { + if (this.objectPool[i] instanceof type) { + res.push(this.objectPool[i]); + } + } else { + res.push(this.objectPool[i]); + } + } + } + return res; + } + /** * 添加组 */ diff --git a/source/Panel/GroupDetails/GroupDetails.tsx b/source/Panel/GroupDetails/GroupDetails.tsx index 3606c0b..c03ce9c 100644 --- a/source/Panel/GroupDetails/GroupDetails.tsx +++ b/source/Panel/GroupDetails/GroupDetails.tsx @@ -9,6 +9,7 @@ import { LabelPicker } from "@Component/LabelPicker/LabelPicker"; import { Group, GenMod } from "@Model/Group"; import { AllI18nKeys } from "@Component/Localization/Localization"; import { ComboInput, IDisplayItem } from "@Component/ComboInput/ComboInput"; +import { ObjectPicker } from "@Component/ObjectPicker/ObjectPicker"; import "./GroupDetails.scss"; interface IGroupDetailsProps {} @@ -25,58 +26,33 @@ const allOption: IDisplayItem[] = [ @useStatusWithEvent("groupAttrChange", "groupLabelChange", "focusObjectChange") class GroupDetails extends Component { - private renderAttrInput( - id: ObjectID, key: AllI18nKeys, val: string | number | undefined, - change: (val: string, status: Status) => any, - step?: number, max?: number, min?: number - ) { - const handelFunc = (e: string) => { - if (this.props.status) { - change(e, this.props.status); - } - } - if (step) { - return - } else { - return - } - } - private renderFrom(group: Group) { return <> - {this.renderAttrInput( - group.id, "Common.Attr.Key.Display.Name", group.displayName, - (val, status) => { - status.changeGroupAttrib(group.id, "displayName", val); - } - )} + { + this.props.status?.changeGroupAttrib(group.id, "displayName", val); + }} + /> { - if (this.props.status) { - this.props.status.changeGroupAttrib(group.id, "color", color); - } + this.props.status?.changeGroupAttrib(group.id, "color", color); }} /> - {this.renderAttrInput( - group.id, "Common.Attr.Key.Size", group.size, - (val, status) => { - status.changeGroupAttrib(group.id, "size", (val as any) / 1); - }, 10, undefined, 0 - )} + { + this.props.status?.changeGroupAttrib(group.id, "size", (val as any) / 1); + }} + /> { } }} /> + + { + this.props.status?.changeGroupAttrib(group.id, "genCount", (val as any) / 1); + }} + /> + + {group.genMethod === GenMod.Point ? this.renderPointGenOption(group) : null} + + {group.genMethod === GenMod.Range ? this.renderRangeGenOption(group) : null} + + { + console.log("gen"); + }} + /> + } + private renderPointGenOption(group: Group) { + return <> + { + group.genPoint[0] = (val as any) / 1; + this.props.status?.changeGroupAttrib(group.id, "genPoint", group.genPoint); + }} + /> + + { + group.genPoint[1] = (val as any) / 1; + this.props.status?.changeGroupAttrib(group.id, "genPoint", group.genPoint); + }} + /> + + { + group.genPoint[2] = (val as any) / 1; + this.props.status?.changeGroupAttrib(group.id, "genPoint", group.genPoint); + }} + /> + + } + + private renderRangeGenOption(group: Group) { + return <> + { + this.props.status?.changeGroupAttrib(group.id, "genRange", value); + }} + cleanValue={() => { + this.props.status?.changeGroupAttrib(group.id, "genRange", undefined); + }} + /> + + } + public render(): ReactNode { if (this.props.status) { if (this.props.status.focusObject.size <= 0) {