diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index 1b25b6a..bd21180 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -68,7 +68,13 @@ class CommandBar extends Component
- {this.getRenderButton({ iconName: "Settings", i18NKey: "Command.Bar.Setting.Info" })} + {this.getRenderButton({ + iconName: "Settings", + i18NKey: "Command.Bar.Setting.Info", + click: () => { + this.props.status ? this.props.status.popup.showPopup() : undefined; + } + })}
} diff --git a/source/Component/Container/Container.tsx b/source/Component/Container/Container.tsx index b60e1d5..a520149 100644 --- a/source/Component/Container/Container.tsx +++ b/source/Component/Container/Container.tsx @@ -2,7 +2,7 @@ import { Localization } from "@Component/Localization/Localization"; import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme"; import { Themes } from "@Context/Setting"; import { DirectionalHint } from "@fluentui/react"; -import { ILayout, LayoutDirection } from "@Model/Layout"; +import { ILayout, LayoutDirection } from "@Context/Layout"; import { Component, ReactNode, MouseEvent } from "react"; import { getPanelById, getPanelInfoById } from "../../Panel/Panel"; import { LocalizationTooltipHost } from "../Localization/LocalizationTooltipHost"; diff --git a/source/Component/Localization/Localization.tsx b/source/Component/Localization/Localization.tsx index 77d2410..1a16ee7 100644 --- a/source/Component/Localization/Localization.tsx +++ b/source/Component/Localization/Localization.tsx @@ -2,8 +2,8 @@ import { Component, ReactNode, DetailedHTMLProps, HTMLAttributes } from "react"; import { useSetting, IMixinSettingProps, Language } from "@Context/Setting"; import "./Localization.scss"; -import EN_US from "../../Localization/EN-US"; -import ZH_CN from "../../Localization/ZH-CN"; +import EN_US from "@Localization/EN-US"; +import ZH_CN from "@Localization/ZH-CN"; const LanguageDataBase = { EN_US, ZH_CN diff --git a/source/Component/Message/Message.tsx b/source/Component/Message/Message.tsx index 185266a..d46f997 100644 --- a/source/Component/Message/Message.tsx +++ b/source/Component/Message/Message.tsx @@ -1,5 +1,5 @@ import { AllI18nKeys, I18N } from "@Component/Localization/Localization"; -import { useSetting, IMixinSettingProps, Themes, Language } from "@Context/Setting"; +import { useSettingWithEvent, IMixinSettingProps, Themes, Language } from "@Context/Setting"; import { FunctionComponent } from "react"; import "./Message.scss"; @@ -38,5 +38,5 @@ const MessageView: FunctionComponent = (prop } -const Message = useSetting(MessageView); +const Message = useSettingWithEvent("language", "themes")(MessageView); export { Message }; \ No newline at end of file diff --git a/source/Component/Popup/Popup.scss b/source/Component/Popup/Popup.scss new file mode 100644 index 0000000..a39b8d6 --- /dev/null +++ b/source/Component/Popup/Popup.scss @@ -0,0 +1,114 @@ +@import "../Theme/Theme.scss"; + +$header-height: 32px; + +@keyframes show-scale{ + from { + transform: scale3d(1.15, 1.15, 1); + } + to { + transform: scale3d(1, 1, 1); + } +} + +@keyframes show-fade{ + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +div.popup-mask.show-fade { + animation: show-fade .1s cubic-bezier(0, 0, 1, 1) both; + opacity: 1; +} + +div.popup-layer.show-scale { + animation: show-scale .3s cubic-bezier(.1, .9, .2, 1) both, + show-fade .1s cubic-bezier(0, 0, 1, 1) both; + transform: scale3d(1, 1, 1); + opacity: 1; +} + +div.popup-mask { + position: absolute; + cursor: pointer; + width: 100%; + height: 100%; +} + +div.popup-layer { + position: absolute; + border-radius: 3px; + overflow: hidden; + + div.popup-layer-header { + min-height: $header-height; + max-height: $header-height; + height: $header-height; + display: flex; + width: 100%; + + div.header-text { + width: calc( 100% - 32px ); + flex-shrink: 1; + display: flex; + align-items: center; + user-select: none; + + span { + padding-left: 8px; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + } + + div.header-close-icon { + width: $header-height; + height: $header-height; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + user-select: none; + cursor: pointer; + } + } +} + +div.popup-layer.dark { + + div.popup-layer-header { + background-color: $lt-bg-color-lvl3-dark; + + div.header-close-icon:hover { + background-color: $lt-bg-color-lvl2-dark; + color: $lt-red; + } + } +} + +div.popup-layer.light { + + div.popup-layer-header { + background-color: $lt-bg-color-lvl3-light; + + div.header-close-icon:hover { + background-color: $lt-bg-color-lvl2-light; + color: $lt-red; + } + } +} + +div.dark.popup-mask { + background-color: rgba(0, 0, 0, 0.55); +} + +div.light.popup-mask { + background-color: rgba(0, 0, 0, 0.15); +} \ No newline at end of file diff --git a/source/Component/Popup/Popup.tsx b/source/Component/Popup/Popup.tsx new file mode 100644 index 0000000..2bfaefe --- /dev/null +++ b/source/Component/Popup/Popup.tsx @@ -0,0 +1,112 @@ +import { Component, ReactNode } from "react"; +import { IMixinStatusProps, useStatusWithEvent } from "@Context/Status"; +import { BackgroundLevel, Theme } from "@Component/Theme/Theme"; +import { Popup as PopupModel } from "@Context/Popups"; +import { Icon } from "@fluentui/react"; +import "./Popup.scss"; + +interface IPopupProps {} + +@useStatusWithEvent("popupChange") +class Popup extends Component { + + public renderMask(index?: number, click?: () => void, key?: string): ReactNode { + const classList: string[] = ["popup-mask", "show-fade"]; + return + } + + public renderRootMask(): ReactNode { + if (this.props.status) { + const needMask = this.props.status.popup.popups.some(popup => popup.needMask); + if (!needMask) return null; + return this.renderMask(this.props.status.popup.zIndex, + () => { + this.props.status?.popup.popups.forEach( + popup => popup.onClose() + ) + } + ); + } else { + return null; + } + } + + public renderMaskList(): ReactNode { + if (this.props.status) { + return this.props.status.popup.popups + .filter((popup) => { + return popup.needMask && popup.maskForSelf; + }) + .filter((_, index) => { + if (index === 0) return false; + return true; + }) + .map((popup) => { + return this.renderMask(popup.zIndex() - 1, + () => { + popup.onClose(); + }, popup.id + ); + }) + } else { + return null; + } + } + + public renderHeader(popup: PopupModel): ReactNode { + return
+
+ {popup.onRenderHeader()} +
+
{ + popup.onClose(); + }} + > + +
+
+ } + + public renderLayer(popup: PopupModel) { + const pageWidth = document.documentElement.clientWidth; + const pageHeight = document.documentElement.clientHeight; + const top = (pageHeight - popup.height) / 2; + const left = (pageWidth - popup.width) / 2; + + return + {this.renderHeader(popup)} + + } + + public render(): ReactNode { + return <> + {this.renderRootMask()} + {this.renderMaskList()} + {this.props.status?.popup.popups.map((popup) => { + return this.renderLayer(popup); + })} + ; + } +} + +export { Popup }; \ No newline at end of file diff --git a/source/Context/Context.tsx b/source/Context/Context.tsx new file mode 100644 index 0000000..72c052f --- /dev/null +++ b/source/Context/Context.tsx @@ -0,0 +1,78 @@ +import { Emitter, EventType } from "@Model/Emitter"; +import { Component, FunctionComponent, ReactNode, Consumer } from "react"; + +type RenderComponent = (new (...p: any) => Component) | FunctionComponent; + +function superConnectWithEvent, E extends Record>( + consumer: Consumer, keyName: string +) { + return (...events: Array) => { + return (components: R): R => { + const Components = components as any; + const Consumer = consumer; + return class extends Component { + + private status: C | undefined; + private isEventMount: boolean = false; + private propsObject: Record = {}; + + private handelChange = () => { + this.forceUpdate(); + } + + private mountEvent() { + if (this.status && !this.isEventMount) { + this.isEventMount = true; + console.log("Component dep event mount: " + events.join(", ")); + 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: C) => { + this.status = status; + this.propsObject[keyName] = status; + this.mountEvent(); + return ; + }} + + } + + public componentWillUnmount() { + this.unmountEvent(); + } + + } as any; + } + } +} + +function superConnect>(consumer: Consumer, keyName: string) { + return (components: R): R => { + return ((props: any) => { + + const Components = components as any; + const Consumer = consumer; + + return + {(status: C) => } + + }) as any; + } +} + +export { superConnectWithEvent, superConnect }; \ No newline at end of file diff --git a/source/Model/Layout.ts b/source/Context/Layout.ts similarity index 98% rename from source/Model/Layout.ts rename to source/Context/Layout.ts index 9e5229a..2cfc84e 100644 --- a/source/Model/Layout.ts +++ b/source/Context/Layout.ts @@ -1,4 +1,4 @@ -import { Emitter } from "./Emitter"; +import { Emitter } from "@Model/Emitter"; enum LayoutDirection { X = 1, diff --git a/source/Context/Popups.ts b/source/Context/Popups.ts new file mode 100644 index 0000000..0b75ccd --- /dev/null +++ b/source/Context/Popups.ts @@ -0,0 +1,175 @@ +import { ReactNode, createElement } from "react"; +import { Emitter } from "@Model/Emitter"; +import { Localization } from "@Component/Localization/Localization"; + +type IPopupConstructor = new (controller: PopupController, id: string) => Popup; + +/** + * 弹窗类型 + */ +class Popup { + + public zIndex() { + return this.index * 2 + this.controller.zIndex; + } + + public width: number = 300; + + public height: number = 200; + + public top: number = 0; + + public left: number = 0; + + /** + * 是否关闭 + */ + public isClose: boolean = false; + + /** + * 需要蒙版 + */ + public needMask: boolean = true; + + /** + * 单独遮挡下层的蒙版 + */ + public maskForSelf: boolean = false; + + /** + * 唯一标识符 + */ + public id: string; + + /** + * 控制器 + */ + public controller: PopupController; + + /** + * 渲染层级 + */ + public index: number = Infinity; + + /** + * react 节点 + */ + public reactNode: ReactNode; + + /** + * 渲染标题 + */ + public onRenderHeader(): ReactNode { + return createElement(Localization, {i18nKey: "Popup.Title.Unnamed"}); + } + + /** + * 渲染函数 + */ + public onRender(p: Popup): ReactNode { + return null; + } + + /** + * 关闭回调 + */ + public onClose(): void { + this.close(); + }; + + /** + * 渲染节点 + */ + public render(): ReactNode { + this.reactNode = this.onRender(this); + return this.reactNode; + }; + + public close() { + return this.controller.closePopup(this); + } + + public constructor(controller: PopupController, id: string) { + this.controller = controller; + this.id = id; + } +} + +interface IPopupControllerEvent { + popupChange: void; +} + +/** + * 弹窗模型 + */ +class PopupController extends Emitter { + + /** + * ID 序列号 + */ + private idIndex = 0; + + /** + * 最小弹窗 Index + */ + public zIndex = 100; + + /** + * 弹窗列表 + */ + public popups: Popup[] = []; + + /** + * 排序并重置序号 + */ + public sortPopup() { + this.popups = this.popups.sort((a, b) => a.index - b.index); + this.popups = this.popups.map((popup, index) => { + popup.index = (index + 1); + return popup; + }); + this.emit("popupChange"); + } + + /** + * 实例化并开启一个弹窗 + */ + public showPopup

(popup?: P): Popup { + let newPopup = new (popup ?? Popup)(this, `P-${this.idIndex ++}`); + this.popups.push(newPopup); + this.sortPopup(); + return newPopup; + } + + /** + * 关闭一个弹窗 + */ + public closePopup(popup: Popup | string): Popup | undefined { + let id: string; + if (popup instanceof Popup) { + id = popup.id; + } else { + id = popup; + } + let closePopup: Popup | undefined; + this.popups = this.popups.filter( + currentPopup => { + let isDelete = currentPopup.id === id; + if (isDelete) { + closePopup = currentPopup; + currentPopup.isClose = true; + return false; + } else { + return true; + } + } + ); + if (closePopup) { + this.sortPopup(); + this.emit("popupChange"); + } + return closePopup; + } +} + +export { Popup, PopupController } \ No newline at end of file diff --git a/source/Context/Setting.tsx b/source/Context/Setting.tsx index eacc522..f6ee558 100644 --- a/source/Context/Setting.tsx +++ b/source/Context/Setting.tsx @@ -1,6 +1,7 @@ -import { createContext, Component, FunctionComponent } from "react"; +import { createContext } from "react"; +import { superConnect, superConnectWithEvent } from "./Context"; import { Emitter } from "@Model/Emitter"; -import { Layout } from "@Model/Layout"; +import { Layout } from "./Layout"; /** * 主题模式 @@ -12,9 +13,11 @@ enum Themes { type Language = "ZH_CN" | "EN_US"; -class Setting extends Emitter< - Setting & {change: keyof Setting} -> { +interface ISettingEvents extends Setting { + attrChange: keyof Setting; +} + +class Setting extends Emitter { /** * 主题 @@ -36,7 +39,7 @@ class Setting extends Emitter< */ public setProps

(key: P, value: Setting[P]) { this[key] = value as any; - this.emit("change", key); + this.emit("attrChange", key); this.emit(key as any, value as any); } } @@ -51,21 +54,14 @@ SettingContext.displayName = "Setting"; const SettingProvider = SettingContext.Provider; const SettingConsumer = SettingContext.Consumer; -type RenderComponent = (new (...p: any) => Component) | FunctionComponent; - /** * 修饰器 */ -function useSetting(components: R): R { - return ((props: any) => { - const C = components; - return - {(setting: Setting) => } - - }) as any; -} +const useSetting = superConnect(SettingConsumer, "setting"); + +const useSettingWithEvent = superConnectWithEvent(SettingConsumer, "setting"); export { - Themes, Setting, SettingContext, useSetting, Language, + Themes, Setting, SettingContext, useSetting, Language, useSettingWithEvent, IMixinSettingProps, SettingProvider, SettingConsumer }; \ No newline at end of file diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index 3e0ce63..b74e557 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -1,4 +1,4 @@ -import { createContext, Component, FunctionComponent, useState, useEffect, ReactNode } from "react"; +import { createContext } from "react"; import { Emitter } from "@Model/Emitter"; import { Model, ObjectID } from "@Model/Model"; import { Label } from "@Model/Label"; @@ -9,6 +9,8 @@ import { AbstractRenderer } from "@Model/Renderer"; import { ClassicRenderer, MouseMod } from "@GLRender/ClassicRenderer"; import { Setting } from "./Setting"; import { I18N } from "@Component/Localization/Localization"; +import { superConnectWithEvent, superConnect } from "./Context"; +import { PopupController } from "./Popups"; function randomColor(unNormal: boolean = false) { const color = [ @@ -38,6 +40,7 @@ interface IStatusEvent { labelAttrChange: void; groupAttrChange: void; individualChange: void; + popupChange: void; } class Status extends Emitter { @@ -65,6 +68,11 @@ class Status extends Emitter { */ public model: Model = new Model(); + /** + * 弹窗 + */ + public popup: PopupController = new PopupController(); + /** * 焦点对象 */ @@ -95,6 +103,9 @@ class Status extends Emitter { this.model.on("objectChange", () => this.emit("objectChange")); this.model.on("labelChange", () => this.emit("labelChange")); + // 弹窗事件 + this.popup.on("popupChange", () => this.emit("popupChange")); + // 对象变换时执行渲染,更新渲染器数据 this.on("objectChange", this.delayDraw); this.model.on("individualChange", this.delayDraw); @@ -258,67 +269,12 @@ StatusContext.displayName = "Status"; const StatusProvider = StatusContext.Provider; const StatusConsumer = StatusContext.Consumer; -type RenderComponent = (new (...p: any) => Component) | FunctionComponent; - /** * 修饰器 */ -function useStatus(components: R): R { - return ((props: any) => { - const C = components; - return - {(status: Status) => } - - }) as any; -} +const useStatus = superConnect(StatusConsumer, "status"); -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("Component dep event mount: " + events.join(", ")); - 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; - } -} +const useStatusWithEvent = superConnectWithEvent(StatusConsumer, "status"); export { Status, StatusContext, useStatus, useStatusWithEvent, diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index b18fb44..8e4ceb1 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -44,6 +44,7 @@ const EN_US = { "Panel.Info.Label.Details.View": "Edit view label attributes", "Panel.Title.Group.Details.View": "Group", "Panel.Info.Group.Details.View": "Edit view group attributes", + "Popup.Title.Unnamed": "Popup message", "Build.In.Label.Name.All.Group": "All group", "Build.In.Label.Name.All.Range": "All range", "Common.No.Data": "No Data", @@ -51,6 +52,7 @@ const EN_US = { "Common.Attr.Title.Basic": "Basic properties", "Common.Attr.Title.Spatial": "Spatial property", "Common.Attr.Title.Individual.Generation": "Individual generation", + "Common.Attr.Title.Individual.kill": "Individual kill", "Common.Attr.Key.Display.Name": "Display name", "Common.Attr.Key.Position.X": "Position X", "Common.Attr.Key.Position.Y": "Position Y", @@ -79,6 +81,8 @@ const EN_US = { "Common.Attr.Key.Generation.Error.Empty.Range.List": "The specified label does not contain any scope objects", "Common.Attr.Key.Generation.Error.Invalid.Range": "The specified scope object is invalid", "Common.Attr.Key.Generation.Error.Invalid.Label": "The specified label has expired", + "Common.Attr.Key.Kill.Random": "Random kill", + "Common.Attr.Key.Kill.Count": "Kill count", "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 e880dd5..784e522 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -44,13 +44,15 @@ const ZH_CN = { "Panel.Info.Label.Details.View": "编辑查看标签属性", "Panel.Title.Group.Details.View": "群", "Panel.Info.Group.Details.View": "编辑查看群属性", + "Popup.Title.Unnamed": "弹窗消息", "Build.In.Label.Name.All.Group": "全部群", "Build.In.Label.Name.All.Range": "全部范围", "Common.No.Data": "暂无数据", "Common.No.Unknown.Error": "未知错误", "Common.Attr.Title.Basic": "基础属性", "Common.Attr.Title.Spatial": "空间属性", - "Common.Attr.Title.Individual.Generation": "个体生成", + "Common.Attr.Title.Individual.Generation": "生成个体", + "Common.Attr.Title.Individual.kill": "消除个体", "Common.Attr.Key.Display.Name": "显示名称", "Common.Attr.Key.Position.X": "X 坐标", "Common.Attr.Key.Position.Y": "Y 坐标", @@ -79,6 +81,8 @@ const ZH_CN = { "Common.Attr.Key.Generation.Error.Empty.Range.List": "指定的标签中没有包含任何范围对象", "Common.Attr.Key.Generation.Error.Invalid.Range": "指定的范围对象已失效", "Common.Attr.Key.Generation.Error.Invalid.Label": "指定的标签已失效", + "Common.Attr.Key.Kill.Random": "随机消除", + "Common.Attr.Key.Kill.Count": "消除数量", "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/Group.ts b/source/Model/Group.ts index 831083f..b536ae5 100644 --- a/source/Model/Group.ts +++ b/source/Model/Group.ts @@ -49,6 +49,11 @@ class Group extends CtrlObject { */ public genErrorMessageShowCount: number = 0; + /** + * 删除个数 + */ + public killCount: number = 1; + private genInSingleRange(count: number, range: Range) { for (let i = 0; i < count; i++) { let individual = new Individual(this); @@ -188,6 +193,42 @@ class Group extends CtrlObject { return success; } + /** + * 随机杀死个体 + */ + public killIndividuals(): boolean { + let success = false; + let killCount = this.killCount; + if (killCount > this.individuals.size) { + killCount = this.individuals.size; + } + + // 生成索引数组 + const allIndex = new Array(this.individuals.size).fill(0).map((_, i) => i); + const deleteIndex: Set = new Set(); + + for (let i = 0; i < killCount; i++) { + let randomIndex = Math.floor(Math.random() * allIndex.length); + deleteIndex.add(allIndex[randomIndex]); + allIndex.splice(randomIndex, 1); + } + + let j = 0; + this.individuals.forEach((individual) => { + if (deleteIndex.has(j)) { + this.remove(individual); + success = true; + } + j++; + }); + + if (success) { + this.model.emit("individualChange", this); + } + + return success + } + /** * 创建个体 * @param count 创建数量 diff --git a/source/Model/Label.ts b/source/Model/Label.ts index 81c1d2b..3cdf609 100644 --- a/source/Model/Label.ts +++ b/source/Model/Label.ts @@ -46,7 +46,7 @@ class Label { * 判断是否为相同标签 */ public equal(label: Label): boolean { - if (this.isDeleted() || label.isDeleted()) return false; + // if (this.isDeleted() || label.isDeleted()) return false; return this === label || this.id === label.id; } diff --git a/source/Page/SimulatorWeb/SimulatorWeb.tsx b/source/Page/SimulatorWeb/SimulatorWeb.tsx index 77c76f7..07587a5 100644 --- a/source/Page/SimulatorWeb/SimulatorWeb.tsx +++ b/source/Page/SimulatorWeb/SimulatorWeb.tsx @@ -7,9 +7,10 @@ import { StatusProvider, Status } from "@Context/Status"; import { ClassicRenderer } from "@GLRender/ClassicRenderer"; import { initializeIcons } from '@fluentui/font-icons-mdl2'; import { RootContainer } from "@Component/Container/RootContainer"; -import { LayoutDirection } from "@Model/Layout"; -import "./SimulatorWeb.scss"; +import { LayoutDirection } from "@Context/Layout"; import { CommandBar } from "@Component/CommandBar/CommandBar"; +import { Popup } from "@Component/Popup/Popup"; +import "./SimulatorWeb.scss"; initializeIcons("https://img.mrkbear.com/fabric-cdn-prod_20210407.001/"); @@ -101,6 +102,7 @@ class SimulatorWeb extends Component { backgroundLevel={BackgroundLevel.Level5} fontLevel={FontLevel.Level3} > +

{ }} /> + + + { + this.props.status?.changeGroupAttrib(group.id, "killCount", (val as any) / 1); + }} + /> + + { + group.killIndividuals() + }} + /> + } diff --git a/tsconfig.json b/tsconfig.json index 8df4d3a..e8bd41b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,6 +31,12 @@ ], "@Component/*": [ "./source/Component/*" + ], + "@Localization/*": [ + "./source/Localization/*" + ], + "@Panel/*": [ + "./source/Panel/*" ] } },