Merge pull request 'Add save and load function' (#46) from dev-mrkbear into master

Reviewed-on: http://git.mrkbear.com/MrKBear/living-together/pulls/46
This commit is contained in:
MrKBear 2022-04-26 16:44:39 +08:00
commit 39a514b2cc
19 changed files with 668 additions and 296 deletions

View File

@ -123,6 +123,10 @@ const resolve = (plugins = []) => {
let res = {
extensions: [ ".tsx", '.ts', '.js' ],
fallback: {
'react/jsx-runtime': 'react/jsx-runtime.js',
'react/jsx-dev-runtime': 'react/jsx-dev-runtime.js',
},
plugins: plugins
};

190
package-lock.json generated
View File

@ -16,6 +16,8 @@
"express": "^4.17.3",
"gl-matrix": "^3.4.3",
"react": "^17.0.2",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^17.0.2",
"uuid": "^8.3.2"
},
@ -726,6 +728,17 @@
"node": ">=4"
}
},
"node_modules/@babel/runtime": {
"version": "7.17.9",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.17.9.tgz",
"integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz",
@ -1023,6 +1036,21 @@
"node": ">=10"
}
},
"node_modules/@react-dnd/asap": {
"version": "5.0.2",
"resolved": "https://registry.npmmirror.com/@react-dnd/asap/-/asap-5.0.2.tgz",
"integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
},
"node_modules/@react-dnd/invariant": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@react-dnd/invariant/-/invariant-4.0.2.tgz",
"integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
},
"node_modules/@react-dnd/shallowequal": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
"integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
},
"node_modules/@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-0.14.0.tgz",
@ -1195,7 +1223,7 @@
"version": "17.0.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
"dev": true
"devOptional": true
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.1",
@ -2817,6 +2845,16 @@
"node": ">=8"
}
},
"node_modules/dnd-core": {
"version": "16.0.1",
"resolved": "https://registry.npmmirror.com/dnd-core/-/dnd-core-16.0.1.tgz",
"integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
"dependencies": {
"@react-dnd/asap": "^5.0.1",
"@react-dnd/invariant": "^4.0.1",
"redux": "^4.2.0"
}
},
"node_modules/dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@ -3317,8 +3355,7 @@
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-glob": {
"version": "3.2.11",
@ -3945,6 +3982,14 @@
"he": "bin/he"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@ -6164,6 +6209,43 @@
"node": ">=0.10.0"
}
},
"node_modules/react-dnd": {
"version": "16.0.1",
"resolved": "https://registry.npmmirror.com/react-dnd/-/react-dnd-16.0.1.tgz",
"integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
"dependencies": {
"@react-dnd/invariant": "^4.0.1",
"@react-dnd/shallowequal": "^4.0.1",
"dnd-core": "^16.0.1",
"fast-deep-equal": "^3.1.3",
"hoist-non-react-statics": "^3.3.2"
},
"peerDependencies": {
"@types/hoist-non-react-statics": ">= 3.3.1",
"@types/node": ">= 12",
"@types/react": ">= 16",
"react": ">= 16.14"
},
"peerDependenciesMeta": {
"@types/hoist-non-react-statics": {
"optional": true
},
"@types/node": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/react-dnd-html5-backend": {
"version": "16.0.1",
"resolved": "https://registry.npmmirror.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
"integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
"dependencies": {
"dnd-core": "^16.0.1"
}
},
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@ -6177,6 +6259,11 @@
"react": "17.0.2"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@ -6305,6 +6392,19 @@
"node": ">=8"
}
},
"node_modules/redux": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/redux/-/redux-4.2.0.tgz",
"integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"node_modules/regexp.prototype.flags": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz",
@ -8767,6 +8867,14 @@
}
}
},
"@babel/runtime": {
"version": "7.17.9",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.17.9.tgz",
"integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@discoveryjs/json-ext": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz",
@ -9013,6 +9121,21 @@
"rimraf": "^3.0.2"
}
},
"@react-dnd/asap": {
"version": "5.0.2",
"resolved": "https://registry.npmmirror.com/@react-dnd/asap/-/asap-5.0.2.tgz",
"integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
},
"@react-dnd/invariant": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@react-dnd/invariant/-/invariant-4.0.2.tgz",
"integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
},
"@react-dnd/shallowequal": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
"integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
},
"@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-0.14.0.tgz",
@ -9176,7 +9299,7 @@
"version": "17.0.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
"dev": true
"devOptional": true
},
"@types/normalize-package-data": {
"version": "2.4.1",
@ -10488,6 +10611,16 @@
"path-type": "^4.0.0"
}
},
"dnd-core": {
"version": "16.0.1",
"resolved": "https://registry.npmmirror.com/dnd-core/-/dnd-core-16.0.1.tgz",
"integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
"requires": {
"@react-dnd/asap": "^5.0.1",
"@react-dnd/invariant": "^4.0.1",
"redux": "^4.2.0"
}
},
"dns-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
@ -10905,8 +11038,7 @@
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"fast-glob": {
"version": "3.2.11",
@ -11384,6 +11516,14 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
}
},
"hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@ -13051,6 +13191,26 @@
"object-assign": "^4.1.1"
}
},
"react-dnd": {
"version": "16.0.1",
"resolved": "https://registry.npmmirror.com/react-dnd/-/react-dnd-16.0.1.tgz",
"integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
"requires": {
"@react-dnd/invariant": "^4.0.1",
"@react-dnd/shallowequal": "^4.0.1",
"dnd-core": "^16.0.1",
"fast-deep-equal": "^3.1.3",
"hoist-non-react-statics": "^3.3.2"
}
},
"react-dnd-html5-backend": {
"version": "16.0.1",
"resolved": "https://registry.npmmirror.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
"integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
"requires": {
"dnd-core": "^16.0.1"
}
},
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@ -13061,6 +13221,11 @@
"scheduler": "^0.20.2"
}
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
@ -13163,6 +13328,19 @@
"strip-indent": "^3.0.0"
}
},
"redux": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/redux/-/redux-4.2.0.tgz",
"integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
"requires": {
"@babel/runtime": "^7.9.2"
}
},
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
},
"regexp.prototype.flags": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz",

