Add depends fluent ui #5

Merged
MrKBear merged 5 commits from dev-mrkbear into master 2022-02-24 19:10:26 +08:00
21 changed files with 1639 additions and 872 deletions

1960
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -39,6 +39,7 @@
"webpack-dev-server": "^4.7.2"
},
"dependencies": {
"@fluentui/react": "^8.56.0",
"@juggle/resize-observer": "^3.3.1",
"gl-matrix": "^3.4.3",
"react": "^17.0.2",

View File

@ -0,0 +1,3 @@
div.header-bar {
display: flex;
}

View File

@ -0,0 +1,40 @@
import { Component, ReactNode } from "react";
import { useStatus, IMixinStatusProps } from "@Context/Status";
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
interface IHeaderBarProps {
height: number;
}
/**
*
*/
@useStatus
class HeaderBar extends Component<IHeaderBarProps & IMixinStatusProps> {
private changeListener = () => {
this.forceUpdate();
}
public componentDidMount() {
}
public componentWillUnmount() {
}
public render(): ReactNode {
return <Theme
className="header-bar"
backgroundLevel={BackgroundLevel.Level1}
fontLevel={FontLevel.Level3}
style={{ height: this.props.height }}
>
Living Together | Web
</Theme>
}
}
export default HeaderBar;
export { HeaderBar };

View File

@ -0,0 +1,7 @@
span.ZH_CN {
font-family: 'Microsoft Yahei', Verdana, Simsun, 'Segoe UI', Tahoma, Arial, sans-serif;
}
span.EN_US {
font-family: 'Segoe UI Web Regular', 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif;
}

View File

@ -0,0 +1,70 @@
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";
const LanguageDataBase = {
EN_US, ZH_CN
}
interface ILocalizationProps {
className?: string;
i18nKey: keyof typeof EN_US;
options?: Record<string, string>;
}
function I18N(language: Language, key: keyof typeof EN_US, values?: Record<string, string>) {
let i18nValue = LanguageDataBase[language][key];
if (values) {
for (let valueKey in values) {
i18nValue = i18nValue.replaceAll(new RegExp(`\\{\\s*${valueKey}\\s*\\}`, "g"), values[valueKey]);
}
}
return i18nValue;
}
/**
*
*/
@useSetting
class Localization extends Component<ILocalizationProps & IMixinSettingProps &
DetailedHTMLProps<
HTMLAttributes<HTMLSpanElement>, HTMLSpanElement
>
> {
private handelLanguageChange = () => {
this.forceUpdate();
}
public componentDidMount() {
if (this.props.setting) {
this.props.setting.on("language", this.handelLanguageChange);
}
}
public componentWillUnmount() {
if (this.props.setting) {
this.props.setting.off("language", this.handelLanguageChange);
}
}
public render(): ReactNode {
let language: Language = this.props.setting ? this.props.setting.language : "EN_US";
let classNameList: string[] = [];
if (this.props.className) classNameList.push(this.props.className);
classNameList.push(language);
let safeProps = {...this.props};
delete safeProps.className;
delete safeProps.setting;
delete (safeProps as any).i18nKey;
delete safeProps.options;
return <span {...safeProps} className={classNameList.join(" ")}>
{I18N(language, this.props.i18nKey, this.props.options)}
</span>
}
}
export { Localization, I18N };

View File

@ -0,0 +1,80 @@
@import "@fluentui/react/dist/sass/References";
$lt-font-size-normal: $ms-font-size-14;
$lt-font-size-lvl3: $ms-font-size-16;
$lt-font-size-lvl2: $ms-font-size-18;
$lt-font-size-lvl1: $ms-font-size-24;
$lt-font-weight-normal: $ms-font-weight-regular;
$lt-font-weight-lvl3: $ms-font-weight-semibold;
$lt-font-weight-lvl2: $ms-font-weight-semibold;
$lt-font-weight-lvl1: $ms-font-weight-bold;
// 背景颜色
$lt-bg-color-lvl1-dark: $ms-color-gray140;
$lt-bg-color-lvl2-dark: $ms-color-gray150;
$lt-bg-color-lvl3-dark: $ms-color-gray160;
$lt-bg-color-lvl4-dark: $ms-color-gray180;
$lt-bg-color-lvl5-dark: $ms-color-gray200;
// 文字颜色
$lt-font-color-normal-dark: $ms-color-gray110;
$lt-font-color-lvl3-dark: $ms-color-gray100;
$lt-font-color-lvl2-dark: $ms-color-gray100;
$lt-font-color-lvl1-dark: $ms-color-gray90;
// 背景颜色
$lt-bg-color-lvl1-light: $ms-color-gray10;
$lt-bg-color-lvl2-light: $ms-color-gray20;
$lt-bg-color-lvl3-light: $ms-color-gray30;
$lt-bg-color-lvl4-light: $ms-color-gray50;
$lt-bg-color-lvl5-light: $ms-color-gray70;
// 文字颜色
$lt-font-color-normal-light: $ms-color-gray130;
$lt-font-color-lvl3-light: $ms-color-gray140;
$lt-font-color-lvl2-light: $ms-color-gray140;
$lt-font-color-lvl1-light: $ms-color-gray150;
div.dark, div.light {
transition: all 300ms ease-in-out;
}
div.dark.background-lvl1 { background-color: $lt-bg-color-lvl1-dark; }
div.dark.background-lvl2 { background-color: $lt-bg-color-lvl2-dark; }
div.dark.background-lvl3 { background-color: $lt-bg-color-lvl3-dark; }
div.dark.background-lvl4 { background-color: $lt-bg-color-lvl4-dark; }
div.dark.background-lvl5 { background-color: $lt-bg-color-lvl5-dark; }
div.light.background-lvl1 { background-color: $lt-bg-color-lvl1-light; }
div.light.background-lvl2 { background-color: $lt-bg-color-lvl2-light; }
div.light.background-lvl3 { background-color: $lt-bg-color-lvl3-light; }
div.light.background-lvl4 { background-color: $lt-bg-color-lvl4-light; }
div.light.background-lvl5 { background-color: $lt-bg-color-lvl5-light; }
div.font-normal {
font-size: $lt-font-size-normal;
font-weight: $lt-font-weight-normal;
}
div.font-lvl3 {
font-size: $lt-font-size-lvl3;
font-weight: $lt-font-weight-lvl3;
}
div.font-lvl2 {
font-size: $lt-font-size-lvl2;
font-weight: $lt-font-weight-lvl2;
}
div.font-lvl1 {
font-size: $lt-font-size-lvl1;
font-weight: $lt-font-weight-lvl1;
}
div.dark.font-normal { color: $lt-font-color-normal-dark; }
div.dark.font-lvl3 { color: $lt-font-color-lvl3-dark; }
div.dark.font-lvl2 { color: $lt-font-color-lvl2-dark; }
div.dark.font-lvl1 { color: $lt-font-color-lvl1-dark; }
div.light.font-normal { color: $lt-font-color-normal-light; }
div.light.font-lvl3 { color: $lt-font-color-lvl3-light; }
div.light.font-lvl2 { color: $lt-font-color-lvl2-light; }
div.light.font-lvl1 { color: $lt-font-color-lvl1-light; }

View File

@ -0,0 +1,85 @@
import { useSetting, Themes, IMixinSettingProps } from "@Context/Setting";
import { Component, ReactNode, DetailedHTMLProps, HTMLAttributes } from "react";
import "./Theme.scss";
enum FontLevel {
normal = "normal",
Level3 = "lvl3",
Level2 = "lvl2",
Level1 = "lvl1"
}
enum BackgroundLevel {
Level5 = "lvl5",
Level4 = "lvl4",
Level3 = "lvl3",
Level2 = "lvl2",
Level1 = "lvl1"
}
interface IThemeProps {
className?: string;
fontLevel?: FontLevel;
backgroundLevel?: BackgroundLevel;
}
/**
*
*/
@useSetting
class Theme extends Component<
IThemeProps & IMixinSettingProps & DetailedHTMLProps<
HTMLAttributes<HTMLDivElement>, HTMLDivElement
>
> {
private handelThemeChange = () => {
this.forceUpdate();
}
public componentDidMount() {
if (this.props.setting) {
this.props.setting.on("themes", this.handelThemeChange);
}
}
public componentWillUnmount() {
if (this.props.setting) {
this.props.setting.off("themes", this.handelThemeChange);
}
}
public render(): ReactNode {
const setting = this.props.setting;
const classNameList: string[] = [];
if (this.props.className) {
classNameList.push(this.props.className);
}
const theme = setting ? setting.themes : Themes.dark;
classNameList.push(theme === Themes.light ? "light" : "dark");
if (this.props.fontLevel) {
classNameList.push(`font-${this.props.fontLevel}`);
}
if (this.props.backgroundLevel) {
classNameList.push(`background-${this.props.backgroundLevel}`);
}
const propsObj = {...this.props};
delete propsObj.className;
delete propsObj.setting;
delete propsObj.backgroundLevel;
delete propsObj.fontLevel;
return <div {...propsObj} className={`${classNameList.join(" ")}`}>
{ this.props.children }
</div>
}
}
export default Theme;
export { Theme, FontLevel, BackgroundLevel };

View File

@ -0,0 +1,65 @@
import { createContext, Component, FunctionComponent } from "react";
import { Emitter } from "@Model/Emitter";
/**
*
*/
enum Themes {
light = 1,
dark = 2
}
type Language = "ZH_CN" | "EN_US";
class Setting extends Emitter<
Setting & {change: keyof Setting}
> {
/**
*
*/
public themes: Themes = Themes.dark;
/**
*
*/
public language: Language = "EN_US";
/**
*
*/
public setProps<P extends keyof Setting>(key: P, value: Setting[P]) {
this[key] = value as any;
this.emit("change", key);
this.emit(key as any, value as any);
}
}
interface IMixinSettingProps {
setting?: Setting;
}
const SettingContext = createContext<Setting>(new Setting());
SettingContext.displayName = "Setting";
const SettingProvider = SettingContext.Provider;
const SettingConsumer = SettingContext.Consumer;
type RenderComponent = (new (...p: any) => Component<any, any, any>) | FunctionComponent<any>;
/**
*
*/
function useSetting<R extends RenderComponent>(components: R): R {
return ((props: any) => {
const C = components;
return <SettingConsumer>
{(setting: Setting) => <C {...props} setting={setting}></C>}
</SettingConsumer>
}) as any;
}
export {
Themes, Setting, SettingContext, useSetting, Language,
IMixinSettingProps, SettingProvider, SettingConsumer
};

53
source/Context/Status.tsx Normal file
View File

@ -0,0 +1,53 @@
import { createContext, Component, FunctionComponent } from "react";
import { Emitter } from "@Model/Emitter";
import { Model } from "@Model/Model";
import { Archive } from "@Model/Archive";
import { AbstractRenderer } from "@Model/Renderer";
class Status extends Emitter<{}> {
/**
*
*/
public renderer: AbstractRenderer = undefined as any;
/**
*
*/
public archive: Archive = new Archive();
/**
*
*/
public model: Model = new Model();
}
interface IMixinStatusProps {
status?: Status;
}
const StatusContext = createContext<Status>(new Status());
StatusContext.displayName = "Status";
const StatusProvider = StatusContext.Provider;
const StatusConsumer = StatusContext.Consumer;
type RenderComponent = (new (...p: any) => Component<any, any, any>) | FunctionComponent<any>;
/**
*
*/
function useStatus<R extends RenderComponent>(components: R): R {
return ((props: any) => {
const C = components;
return <StatusConsumer>
{(status: Status) => <C {...props} status={status}></C>}
</StatusConsumer>
}) as any;
}
export {
Status, StatusContext, useStatus,
IMixinStatusProps, StatusProvider, StatusConsumer
};

View File

@ -5,7 +5,9 @@ import { GLContext } from "./GLContext";
import { Camera } from "./Camera";
import { Clock } from "@GLRender/Clock";
interface IRendererOwnParams {}
interface IRendererOwnParams {
canvas: HTMLCanvasElement;
}
/**
*
@ -38,7 +40,7 @@ abstract class BasicRenderer<
*/
protected clock: Clock;
public constructor(canvas: HTMLCanvasElement, param: Partial<M & IRendererParams> = {}) {
public constructor(param: Partial<M & IRendererParams> = {}) {
super();
// 初始化参数
@ -50,7 +52,7 @@ abstract class BasicRenderer<
} as M & IRendererParams;
// 实例化画布对象
this.canvas = new GLCanvas(canvas, this.param);
this.canvas = new GLCanvas(param.canvas, this.param);
// 实例化摄像机
this.camera = new Camera(this.canvas);
@ -154,7 +156,7 @@ abstract class BasicRenderer<
/**
*
*/
abstract onLoad(): void;
abstract onLoad(): this;
/**
*

View File

@ -34,7 +34,7 @@ class ClassicRenderer extends BasicRenderer<{}, IClassicRendererParams> {
*/
private objectPool = new Map<ObjectID, DisplayObject>();
public onLoad(): void {
public onLoad(): this {
// 自动调节分辨率
this.autoResize();
@ -69,6 +69,8 @@ class ClassicRenderer extends BasicRenderer<{}, IClassicRendererParams> {
// setInterval(() => {
// this.basicGroup.upLoadData(new Array(100 * 3).fill(0).map(() => (Math.random() - .5) * 2));
// }, 500);
return this;
}
loop(t: number): void {

View File

@ -0,0 +1,5 @@
const EN_US = {
"EN_US": "English (US)",
"ZH_CN": "Chinese (Simplified)"
}
export default EN_US;

View File

@ -0,0 +1,5 @@
const ZH_CN = {
"EN_US": "英语 (美国)",
"ZH_CN": "中文 (简体)"
}
export default ZH_CN;

42
source/Model/Archive.ts Normal file
View File

@ -0,0 +1,42 @@
import { Emitter, EventType, EventMixin } from "./Emitter";
interface IArchiveEvent {
save: Archive;
load: Archive;
}
class Archive<
M extends any = any,
E extends Record<EventType, any> = {}
> extends Emitter<E> {
/**
*
*/
public isNewFile: boolean = true;
/**
*
*/
public fileName?: string;
/**
*
*/
public fileData?: M;
/**
*
*
*/
public save() {};
/**
*
* return Model
*/
public load() {};
}
export { Archive };
export default Archive;

View File

@ -21,10 +21,8 @@ class Laboratory extends Component {
throw new Error("Laboratory: 重复引用 canvas 节点");
}
const canvas = document.createElement("canvas");
const renderer = new ClassicRenderer(canvas, { className: "canvas" });
const renderer = new ClassicRenderer({ className: "canvas" }).onLoad();
this.canvasContRef.current.appendChild(renderer.canvas.dom);
renderer.onLoad();
let model = new Model().bindRenderer(renderer);
let group = model.addGroup();

View File

@ -0,0 +1,6 @@
div.app-root {
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
}

View File

@ -1,13 +1,70 @@
import { Component, ReactNode, createRef } from "react";
import { Component, ReactNode } from "react";
import { SettingProvider, Setting } from "@Context/Setting";
import { HeaderBar } from "@Component/HeaderBar/HeaderBar";
import { Theme, FontLevel, BackgroundLevel } from "@Component/Theme/Theme";
import { Localization } from "@Component/Localization/Localization";
import { Entry } from "../Entry/Entry";
import { StatusProvider, Status } from "@Context/Status";
import { ClassicRenderer } from "@GLRender/ClassicRenderer";
import "./SimulatorWeb.scss";
class SimulatorWeb extends Component {
private canvasContRef = createRef<HTMLDivElement>();
/**
*
*/
private setting: Setting;
/**
*
*/
private status: Status;
public constructor(props: any) {
super(props);
// TODO: 这里要读取设置
this.setting = new Setting();
(window as any).setting = (this.setting as any);
// TODO: 这里要读取存档
this.status = new Status();
this.status.renderer = new ClassicRenderer({ className: "canvas" }).onLoad();
this.status.model.bindRenderer(this.status.renderer);
// 测试代码
if (true) {
let group = this.status.model.addGroup();
let range = this.status.model.addRange();
range.color = [.1, .5, .9];
group.new(100);
group.color = [.8, .1, .6];
group.individuals.forEach((individual) => {
individual.position[0] = (Math.random() - .5) * 2;
individual.position[1] = (Math.random() - .5) * 2;
individual.position[2] = (Math.random() - .5) * 2;
})
this.status.model.update(0);
}
(window as any).s = this;
}
public render(): ReactNode {
return <div>Web</div>
return <SettingProvider value={this.setting}>
<StatusProvider value={this.status}>
{this.renderContent()}
</StatusProvider>
</SettingProvider>
}
private renderContent(): ReactNode {
return <Theme
className="app-root"
backgroundLevel={BackgroundLevel.Level5}
>
<HeaderBar height={45}/>
</Theme>
}
}

View File

@ -1,6 +1,8 @@
{
"compileOnSave": true,
"compilerOptions": {
"experimentalDecorators": true,
"resolveJsonModule": true,
"alwaysStrict": true,
"strict": true,
"sourceMap": true,
@ -23,6 +25,12 @@
],
"@GLRender/*": [
"./source/GLRender/*"
],
"@Context/*": [
"./source/Context/*"
],
"@Component/*": [
"./source/Component/*"
]
}
},