View File

@ -75,6 +75,8 @@
"express": "^4.17.3",
"gl-matrix": "^3.4.3",
"react": "^17.0.2",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^17.0.2",
"uuid": "^8.3.2"
}

View File

@ -1,3 +1,5 @@
@import "../Theme/Theme.scss";
div.command-bar {
height: 100%;
user-select: none;
@ -5,32 +7,53 @@ div.command-bar {
flex-direction: column;
justify-content: space-between;
button.ms-Button.command-button {
div.command-button {
width: 100%;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
transition: all 100ms ease-in-out;
color: inherit;
cursor: pointer;
span.ms-Button-flexContainer i.ms-Icon {
font-size: 25px;
i {
font-size: 22px;
}
div.command-button-loading {
div.ms-Spinner-circle {
border-width: 2px;
}
}
}
button.ms-Button.command-button.on-end {
div.command-button.on-end {
align-self: flex-end;
}
}
div.command-bar.dark button.ms-Button.command-button.active,
div.command-bar.dark button.ms-Button.command-button:hover {
div.command-bar.dark div.command-button div.command-button-loading div.ms-Spinner-circle {
border-top-color: rgba($color: #FFFFFF, $alpha: .9);
border-left-color: rgba($color: #FFFFFF, $alpha: .4);
border-bottom-color: rgba($color: #FFFFFF, $alpha: .4);
border-right-color: rgba($color: #FFFFFF, $alpha: .4);
}
div.command-bar.light div.command-button div.command-button-loading div.ms-Spinner-circle {
border-top-color: rgba($color: #000000, $alpha: .9);
border-left-color: rgba($color: #000000, $alpha: .4);
border-bottom-color: rgba($color: #000000, $alpha: .4);
border-right-color: rgba($color: #000000, $alpha: .4);
}
div.command-bar.dark div.command-button.active,
div.command-bar.dark div.command-button:hover {
background-color: rgba($color: #FFFFFF, $alpha: .2);
color: rgba($color: #FFFFFF, $alpha: 1);
}
div.command-bar.light button.ms-Button.command-button.active,
div.command-bar.light button.ms-Button.command-button:hover {
div.command-bar.light div.command-button.active,
div.command-bar.light div.command-button:hover {
background-color: rgba($color: #000000, $alpha: .08);
color: rgba($color: #000000, $alpha: 1);
}

View File

@ -1,5 +1,5 @@
import { Component, ReactNode } from "react";
import { DirectionalHint, IconButton } from "@fluentui/react";
import { Component, ReactNode, FunctionComponent } from "react";
import { DirectionalHint, Icon, Spinner } from "@fluentui/react";
import { useSetting, IMixinSettingProps } from "@Context/Setting";
import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status";
import { BackgroundLevel, Theme } from "@Component/Theme/Theme";
@ -18,23 +18,28 @@ interface IRenderButtonParameter {
iconName?: string;
click?: () => void;
active?: boolean;
isLoading?: boolean;
}
interface ICommandBarState {
isSaveRunning: boolean;
}
function getRenderButton(param: IRenderButtonParameter): ReactNode {
const CommandButton: FunctionComponent<IRenderButtonParameter> = (param) => {
return <LocalizationTooltipHost
i18nKey={param.i18NKey}
directionalHint={DirectionalHint.rightCenter}
>
<IconButton
<div
style={{ height: COMMAND_BAR_WIDTH }}
iconProps={{ iconName: param.iconName }}
onClick={ param.click }
onClick={ param.isLoading ? undefined : param.click }
className={"command-button on-end" + (param.active ? " active" : "")}
/>
>
{param.isLoading ?
<Spinner className="command-button-loading"/> :
<Icon iconName={param.iconName}/>
}
</div>
</LocalizationTooltipHost>
}
@useSetting
@ -68,78 +73,84 @@ class CommandBar extends Component<IMixinSettingProps & IMixinStatusProps, IComm
}}
/>
{getRenderButton({
iconName: "Save",
i18NKey: "Command.Bar.Save.Info",
click: () => {
<CommandButton
iconName="Save"
i18NKey="Command.Bar.Save.Info"
isLoading={this.state.isSaveRunning}
click={() => {
this.setState({
isSaveRunning: true
});
}
})}
}}
/>
{getRenderButton({
iconName: this.props.status?.actuator.start() ? "Pause" : "Play",
i18NKey: "Command.Bar.Play.Info",
click: () => this.props.status ? this.props.status.actuator.start(
<CommandButton
iconName={this.props.status?.actuator.start() ? "Pause" : "Play"}
i18NKey="Command.Bar.Play.Info"
click={() => this.props.status ? this.props.status.actuator.start(
!this.props.status.actuator.start()
) : undefined
})}
) : undefined}
/>
{getRenderButton({
iconName: "HandsFree", i18NKey: "Command.Bar.Drag.Info",
active: mouseMod === MouseMod.Drag,
click: () => this.props.status ? this.props.status.setMouseMod(MouseMod.Drag) : undefined
})}
<CommandButton
iconName="HandsFree"
i18NKey="Command.Bar.Drag.Info"
active={mouseMod === MouseMod.Drag}
click={() => this.props.status ? this.props.status.setMouseMod(MouseMod.Drag) : undefined}
/>
{getRenderButton({
iconName: "TouchPointer", i18NKey: "Command.Bar.Select.Info",
active: mouseMod === MouseMod.click,
click: () => this.props.status ? this.props.status.setMouseMod(MouseMod.click) : undefined
})}
<CommandButton
iconName="TouchPointer"
i18NKey="Command.Bar.Select.Info"
active={mouseMod === MouseMod.click}
click={() => this.props.status ? this.props.status.setMouseMod(MouseMod.click) : undefined}
/>
{getRenderButton({
iconName: "WebAppBuilderFragmentCreate",
i18NKey: "Command.Bar.Add.Group.Info",
click: () => {
<CommandButton
iconName="WebAppBuilderFragmentCreate"
i18NKey="Command.Bar.Add.Group.Info"
click={() => {
this.props.status ? this.props.status.newGroup() : undefined;
}
})}
}}
/>
{getRenderButton({
iconName: "ProductVariant",
i18NKey: "Command.Bar.Add.Range.Info",
click: () => {
<CommandButton
iconName="ProductVariant"
i18NKey="Command.Bar.Add.Range.Info"
click={() => {
this.props.status ? this.props.status.newRange() : undefined;
}
})}
}}
/>
{getRenderButton({
iconName: "Running",
i18NKey: "Command.Bar.Add.Behavior.Info",
click: () => {
<CommandButton
iconName="Running"
i18NKey="Command.Bar.Add.Behavior.Info"
click={() => {
this.props.status?.popup.showPopup(BehaviorPopup, {});
}
})}
}}
/>
{getRenderButton({
iconName: "Tag",
i18NKey: "Command.Bar.Add.Tag.Info",
click: () => {
<CommandButton
iconName="Tag"
i18NKey="Command.Bar.Add.Tag.Info"
click={() => {
this.props.status ? this.props.status.newLabel() : undefined;
}
})}
}}
/>
{getRenderButton({ iconName: "Camera", i18NKey: "Command.Bar.Camera.Info" })}
<CommandButton
iconName="Camera"
i18NKey="Command.Bar.Camera.Info"
/>
</div>
<div>
{getRenderButton({
iconName: "Settings",
i18NKey: "Command.Bar.Setting.Info",
click: () => {
<CommandButton
iconName="Settings"
i18NKey="Command.Bar.Setting.Info"
click={() => {
this.props.status?.popup.showPopup(SettingPopup, {});
}
})}
}}
/>
</div>
</Theme>
}

View File

@ -9,6 +9,7 @@ interface IConfirmPopupProps {
titleI18N?: AllI18nKeys;
titleI18NOption?: Record<string, string>;
infoI18n?: AllI18nKeys;
infoI18nOption?: Record<string, string>;
yesI18n?: AllI18nKeys;
noI18n?: AllI18nKeys;
renderInfo?: () => ReactNode;
@ -64,8 +65,10 @@ class ConfirmPopup extends Popup<IConfirmPopupProps> {
this.props.renderInfo ?
this.props.renderInfo() :
this.props.infoI18n ?
<Message i18nKey={this.props.infoI18n}/> :
null
<Message
i18nKey={this.props.infoI18n}
options={this.props.infoI18nOption}
/> : null
}
</ConfirmContent>
}

View File

@ -1,8 +1,20 @@
@import "../Theme/Theme.scss";
div.load-file-app-root {
width: 100%;
height: 100%;
}
div.load-file-layer-root {
position: fixed;
z-index: 1000;
width: 100%;
height: 100%;
pointer-events: none;
box-sizing: border-box;
padding: 20px;
div.load-file-layer {
width: 100%;
height: 100%;
display: flex;
@ -10,6 +22,7 @@ div.load-file-layer-root {
justify-content: center;
align-items: center;
flex-wrap: wrap;
border-radius: 3px;
div {
user-select: none;
@ -27,12 +40,21 @@ div.load-file-layer-root {
margin-bottom: 5px;
font-size: 1.5em;
}
}
}
div.load-file-layer-root.light {
background-color: rgba($color: #FFFFFF, $alpha: .75);
background-color: rgba($color: #FFFFFF, $alpha: .6);
div.load-file-layer {
border: 2px dashed $lt-font-color-normal-light;
}
}
div.load-file-layer-root.dark {
background-color: rgba($color: #000000, $alpha: .75);
background-color: rgba($color: #000000, $alpha: .6);
div.load-file-layer {
border: 2px dashed $lt-font-color-normal-dark;
}
}

View File

@ -1,16 +1,20 @@
import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup";
import { Localization } from "@Component/Localization/Localization";
import { FontLevel, Theme } from "@Component/Theme/Theme";
import { Status, useStatus, IMixinStatusProps } from "@Context/Status";
import { Icon } from "@fluentui/react";
import { Component, ReactNode } from "react";
import { FunctionComponent } from "react";
import { useDrop } from 'react-dnd'
import { NativeTypes } from "react-dnd-html5-backend"
import "./LoadFile.scss";
class LoadFile extends Component {
const DragFileMask: FunctionComponent = () => {
private renderMask() {
return <Theme
className="load-file-layer-root"
fontLevel={FontLevel.normal}
>
<div className="load-file-layer">
<div className="drag-icon">
<Icon iconName="KnowledgeArticle"/>
</div>
@ -20,12 +24,119 @@ class LoadFile extends Component {
<div className="drag-intro">
<Localization i18nKey="Info.Hint.Load.File.Intro"/>
</div>
</div>
</Theme>;
}
public render(): ReactNode {
return <></>;
}
}
async function fileChecker(status: Status, file?: File) {
if (!status) return undefined;
return new Promise((r, j) => {
// 检查文件存在性
if (!file) {
status.popup.showPopup(ConfirmPopup, {
infoI18n: "Popup.Load.Save.Error.Empty",
titleI18N: "Popup.Load.Save.Title",
yesI18n: "Popup.Load.Save.confirm"
});
return j();
}
// 检测拓展名
let extendName = (file.name.match(/\.(\w+)$/) ?? [])[1];
if (extendName !== "ltss") {
status.popup.showPopup(ConfirmPopup, {
infoI18n: "Popup.Load.Save.Error.Type",
infoI18nOption: { ext: extendName },
titleI18N: "Popup.Load.Save.Title",
yesI18n: "Popup.Load.Save.confirm"
});
return j();
}
// 文件读取
let fileReader = new FileReader();
fileReader.readAsText(file);
fileReader.onload = () => {
const loadFunc = () => {
// 进行转换
let errorMessage = status.archive.load(status.model, fileReader.result as string, file.name, file.path);
if (errorMessage) {
status.popup.showPopup(ConfirmPopup, {
infoI18n: "Popup.Load.Save.Error.Parse",
infoI18nOption: { why: errorMessage },
titleI18N: "Popup.Load.Save.Title",
yesI18n: "Popup.Load.Save.confirm"
});
j();
}
else {
r(undefined);
}
}
// 如果保存进行提示
if (!status.archive.isSaved) {
status.popup.showPopup(ConfirmPopup, {
infoI18n: "Popup.Load.Save.Overwrite.Info",
titleI18N: "Popup.Load.Save.Title",
yesI18n: "Popup.Load.Save.Overwrite",
noI18n: "Popup.Action.No",
red: "yes",
yes: () => {
loadFunc();
},
no: () => {
j();
}
});
}
else {
loadFunc();
}
}
fileReader.onerror = () => {
status.popup.showPopup(ConfirmPopup, {
infoI18n: "Popup.Load.Save.Error.Parse",
infoI18nOption: { why: "Unknown error" },
titleI18N: "Popup.Load.Save.Title",
yesI18n: "Popup.Load.Save.confirm"
});
j();
}
});
}
const LoadFileView: FunctionComponent<IMixinStatusProps> = (props) => {
const [{ isOver }, drop] = useDrop(() => ({
accept: NativeTypes.FILE,
drop: (item: { files: File[] }) => {
if (props.status) {
fileChecker(props.status, item.files[0]).catch((e) => undefined);
}
},
collect: (monitor) => ({
isOver: monitor.isOver()
})
}));
return <>
{
isOver ? <DragFileMask/> : null
}
<div className="load-file-app-root" ref={drop}>
{props.children}
</div>
</>
}
const LoadFile = useStatus(LoadFileView);
export { LoadFile };

View File

@ -1,8 +1,9 @@
import { FunctionComponent, useEffect } from "react";
import * as download from "downloadjs";
import { useSetting, IMixinSettingProps, Platform } from "@Context/Setting";
import { useStatus, IMixinStatusProps } from "@Context/Status";
import { useElectron, IMixinElectronProps } from "@Context/Electron";
import { I18N } from "@Component/Localization/Localization";
import * as download from "downloadjs";
interface IFileInfo {
fileName: string;
@ -21,7 +22,7 @@ interface ICallBackProps {
then: () => any;
}
const ArchiveSaveDownloadView: FunctionComponent<IFileInfo & ICallBackProps> = function ArchiveSave(props) {
const ArchiveSaveDownloadView: FunctionComponent<IFileInfo & ICallBackProps> = function ArchiveSaveDownloadView(props) {
const runner = async () => {
const file = await props.fileData();
@ -38,6 +39,47 @@ const ArchiveSaveDownloadView: FunctionComponent<IFileInfo & ICallBackProps> = f
const ArchiveSaveDownload = ArchiveSaveDownloadView;
const ArchiveSaveFsView: FunctionComponent<IFileInfo & ICallBackProps & IMixinElectronProps & IMixinSettingProps & IMixinStatusProps> =
function ArchiveSaveFsView(props) {
const runner = async () => {
const file = await props.fileData();
setTimeout(() => {
if (props.electron) {
props.electron.fileSave(
file,
I18N(props, "Popup.Load.Save.Select.File.Name"),
I18N(props, "Popup.Load.Save.Select.Path.Title"),
I18N(props, "Popup.Load.Save.Select.Path.Button"),
props.fileUrl
);
}
}, 100);
}
const saveEvent = ({name, url, success} : {name: string, url: string, success: boolean}) => {
if (success && props.status) {
props.status.archive.fileUrl = url;
props.status.archive.fileName = name;
props.status.archive.isNewFile = false;
props.status.archive.emit("fileSave", props.status.archive);
}
props.then();
}
useEffect(() => {
runner();
props.electron?.on("fileSave", saveEvent);
return () => {
props.electron?.off("fileSave", saveEvent);
};
}, []);
return <></>;
}
const ArchiveSaveFs = useSetting(useElectron(useStatus(ArchiveSaveFsView)));
/**
*
*/
@ -81,7 +123,7 @@ const ArchiveSaveView: FunctionComponent<IMixinSettingProps & IMixinStatusProps
{
props.setting?.platform === Platform.web ?
<ArchiveSaveDownload {...fileData} then={callBack}/> :
<></>
<ArchiveSaveFs {...fileData} then={callBack}/>
}
</>
}

View File

@ -1,4 +1,5 @@
import { createContext } from "react";
import { Emitter } from "@Model/Emitter";
import { superConnect, superConnectWithEvent } from "@Context/Context";
import { ISimulatorAPI, IApiEmitterEvent } from "@Electron/SimulatorAPI";
@ -6,6 +7,25 @@ interface IMixinElectronProps {
electron?: ISimulatorAPI;
}
const getElectronAPI: () => ISimulatorAPI = () => {
const API = (window as any).API;
const mapperEmitter = new Emitter();
const ClassElectron: new () => ISimulatorAPI = function (this: Record<string, any>) {
this.resetAll = () => mapperEmitter.resetAll();
this.reset = (type: string) => mapperEmitter.reset(type);
this.on = (type: string, handel: any) => mapperEmitter.on(type, handel);
this.off = (type: string, handel: any) => mapperEmitter.off(type, handel);
this.emit = (type: string, data: any) => mapperEmitter.emit(type, data);
} as any;
ClassElectron.prototype = API;
// Emitter Mapper
API.mapEmit((...p: any) => {
mapperEmitter.emit(...p);
});
return new ClassElectron();
}
const ElectronContext = createContext<ISimulatorAPI>((window as any).API ?? {} as ISimulatorAPI);
ElectronContext.displayName = "Electron";
@ -19,4 +39,4 @@ const useElectron = superConnect<ISimulatorAPI>(ElectronConsumer, "electron");
const useElectronWithEvent = superConnectWithEvent<ISimulatorAPI, IApiEmitterEvent>(ElectronConsumer, "electron");
export { useElectron, ElectronProvider, IMixinElectronProps, ISimulatorAPI, useElectronWithEvent };
export { useElectron, ElectronProvider, IMixinElectronProps, ISimulatorAPI, useElectronWithEvent, getElectronAPI };

View File

@ -161,6 +161,11 @@ class Status extends Emitter<IStatusEvent> {
this.emit("labelChange");
this.emit("behaviorChange");
// 清除焦点对象
this.setBehaviorObject();
this.setFocusObject(new Set());
this.setLabelObject();
// 映射
this.emit("fileLoad");
});

View File

@ -1,6 +1,7 @@
import { app, BrowserWindow, ipcMain } from "electron";
import { app, BrowserWindow, ipcMain, dialog } from "electron";
import { Service } from "@Service/Service";
import { join as pathJoin } from "path";
import { writeFile } from "fs";
const ENV = process.env ?? {};
class ElectronApp {
@ -55,6 +56,7 @@ class ElectronApp {
this.simulatorWindow.loadURL(this.serviceUrl + (ENV.LIVING_TOGETHER_WEB_PATH ?? "/resources/app.asar/"));
this.handelSimulatorWindowBehavior();
this.handelFileChange();
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
@ -90,6 +92,56 @@ class ElectronApp {
this.simulatorWindow?.on("maximize", sendWindowsChangeMessage);
this.simulatorWindow?.on("unmaximize", sendWindowsChangeMessage);
}
private handelFileChange() {
// 文件保存
const saveFile = async (path: string, text: string) => {
return new Promise((r) => {
writeFile(path ?? "", text, {}, (e) => {
this.simulatorWindow?.webContents.send(
"windows.EndFileSave",
(path.match(/.+(\/|\\)(.+)$/) ?? [])[2],
path, !e
);
r(undefined);
});
})
};
// 处理文件保存事件
ipcMain.on("windows.fileSave",
(_, text: string, name: string, title: string, button: string, url?: string) => {
// 如果没有路径,询问新的路径
if (url) {
saveFile(url, text);
}
// 询问保存位置
else {
dialog.showSaveDialog(this.simulatorWindow!, {
title: title,
buttonLabel: button,
filters: [
{ name: name, extensions: ["ltss"] }
]
}).then(res => {
// 用户选择后继续保存
if (!res.canceled && res.filePath) {
saveFile(res.filePath, text);
} else {
this.simulatorWindow?.webContents.send(
"windows.EndFileSave",
undefined, undefined, false
);
}
});
}
}
);
}
}
new ElectronApp().runMainThread();

View File

@ -2,6 +2,7 @@ import { Emitter } from "@Model/Emitter";
type IApiEmitterEvent = {
windowsSizeStateChange: void;
fileSave: {success: boolean, name: string, url: string};
}
interface ISimulatorAPI extends Emitter<IApiEmitterEvent> {
@ -30,6 +31,11 @@ interface ISimulatorAPI extends Emitter<IApiEmitterEvent> {
*
*/
minimize: () => void;
/**
*
*/
fileSave: (text: string, name: string, title: string, button: string, url?: string) => void;
}
export { ISimulatorAPI, IApiEmitterEvent }

View File

@ -1,23 +1,11 @@
import { contextBridge, ipcRenderer } from "electron";
import { ISimulatorAPI } from "@Electron/SimulatorAPI"
import { ISimulatorAPI } from "@Electron/SimulatorAPI";
const emitterMap: Array<[key: string, value: Function[]]> = [];
const queryEmitter = (key: string) => {
let res: (typeof emitterMap)[0] | undefined;
emitterMap.forEach((item) => {
if (item[0] === key) res = item;
});
const emitterMap: { fn?: Function } = { fn: undefined };
if (res) {
if (Array.isArray(res[1])) return res[1];
res[1] = [];
return res[1];
}
else {
res = [key, []];
emitterMap.push(res);
return res[1];
const emit = (type: string, evt?: any) => {
if (emitterMap.fn) {
emitterMap.fn(type, evt);
}
}
@ -43,22 +31,19 @@ const API: ISimulatorAPI = {
ipcRenderer.send("windows.minimize");
},
all: new Map() as any,
resetAll: () => emitterMap.splice(0),
reset: (type) => queryEmitter(type).splice(0),
on: (type, handler) => queryEmitter(type).push(handler),
off: (type, handler) => {
const handlers = queryEmitter(type);
handlers.splice(handlers.indexOf(handler!) >>> 0, 1);
fileSave(text: string, name: string, title: string, button: string, url?: string) {
ipcRenderer.send("windows.fileSave", text, name, title, button, url);
},
emit: ((type: string, evt: any) => {
queryEmitter(type).slice().map((handler: any) => { handler(evt) });
}) as any,
}
mapEmit: (fn: Function) => { emitterMap.fn = fn },
} as any;
ipcRenderer.on("windows.windowsSizeStateChange", () => {
API.emit("windowsSizeStateChange");
emit("windowsSizeStateChange");
});
ipcRenderer.on("windows.EndFileSave", (_, name: string, url: string, success: boolean) => {
emit("fileSave", {name, url, success});
});
contextBridge.exposeInMainWorld("API", API);

View File

@ -65,6 +65,16 @@ const EN_US = {
"Popup.Delete.Behavior.Confirm": "Are you sure you want to delete this behavior? The behavior is deleted and cannot be recalled.",
"Popup.Restore.Behavior.Confirm": "Are you sure you want to reset all parameters of this behavior? This operation cannot be recalled.",
"Popup.Setting.Title": "Preferences setting",
"Popup.Load.Save.Title": "Load save",
"Popup.Load.Save.confirm": "Got it",
"Popup.Load.Save.Overwrite": "Overwrite and continue",
"Popup.Load.Save.Overwrite.Info": "The current workspace will be overwritten after the archive is loaded, and all unsaved progress will be lost. Are you sure you want to continue?",
"Popup.Load.Save.Error.Empty": "File information acquisition error. The file has been lost or moved.",
"Popup.Load.Save.Error.Type": "The file with extension name \"{ext}\" cannot be loaded temporarily",
"Popup.Load.Save.Error.Parse": "Archive parsing error, detailed reason: \n{why}",
"Popup.Load.Save.Select.Path.Title": "Please select an archive location",
"Popup.Load.Save.Select.Path.Button": "Save",
"Popup.Load.Save.Select.File.Name": "Living Together Simulator Save",
"Popup.Add.Behavior.Title": "Add behavior",
"Popup.Add.Behavior.Action.Add": "Add all select behavior",
"Popup.Add.Behavior.Select.Counter": "Selected {count} behavior",

View File

@ -65,6 +65,16 @@ const ZH_CN = {
"Popup.Delete.Behavior.Confirm": "你确定要删除这个行为吗?行为被删除将无法撤回。",
"Popup.Restore.Behavior.Confirm": "你确定要重置此行为的全部参数吗?此操作无法撤回。",
"Popup.Setting.Title": "首选项设置",
"Popup.Load.Save.Title": "加载存档",
"Popup.Load.Save.confirm": "我知道了",
"Popup.Load.Save.Overwrite": "覆盖并继续",
"Popup.Load.Save.Overwrite.Info": "存档加载后将覆盖当前工作区,未保存的进度将全部丢失,确定要继续吗?",
"Popup.Load.Save.Error.Empty": "文件信息获取错误,文件已丢失或已被移动",
"Popup.Load.Save.Error.Type": "暂时无法加载拓展名为 \"{ext}\" 的文件",
"Popup.Load.Save.Error.Parse": "存档解析错误,详细原因: \n{why}",
"Popup.Load.Save.Select.Path.Title": "请选择存档保存位置",
"Popup.Load.Save.Select.Path.Button": "保存",
"Popup.Load.Save.Select.File.Name": "群生共进存档",
"Popup.Add.Behavior.Title": "添加行为",
"Popup.Add.Behavior.Action.Add": "添加全部选中行为",
"Popup.Add.Behavior.Select.Counter": "已选择 {count} 个行为",

View File

@ -37,7 +37,7 @@ class Archive extends Emitter<IArchiveEvent> {
/**
*
*/
public isSaved: boolean = false;
public isSaved: boolean = true;
/**
*
@ -100,7 +100,7 @@ class Archive extends Emitter<IArchiveEvent> {
// 解析为 JSON 对象
const archive: IArchiveObject = JSON.parse(data);
console.log(archive);
// console.log(archive);
// 实例化全部对象
const objectPool: CtrlObject[] = [];
@ -254,7 +254,7 @@ class Archive extends Emitter<IArchiveEvent> {
*
* @return Model
*/
public load(model: Model, data: string): string | undefined {
public load(model: Model, data: string, name: string, url?: string): string | undefined {
try {
this.loadArchiveIntoModel(model, data);
@ -262,8 +262,12 @@ class Archive extends Emitter<IArchiveEvent> {
return e as string;
}
this.isSaved = true;
this.emit("fileLoad", this);
this.fileName = name;
this.isSaved = true;
this.isNewFile = false;
this.fileUrl = url;
this.emit("fileSave", this);
};
public constructor() {

View File

@ -1,18 +1,20 @@
import { Component, ReactNode } from "react";
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import { SettingProvider, Setting, Platform } from "@Context/Setting";
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
import { ISimulatorAPI } from "@Electron/SimulatorAPI";
import { StatusProvider, Status } from "@Context/Status";
import { ElectronProvider } from "@Context/Electron";
import { ElectronProvider, getElectronAPI } from "@Context/Electron";
import { ClassicRenderer } from "@GLRender/ClassicRenderer";
import { initializeIcons } from '@fluentui/font-icons-mdl2';
import { RootContainer } from "@Component/Container/RootContainer";
import { LayoutDirection } from "@Context/Layout";
import { CommandBar } from "@Component/CommandBar/CommandBar";
import { LoadFile } from "@Component/LoadFile/LoadFile";
import { HeaderBar } from "@Component/HeaderBar/HeaderBar";
import { Popup } from "@Component/Popup/Popup";
import { Entry } from "../Entry/Entry";
import { Group } from "@Model/Group";
import "./SimulatorDesktop.scss";
initializeIcons("./font-icon/");
@ -47,25 +49,12 @@ class SimulatorDesktop extends Component {
this.status.bindRenderer(classicRender);
this.status.setting = this.setting;
const randomPosition = (group: Group) => {
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;
})
};
(window as any).LT = {
status: this.status,
setting: this.setting
};
this.electron = {} as ISimulatorAPI;
if ((window as any).API) {
this.electron = (window as any).API;
} else {
console.error("SimulatorDesktop: Can't find electron API");
}
this.electron = getElectronAPI();
}
public componentDidMount() {
@ -102,7 +91,9 @@ class SimulatorDesktop extends Component {
return <SettingProvider value={this.setting}>
<StatusProvider value={this.status}>
<ElectronProvider value={this.electron}>
<DndProvider backend={HTML5Backend}>
{this.renderContent()}
</DndProvider>
</ElectronProvider>
</StatusProvider>
</SettingProvider>
@ -115,6 +106,7 @@ class SimulatorDesktop extends Component {
fontLevel={FontLevel.Level3}
>
<Popup/>
<LoadFile>
<HeaderBar height={35}/>
<div className="app-root-space" style={{
height: `calc( 100% - ${35}px)`
@ -122,6 +114,7 @@ class SimulatorDesktop extends Component {
<CommandBar/>
<RootContainer/>
</div>
</LoadFile>
</Theme>
}
}

View File

@ -1,4 +1,6 @@
import { Component, ReactNode } from "react";
import { DndProvider } from 'react-dnd'
import { HTML5Backend } from 'react-dnd-html5-backend'
import { SettingProvider, Setting, Platform } from "@Context/Setting";
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
import { StatusProvider, Status } from "@Context/Status";
@ -7,12 +9,10 @@ import { initializeIcons } from '@fluentui/font-icons-mdl2';
import { RootContainer } from "@Component/Container/RootContainer";
import { LayoutDirection } from "@Context/Layout";
import { LoadFile } from "@Component/LoadFile/LoadFile";
import { AllBehaviors, getBehaviorById } from "@Behavior/Behavior";
import { CommandBar } from "@Component/CommandBar/CommandBar";
import { HeaderBar } from "@Component/HeaderBar/HeaderBar";
import { Popup } from "@Component/Popup/Popup";
import { Entry } from "../Entry/Entry";
import { Group } from "@Model/Group";
import "./SimulatorWeb.scss";
initializeIcons("https://img.mrkbear.com/fabric-cdn-prod_20210407.001/");
@ -42,118 +42,6 @@ class SimulatorWeb extends Component {
this.status.bindRenderer(classicRender);
this.status.setting = this.setting;
const randomPosition = (group: Group) => {
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;
})
};
// 测试代码
if (false) {
let group = this.status.newGroup();
let range = this.status.newRange();
range.color = [.1, .5, .9];
group.new(100);
group.color = [.8, .1, .6];
randomPosition(group);
this.status.model.update(0);
this.status.newLabel().name = "New Label";
this.status.newLabel().name = "Test Label 01";
let template = this.status.model.addBehavior(AllBehaviors[0]);
template.name = "Template"; template.color = [150, 20, 220];
let dynamic = this.status.model.addBehavior(AllBehaviors[1]);
dynamic.name = "Dynamic"; dynamic.color = [250, 200, 80];
let brownian = this.status.model.addBehavior(AllBehaviors[2]);
brownian.name = "Brownian"; brownian.color = [200, 80, 250];
let boundary = this.status.model.addBehavior(AllBehaviors[3]);
boundary.name = "Boundary"; boundary.color = [80, 200, 250];
boundary.parameter.range.picker = this.status.model.allRangeLabel;
group.addBehavior(template);
group.addBehavior(dynamic);
group.addBehavior(brownian);
group.addBehavior(boundary);
}
// 鱼群模型测试
if (false) {
let fish1 = this.status.newGroup();
let fish2 = this.status.newGroup();
let shark = this.status.newGroup();
let range = this.status.newRange();
range.displayName = "Experimental site";
range.color = [.8, .1, .6];
fish1.new(100);
fish1.displayName = "Fish A";
fish1.color = [.1, .5, .9];
randomPosition(fish1);
fish2.new(50);
fish2.displayName = "Fish B";
fish2.color = [.3, .2, .9];
randomPosition(fish2);
shark.new(3);
shark.displayName = "Shark";
shark.color = [.8, .2, .3];
shark.renderParameter.size = 100;
shark.renderParameter.shape = "5";
randomPosition(shark);
this.status.model.update(0);
let fishLabel = this.status.newLabel();
fishLabel.name = "Fish";
fish1.addLabel(fishLabel);
fish2.addLabel(fishLabel);
let template = this.status.model.addBehavior(getBehaviorById("Template"));
template.name = "Template"; template.color = [150, 20, 220];
let dynamicFish = this.status.model.addBehavior(getBehaviorById("PhysicsDynamics"));
dynamicFish.name = "Dynamic Fish"; dynamicFish.color = [250, 200, 80];
let dynamicShark = this.status.model.addBehavior(getBehaviorById("PhysicsDynamics"));
dynamicShark.name = "Dynamic Shark"; dynamicShark.color = [250, 200, 80];
dynamicShark.parameter.maxAcceleration = 8.5;
dynamicShark.parameter.maxVelocity = 15.8;
dynamicShark.parameter.resistance = 3.6;
let brownian = this.status.model.addBehavior(getBehaviorById("Brownian"));
brownian.name = "Brownian"; brownian.color = [200, 80, 250];
let boundary = this.status.model.addBehavior(getBehaviorById("BoundaryConstraint"));
boundary.name = "Boundary"; boundary.color = [80, 200, 250];
boundary.parameter.range.picker = this.status.model.allRangeLabel;
let tracking = this.status.model.addBehavior(getBehaviorById("Tracking"));
tracking.name = "Tracking"; tracking.color = [80, 200, 250];
tracking.parameter.target.picker = fishLabel;
let attacking = this.status.model.addBehavior(getBehaviorById("ContactAttacking"));
attacking.name = "Contact Attacking"; attacking.color = [120, 100, 250];
attacking.parameter.target.picker = fishLabel;
fish1.addBehavior(dynamicFish);
fish1.addBehavior(brownian);
fish1.addBehavior(boundary);
fish2.addBehavior(dynamicFish);
fish2.addBehavior(brownian);
fish2.addBehavior(boundary);
shark.addBehavior(dynamicShark);
shark.addBehavior(boundary);
shark.addBehavior(tracking);
shark.addBehavior(attacking);
setTimeout(() => {
this.status.model.updateBehaviorParameter();
}, 200)
}
(window as any).LT = {
status: this.status,
setting: this.setting
@ -193,7 +81,9 @@ class SimulatorWeb extends Component {
public render(): ReactNode {
return <SettingProvider value={this.setting}>
<StatusProvider value={this.status}>
<DndProvider backend={HTML5Backend}>
{this.renderContent()}
</DndProvider>
</StatusProvider>
</SettingProvider>
}
@ -204,8 +94,8 @@ class SimulatorWeb extends Component {
backgroundLevel={BackgroundLevel.Level5}
fontLevel={FontLevel.Level3}
>
<LoadFile/>
<Popup/>
<LoadFile>
<HeaderBar height={45}/>
<div className="app-root-space" style={{
height: `calc( 100% - ${45}px)`
@ -213,6 +103,7 @@ class SimulatorWeb extends Component {
<CommandBar/>
<RootContainer/>
</div>
</LoadFile>
</Theme>
}
}