Add electron entry #40
@ -58,10 +58,6 @@ const Entry = () => ({
|
|||||||
import: source("./GLRender/ClassicRenderer.ts")
|
import: source("./GLRender/ClassicRenderer.ts")
|
||||||
},
|
},
|
||||||
|
|
||||||
livingTogether: {
|
|
||||||
import: source("./livingTogether.ts")
|
|
||||||
},
|
|
||||||
|
|
||||||
LaboratoryPage: {
|
LaboratoryPage: {
|
||||||
import: source("./Page/Laboratory/Laboratory.tsx"),
|
import: source("./Page/Laboratory/Laboratory.tsx"),
|
||||||
dependOn: ["Model", "GLRender"]
|
dependOn: ["Model", "GLRender"]
|
||||||
@ -70,7 +66,26 @@ const Entry = () => ({
|
|||||||
SimulatorWeb: {
|
SimulatorWeb: {
|
||||||
import: source("./Page/SimulatorWeb/SimulatorWeb.tsx"),
|
import: source("./Page/SimulatorWeb/SimulatorWeb.tsx"),
|
||||||
dependOn: ["Model", "GLRender"]
|
dependOn: ["Model", "GLRender"]
|
||||||
}
|
},
|
||||||
|
|
||||||
|
SimulatorDesktop: {
|
||||||
|
import: source("./Page/SimulatorDesktop/SimulatorDesktop.tsx"),
|
||||||
|
dependOn: ["Model", "GLRender"]
|
||||||
|
},
|
||||||
|
|
||||||
|
Service: {
|
||||||
|
import: source("./Service/Service.ts")
|
||||||
|
},
|
||||||
|
|
||||||
|
ServiceRunner: {
|
||||||
|
import: source("./Service/Runner.ts"),
|
||||||
|
dependOn: ["Service"]
|
||||||
|
},
|
||||||
|
|
||||||
|
Electron: {
|
||||||
|
import: source("./Electron/Electron.ts"),
|
||||||
|
dependOn: ["Service"]
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
56
config/webpack.desktop.js
Normal file
56
config/webpack.desktop.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
const {
|
||||||
|
Entry, Output, resolve, build,
|
||||||
|
TypeScriptRules, ScssRules,
|
||||||
|
HTMLPage, CssPlugin, AutoFixCssPlugin
|
||||||
|
} = require("./webpack.common");
|
||||||
|
|
||||||
|
const AllEntry = Entry();
|
||||||
|
|
||||||
|
module.exports = (env) => {
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
|
||||||
|
entry: {
|
||||||
|
GLRender: AllEntry.GLRender,
|
||||||
|
Model: AllEntry.Model,
|
||||||
|
SimulatorDesktop: AllEntry.SimulatorDesktop
|
||||||
|
},
|
||||||
|
|
||||||
|
output: Output("[name].[contenthash].js"),
|
||||||
|
devtool: 'source-map',
|
||||||
|
mode: "development",
|
||||||
|
resolve: resolve(),
|
||||||
|
|
||||||
|
optimization: {
|
||||||
|
runtimeChunk: 'single',
|
||||||
|
chunkIds: 'named',
|
||||||
|
moduleIds: 'named',
|
||||||
|
splitChunks: {
|
||||||
|
chunks: 'all',
|
||||||
|
minSize: 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
TypeScriptRules(),
|
||||||
|
ScssRules()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
HTMLPage("index.html", "Living Together | Simulator"),
|
||||||
|
CssPlugin(),
|
||||||
|
AutoFixCssPlugin()
|
||||||
|
],
|
||||||
|
|
||||||
|
devServer: {
|
||||||
|
static: {
|
||||||
|
directory: build("./"),
|
||||||
|
},
|
||||||
|
port: 12000,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
44
config/webpack.electron.js
Normal file
44
config/webpack.electron.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
const { Entry, Output, resolve, TypeScriptRules } = require("./webpack.common");
|
||||||
|
const nodeExternals = require("webpack-node-externals");
|
||||||
|
|
||||||
|
const AllEntry = Entry();
|
||||||
|
|
||||||
|
module.exports = (env) => {
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
|
||||||
|
entry: {
|
||||||
|
Service: AllEntry.Service,
|
||||||
|
Electron: AllEntry.Electron,
|
||||||
|
},
|
||||||
|
|
||||||
|
output: Output("[name].js"),
|
||||||
|
devtool: 'source-map',
|
||||||
|
mode: "development",
|
||||||
|
resolve: resolve(),
|
||||||
|
|
||||||
|
optimization: {
|
||||||
|
splitChunks: {
|
||||||
|
chunks: 'all',
|
||||||
|
minSize: 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
externals: [nodeExternals()],
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
TypeScriptRules()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
node: {
|
||||||
|
__filename: false,
|
||||||
|
__dirname: false
|
||||||
|
},
|
||||||
|
|
||||||
|
target: 'node'
|
||||||
|
};
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
41
config/webpack.service.js
Normal file
41
config/webpack.service.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
const { Entry, Output, resolve, TypeScriptRules } = require("./webpack.common");
|
||||||
|
|
||||||
|
const AllEntry = Entry();
|
||||||
|
|
||||||
|
module.exports = (env) => {
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
|
||||||
|
entry: {
|
||||||
|
Service: AllEntry.Service,
|
||||||
|
ServiceRunner: AllEntry.ServiceRunner,
|
||||||
|
},
|
||||||
|
|
||||||
|
output: Output("[name].js"),
|
||||||
|
devtool: 'source-map',
|
||||||
|
mode: "development",
|
||||||
|
resolve: resolve(),
|
||||||
|
|
||||||
|
optimization: {
|
||||||
|
splitChunks: {
|
||||||
|
chunks: 'all',
|
||||||
|
minSize: 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
TypeScriptRules()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
node: {
|
||||||
|
__filename: false,
|
||||||
|
__dirname: false
|
||||||
|
},
|
||||||
|
|
||||||
|
target: 'node'
|
||||||
|
};
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
2726
package-lock.json
generated
2726
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@ -4,13 +4,30 @@
|
|||||||
"description": "An art interactive works for graduation design.",
|
"description": "An art interactive works for graduation design.",
|
||||||
"main": "./source/LivingTogether.ts",
|
"main": "./source/LivingTogether.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf ./build/*",
|
"clean": "fse emptyDir ./build/",
|
||||||
"hmr-lab": "webpack serve --open --config ./config/webpack.lab.js",
|
"hmr-lab": "webpack serve --open --config ./config/webpack.lab.js",
|
||||||
"build-lab": "npm run clean & webpack --mode development --config ./config/webpack.lab.js",
|
"build-lab": "npm run clean & webpack --mode development --config ./config/webpack.lab.js",
|
||||||
"release-lab": "npm run clean & webpack --mode production --no-devtool --config ./config/webpack.lab.js",
|
"release-lab": "npm run clean & webpack --mode production --no-devtool --config ./config/webpack.lab.js",
|
||||||
"hmr-web": "webpack serve --open --config ./config/webpack.web.js",
|
"hmr-web": "webpack serve --open --config ./config/webpack.web.js",
|
||||||
"build-web": "npm run clean & webpack --mode development --config ./config/webpack.web.js",
|
"build-web": "npm run clean & webpack --mode development --config ./config/webpack.web.js",
|
||||||
"release-web": "npm run clean & webpack --mode production --no-devtool --config ./config/webpack.web.js"
|
"release-web": "npm run clean & webpack --mode production --no-devtool --config ./config/webpack.web.js",
|
||||||
|
"build-desktop-web": "npm run clean & webpack --mode development --config ./config/webpack.desktop.js",
|
||||||
|
"release-desktop-web": "npm run clean & webpack --mode production --no-devtool --config ./config/webpack.desktop.js",
|
||||||
|
"build-service": "webpack --mode development --config ./config/webpack.service.js",
|
||||||
|
"release-service": "webpack --mode production --no-devtool --config ./config/webpack.service.js",
|
||||||
|
"run-service": "node ./build/ServiceRunner.js --run --path ./build --port 12000",
|
||||||
|
"build-run-web": "npm run build-web & npm run build-service & npm run run-service",
|
||||||
|
"release-run-web": "npm run release-web & npm run release-service & npm run run-service",
|
||||||
|
"copy-fluent-icon": "fse mkdirp ./build/font-icon/ && fse emptyDir ./build/font-icon/ && fse copy ./node_modules/@fluentui/font-icons-mdl2/fonts/ ./build/font-icon/",
|
||||||
|
"build-run-desktop-web": "npm run build-desktop-web & npm run copy-fluent-icon & npm run build-service & npm run run-service",
|
||||||
|
"release-run-desktop-web": "npm run release-desktop-web & npm run copy-fluent-icon & npm run release-service & npm run run-service",
|
||||||
|
"skip-electron-ci": "set ELECTRON_SKIP_BINARY_DOWNLOAD=1& npm ci",
|
||||||
|
"build-electron": "webpack --mode development --config ./config/webpack.electron.js",
|
||||||
|
"release-electron": "webpack --mode production --no-devtool --config ./config/webpack.electron.js",
|
||||||
|
"electron-cache": "set ELECTRON_SKIP_BINARY_DOWNLOAD=& set ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/& set ELECTRON_CUSTOM_DIR={{ version }}& node ./node_modules/electron/install.js",
|
||||||
|
"electron": "set LIVING_TOGETHER_BASE_PATH=./build& npx electron ./build/Electron.js",
|
||||||
|
"build-run-electron": "npm run build-desktop-web & npm run copy-fluent-icon & npm run build-electron & npm run electron",
|
||||||
|
"release-run-electron": "npm run release-desktop-web & npm run copy-fluent-icon & npm run release-electron & npm run electron"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"artwork",
|
"artwork",
|
||||||
@ -21,26 +38,31 @@
|
|||||||
"author": "MrKBear",
|
"author": "MrKBear",
|
||||||
"license": "GPL",
|
"license": "GPL",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@atao60/fse-cli": "^0.1.7",
|
||||||
|
"@types/detect-port": "^1.3.2",
|
||||||
"@types/react": "^17.0.38",
|
"@types/react": "^17.0.38",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
"css-loader": "^6.5.1",
|
"css-loader": "^6.5.1",
|
||||||
|
"electron": "^18.0.4",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"html-webpack-plugin": "^5.5.0",
|
||||||
"mini-css-extract-plugin": "^2.4.6",
|
"mini-css-extract-plugin": "^2.4.6",
|
||||||
"node-sass": "^7.0.1",
|
"node-sass": "^7.0.1",
|
||||||
"postcss-loader": "^6.2.1",
|
"postcss-loader": "^6.2.1",
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"sass-loader": "^12.4.0",
|
"sass-loader": "^12.4.0",
|
||||||
"ts-loader": "^9.2.6",
|
"ts-loader": "^9.2.6",
|
||||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"webpack": "^5.65.0",
|
"webpack": "^5.65.0",
|
||||||
"webpack-cli": "^4.9.1",
|
"webpack-cli": "^4.9.1",
|
||||||
"webpack-dev-server": "^4.7.2"
|
"webpack-dev-server": "^4.7.2",
|
||||||
|
"webpack-node-externals": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluentui/react": "^8.56.0",
|
"@fluentui/react": "^8.56.0",
|
||||||
"@juggle/resize-observer": "^3.3.1",
|
"@juggle/resize-observer": "^3.3.1",
|
||||||
|
"detect-port": "^1.3.0",
|
||||||
|
"express": "^4.17.3",
|
||||||
"gl-matrix": "^3.4.3",
|
"gl-matrix": "^3.4.3",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^17.0.2"
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
|
@import "../Theme/Theme.scss";
|
||||||
|
|
||||||
div.header-bar {
|
div.header-bar {
|
||||||
padding: 0 3px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
// 在 Electron 中用于拖拽窗口
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
|
||||||
|
div.title {
|
||||||
|
padding-left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
div.title > i, div.fps-view > i {
|
div.title > i, div.fps-view > i {
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
@ -24,4 +32,46 @@ div.header-bar {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.header-windows-action {
|
||||||
|
height: 100%;
|
||||||
|
width: 135px;
|
||||||
|
min-width: 135px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
// 在 Electron 中用于拖拽窗口
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
|
div.action-button {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.action-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.action-button.close-button:hover {
|
||||||
|
color: #FFFFFF !important;
|
||||||
|
background-color: $lt-red !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.header-windows-action.light {
|
||||||
|
|
||||||
|
div.action-button:hover {
|
||||||
|
background-color: rgba($color: #000000, $alpha: .1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.header-windows-action.dark {
|
||||||
|
|
||||||
|
div.action-button:hover {
|
||||||
|
background-color: rgba($color: #FFFFFF, $alpha: .1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, ReactNode } from "react";
|
import { Component, ReactNode } from "react";
|
||||||
import { Icon } from '@fluentui/react/lib/Icon';
|
import { Icon } from '@fluentui/react/lib/Icon';
|
||||||
import { useStatus, IMixinStatusProps } from "@Context/Status";
|
import { useStatusWithEvent, useStatus, IMixinStatusProps } from "@Context/Status";
|
||||||
import { useSetting, IMixinSettingProps } from "@Context/Setting";
|
import { useSettingWithEvent, IMixinSettingProps, Platform } from "@Context/Setting";
|
||||||
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
|
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
|
||||||
import { LocalizationTooltipHost } from "@Component/Localization/LocalizationTooltipHost";
|
import { LocalizationTooltipHost } from "@Component/Localization/LocalizationTooltipHost";
|
||||||
import { I18N } from "@Component/Localization/Localization";
|
import { I18N } from "@Component/Localization/Localization";
|
||||||
@ -11,38 +11,48 @@ interface IHeaderBarProps {
|
|||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeaderBarState {
|
interface IHeaderFpsViewState {
|
||||||
renderFps: number;
|
renderFps: number;
|
||||||
physicsFps: number;
|
physicsFps: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 头部信息栏
|
|
||||||
*/
|
|
||||||
@useSetting
|
|
||||||
@useStatus
|
@useStatus
|
||||||
class HeaderBar extends Component<
|
class HeaderFpsView extends Component<IMixinStatusProps & IMixinSettingProps, IHeaderFpsViewState> {
|
||||||
IHeaderBarProps & IMixinStatusProps & IMixinSettingProps,
|
|
||||||
HeaderBarState
|
|
||||||
> {
|
|
||||||
|
|
||||||
public state = {
|
public state = {
|
||||||
renderFps: 0,
|
renderFps: 0,
|
||||||
physicsFps: 0,
|
physicsFps: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
private changeListener = () => {
|
private updateTime: number = 0;
|
||||||
this.forceUpdate();
|
|
||||||
|
private renderFpsCalc: (t: number) => void = () => {};
|
||||||
|
private physicsFpsCalc: (t: number) => void = () => {};
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
const { status } = this.props;
|
||||||
|
this.renderFpsCalc = this.createFpsCalc("renderFps");
|
||||||
|
this.physicsFpsCalc = this.createFpsCalc("physicsFps");
|
||||||
|
if (status) {
|
||||||
|
status.on("physicsLoop", this.physicsFpsCalc);
|
||||||
|
status.on("renderLoop", this.renderFpsCalc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateTime: number = 0;
|
public componentWillUnmount() {
|
||||||
|
const { status } = this.props;
|
||||||
|
if (status) {
|
||||||
|
status.off("physicsLoop", this.physicsFpsCalc);
|
||||||
|
status.off("renderLoop", this.renderFpsCalc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private createFpsCalc(type: "renderFps" | "physicsFps") {
|
private createFpsCalc(type: "renderFps" | "physicsFps") {
|
||||||
return (t: number) => {
|
return (t: number) => {
|
||||||
if (t === 0) {
|
if (t === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let newState: HeaderBarState = {} as any;
|
let newState: IHeaderFpsViewState = {} as any;
|
||||||
newState[type] = 1 / t;
|
newState[type] = 1 / t;
|
||||||
if (this.updateTime > 20) {
|
if (this.updateTime > 20) {
|
||||||
this.updateTime = 0;
|
this.updateTime = 0;
|
||||||
@ -52,51 +62,59 @@ class HeaderBar extends Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderFpsCalc: (t: number) => void = () => {};
|
public render() {
|
||||||
private physicsFpsCalc: (t: number) => void = () => {};
|
|
||||||
|
|
||||||
public componentDidMount() {
|
|
||||||
const { setting, status } = this.props;
|
|
||||||
this.renderFpsCalc = this.createFpsCalc("renderFps");
|
|
||||||
this.physicsFpsCalc = this.createFpsCalc("physicsFps");
|
|
||||||
if (setting) {
|
|
||||||
setting.on("language", this.changeListener);
|
|
||||||
}
|
|
||||||
if (status) {
|
|
||||||
status.archive.on("save", this.changeListener);
|
|
||||||
status.on("physicsLoop", this.physicsFpsCalc);
|
|
||||||
status.on("renderLoop", this.renderFpsCalc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentWillUnmount() {
|
|
||||||
const { setting, status } = this.props;
|
|
||||||
if (setting) {
|
|
||||||
setting.off("language", this.changeListener);
|
|
||||||
}
|
|
||||||
if (status) {
|
|
||||||
status.archive.off("save", this.changeListener);
|
|
||||||
status.off("physicsLoop", this.physicsFpsCalc);
|
|
||||||
status.off("renderLoop", this.renderFpsCalc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): ReactNode {
|
|
||||||
const { status } = this.props;
|
|
||||||
let fileName: string = "";
|
|
||||||
let isNewFile: boolean = true;
|
|
||||||
let isSaved: boolean = false;
|
|
||||||
if (status) {
|
|
||||||
isNewFile = status.archive.isNewFile;
|
|
||||||
fileName = status.archive.fileName ?? "";
|
|
||||||
isSaved = status.archive.isSaved;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fpsInfo = {
|
const fpsInfo = {
|
||||||
renderFps: Math.floor(this.state.renderFps).toString(),
|
renderFps: Math.floor(this.state.renderFps).toString(),
|
||||||
physicsFps: Math.floor(this.state.physicsFps).toString()
|
physicsFps: Math.floor(this.state.physicsFps).toString()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return <LocalizationTooltipHost i18nKey="Header.Bar.Fps.Info" options={fpsInfo}>
|
||||||
|
<div className="fps-view">
|
||||||
|
<Icon iconName="SpeedHigh"></Icon>
|
||||||
|
<span>{I18N(this.props, "Header.Bar.Fps", fpsInfo)}</span>
|
||||||
|
</div>
|
||||||
|
</LocalizationTooltipHost>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HeaderWindowsAction extends Component {
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return <Theme className="header-windows-action">
|
||||||
|
<div className="action-button">
|
||||||
|
<Icon iconName="ChromeMinimize"/>
|
||||||
|
</div>
|
||||||
|
<div className="action-button">
|
||||||
|
<Icon iconName="ChromeRestore"/>
|
||||||
|
</div>
|
||||||
|
<div className="action-button close-button">
|
||||||
|
<Icon iconName="ChromeClose"/>
|
||||||
|
</div>
|
||||||
|
</Theme>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头部信息栏
|
||||||
|
*/
|
||||||
|
@useSettingWithEvent("language")
|
||||||
|
@useStatusWithEvent("fileChange")
|
||||||
|
class HeaderBar extends Component<IHeaderBarProps & IMixinStatusProps & IMixinSettingProps> {
|
||||||
|
|
||||||
|
public render(): ReactNode {
|
||||||
|
const { status, setting } = this.props;
|
||||||
|
|
||||||
|
let fileName: string = "";
|
||||||
|
let isNewFile: boolean = true;
|
||||||
|
let isSaved: boolean = false;
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
isNewFile = status.archive.isNewFile;
|
||||||
|
fileName = status.archive.fileName ?? "";
|
||||||
|
isSaved = status.archive.isSaved;
|
||||||
|
}
|
||||||
|
|
||||||
return <Theme
|
return <Theme
|
||||||
className="header-bar"
|
className="header-bar"
|
||||||
backgroundLevel={BackgroundLevel.Level1}
|
backgroundLevel={BackgroundLevel.Level1}
|
||||||
@ -127,15 +145,15 @@ class HeaderBar extends Component<
|
|||||||
isSaved ? "" : "*"
|
isSaved ? "" : "*"
|
||||||
}</div>
|
}</div>
|
||||||
</LocalizationTooltipHost>
|
</LocalizationTooltipHost>
|
||||||
<LocalizationTooltipHost i18nKey="Header.Bar.Fps.Info" options={fpsInfo}>
|
|
||||||
<div className="fps-view">
|
{
|
||||||
<Icon iconName="SpeedHigh"></Icon>
|
setting?.platform === Platform.desktop ?
|
||||||
<span>{I18N(this.props, "Header.Bar.Fps", fpsInfo)}</span>
|
<HeaderWindowsAction/> :
|
||||||
</div>
|
<HeaderFpsView setting={setting}/>
|
||||||
</LocalizationTooltipHost>
|
}
|
||||||
|
|
||||||
</Theme>
|
</Theme>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HeaderBar;
|
|
||||||
export { HeaderBar };
|
export { HeaderBar };
|
@ -11,6 +11,11 @@ enum Themes {
|
|||||||
dark = 2
|
dark = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Platform {
|
||||||
|
web = 1,
|
||||||
|
desktop = 2
|
||||||
|
}
|
||||||
|
|
||||||
type Language = "ZH_CN" | "EN_US";
|
type Language = "ZH_CN" | "EN_US";
|
||||||
|
|
||||||
interface ISettingEvents extends Setting {
|
interface ISettingEvents extends Setting {
|
||||||
@ -19,6 +24,11 @@ interface ISettingEvents extends Setting {
|
|||||||
|
|
||||||
class Setting extends Emitter<ISettingEvents> {
|
class Setting extends Emitter<ISettingEvents> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 程序平台
|
||||||
|
*/
|
||||||
|
public platform: Platform = Platform.web;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主题
|
* 主题
|
||||||
*/
|
*/
|
||||||
@ -63,5 +73,5 @@ const useSettingWithEvent = superConnectWithEvent<Setting, ISettingEvents>(Setti
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
Themes, Setting, SettingContext, useSetting, Language, useSettingWithEvent,
|
Themes, Setting, SettingContext, useSetting, Language, useSettingWithEvent,
|
||||||
IMixinSettingProps, SettingProvider, SettingConsumer
|
IMixinSettingProps, SettingProvider, SettingConsumer, Platform
|
||||||
};
|
};
|
@ -30,6 +30,7 @@ function randomColor(unNormal: boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IStatusEvent {
|
interface IStatusEvent {
|
||||||
|
fileChange: void;
|
||||||
renderLoop: number;
|
renderLoop: number;
|
||||||
physicsLoop: number;
|
physicsLoop: number;
|
||||||
mouseModChange: void;
|
mouseModChange: void;
|
||||||
@ -148,6 +149,9 @@ class Status extends Emitter<IStatusEvent> {
|
|||||||
this.on("groupLabelChange", updateBehaviorParameter);
|
this.on("groupLabelChange", updateBehaviorParameter);
|
||||||
this.on("rangeLabelChange", updateBehaviorParameter);
|
this.on("rangeLabelChange", updateBehaviorParameter);
|
||||||
this.on("behaviorAttrChange", updateBehaviorParameter);
|
this.on("behaviorAttrChange", updateBehaviorParameter);
|
||||||
|
|
||||||
|
// 映射文件状态改变事件
|
||||||
|
this.archive.on("fileChange", () => this.emit("fileChange"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bindRenderer(renderer: AbstractRenderer) {
|
public bindRenderer(renderer: AbstractRenderer) {
|
||||||
|
44
source/Electron/Electron.ts
Normal file
44
source/Electron/Electron.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { app, BrowserWindow } from "electron";
|
||||||
|
import { Service } from "@Service/Service";
|
||||||
|
const ENV = process.env ?? {};
|
||||||
|
|
||||||
|
class ElectronApp {
|
||||||
|
|
||||||
|
public service: Service;
|
||||||
|
|
||||||
|
public serviceUrl: string = "http://127.0.0.1";
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.service = new Service();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async runService() {
|
||||||
|
|
||||||
|
let defaultPort: number | undefined = parseInt(ENV.LIVING_TOGETHER_DEFAULT_PORT ?? "");
|
||||||
|
if (isNaN(defaultPort)) defaultPort = undefined;
|
||||||
|
|
||||||
|
this.serviceUrl = await this.service.run(
|
||||||
|
ENV.LIVING_TOGETHER_BASE_PATH, defaultPort
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public mainWindows?: BrowserWindow;
|
||||||
|
|
||||||
|
public async runMainThread() {
|
||||||
|
|
||||||
|
await app.whenReady();
|
||||||
|
|
||||||
|
await this.runService();
|
||||||
|
|
||||||
|
this.mainWindows = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
titleBarStyle: 'hidden',
|
||||||
|
frame: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.mainWindows.loadURL(this.serviceUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new ElectronApp().runMainThread();
|
@ -1 +0,0 @@
|
|||||||
export * from "@Model/Model";
|
|
@ -9,6 +9,8 @@ const EN_US = {
|
|||||||
"Header.Bar.File.Save.Status.Unsaved": "UnSaved",
|
"Header.Bar.File.Save.Status.Unsaved": "UnSaved",
|
||||||
"Header.Bar.Fps": "FPS: {renderFps} | {physicsFps}",
|
"Header.Bar.Fps": "FPS: {renderFps} | {physicsFps}",
|
||||||
"Header.Bar.Fps.Info": "The rendering frame rate ({renderFps} fps) is on the left, and the simulation frame rate ({physicsFps} fps) is on the right.",
|
"Header.Bar.Fps.Info": "The rendering frame rate ({renderFps} fps) is on the left, and the simulation frame rate ({physicsFps} fps) is on the right.",
|
||||||
|
"Header.Bar.Fps.Render.Info": "Render fps {fps}",
|
||||||
|
"Header.Bar.Fps.Simulate.Info": "Simulate fps {fps}",
|
||||||
"Command.Bar.Save.Info": "Save",
|
"Command.Bar.Save.Info": "Save",
|
||||||
"Command.Bar.Play.Info": "Start simulation",
|
"Command.Bar.Play.Info": "Start simulation",
|
||||||
"Command.Bar.Drag.Info": "Drag and drop to move the camera",
|
"Command.Bar.Drag.Info": "Drag and drop to move the camera",
|
||||||
|
@ -9,6 +9,8 @@ const ZH_CN = {
|
|||||||
"Header.Bar.File.Save.Status.Unsaved": "未保存",
|
"Header.Bar.File.Save.Status.Unsaved": "未保存",
|
||||||
"Header.Bar.Fps": "帧率: {renderFps} | {physicsFps}",
|
"Header.Bar.Fps": "帧率: {renderFps} | {physicsFps}",
|
||||||
"Header.Bar.Fps.Info": "左侧为渲染帧率 ({renderFps} fps), 右侧为模拟帧率 ({physicsFps} fps)。",
|
"Header.Bar.Fps.Info": "左侧为渲染帧率 ({renderFps} fps), 右侧为模拟帧率 ({physicsFps} fps)。",
|
||||||
|
"Header.Bar.Fps.Render.Info": "渲染帧率 {fps}",
|
||||||
|
"Header.Bar.Fps.Simulate.Info": "模拟帧率 {fps}",
|
||||||
"Command.Bar.Save.Info": "保存",
|
"Command.Bar.Save.Info": "保存",
|
||||||
"Command.Bar.Play.Info": "开始仿真",
|
"Command.Bar.Play.Info": "开始仿真",
|
||||||
"Command.Bar.Drag.Info": "拖拽进行视角移动",
|
"Command.Bar.Drag.Info": "拖拽进行视角移动",
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Emitter, EventType } from "@Model/Emitter";
|
import { Emitter, EventType } from "@Model/Emitter";
|
||||||
|
import { Model } from "./Model";
|
||||||
|
|
||||||
interface IArchiveEvent {
|
interface IArchiveEvent {
|
||||||
save: Archive;
|
fileChange: Archive;
|
||||||
load: Archive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Archive<
|
class Archive<
|
||||||
@ -34,13 +34,28 @@ class Archive<
|
|||||||
* 保存文件
|
* 保存文件
|
||||||
* 模型转换为文件
|
* 模型转换为文件
|
||||||
*/
|
*/
|
||||||
public save() {};
|
public save(model: Model): string {
|
||||||
|
let fileData: Record<string, any> = {};
|
||||||
|
|
||||||
|
// 保存 Next ID
|
||||||
|
fileData.idIndex = model.idIndex;
|
||||||
|
|
||||||
|
// 保存对象
|
||||||
|
fileData.objects = [];
|
||||||
|
|
||||||
|
// 记录
|
||||||
|
model.objectPool.map((object) => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return JSON.stringify(model);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载文件为模型
|
* 加载文件为模型
|
||||||
* return Model
|
* return Model
|
||||||
*/
|
*/
|
||||||
public load() {};
|
public load(model: Model, data: string) {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Archive };
|
export { Archive };
|
||||||
|
@ -33,7 +33,7 @@ class Model extends Emitter<ModelEvent> {
|
|||||||
/**
|
/**
|
||||||
* 下一个需要分配的 ID
|
* 下一个需要分配的 ID
|
||||||
*/
|
*/
|
||||||
private idIndex: number = 1;
|
public idIndex: number = 1;
|
||||||
public nextId(label: string = "U"): string {
|
public nextId(label: string = "U"): string {
|
||||||
return `${label}-${this.idIndex ++}`;
|
return `${label}-${this.idIndex ++}`;
|
||||||
}
|
}
|
||||||
|
11
source/Page/SimulatorDesktop/SimulatorDesktop.scss
Normal file
11
source/Page/SimulatorDesktop/SimulatorDesktop.scss
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
div.app-root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
div.app-root-space {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
108
source/Page/SimulatorDesktop/SimulatorDesktop.tsx
Normal file
108
source/Page/SimulatorDesktop/SimulatorDesktop.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { Component, ReactNode } from "react";
|
||||||
|
import { SettingProvider, Setting, Platform } from "@Context/Setting";
|
||||||
|
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
|
||||||
|
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 "@Context/Layout";
|
||||||
|
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 "./SimulatorDesktop.scss";
|
||||||
|
|
||||||
|
initializeIcons("./font-icon/");
|
||||||
|
|
||||||
|
class SimulatorDesktop extends Component {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局设置
|
||||||
|
*/
|
||||||
|
private setting: Setting;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局状态
|
||||||
|
*/
|
||||||
|
private status: Status;
|
||||||
|
|
||||||
|
public constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// TODO: 这里要读取设置
|
||||||
|
this.setting = new Setting();
|
||||||
|
this.setting.platform = Platform.desktop;
|
||||||
|
|
||||||
|
// TODO: 这里要读取存档
|
||||||
|
const classicRender = new ClassicRenderer().onLoad();
|
||||||
|
this.status = new Status();
|
||||||
|
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;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
this.setting.layout.setData({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
items: [
|
||||||
|
{panels: ["RenderView"]},
|
||||||
|
{
|
||||||
|
items: [{panels: ["BehaviorList"]}, {panels: ["LabelList"]}],
|
||||||
|
scale: 80,
|
||||||
|
layout: LayoutDirection.X
|
||||||
|
}
|
||||||
|
],
|
||||||
|
scale: 60,
|
||||||
|
layout: LayoutDirection.Y
|
||||||
|
},
|
||||||
|
{
|
||||||
|
items: [{
|
||||||
|
panels: ["ObjectList"]
|
||||||
|
}, {
|
||||||
|
panels: ["GroupDetails", "RangeDetails", "LabelDetails", "BehaviorDetails"]
|
||||||
|
}],
|
||||||
|
scale: 30,
|
||||||
|
layout: LayoutDirection.Y
|
||||||
|
}
|
||||||
|
],
|
||||||
|
scale: 60,
|
||||||
|
layout: LayoutDirection.X
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): ReactNode {
|
||||||
|
return <SettingProvider value={this.setting}>
|
||||||
|
<StatusProvider value={this.status}>
|
||||||
|
{this.renderContent()}
|
||||||
|
</StatusProvider>
|
||||||
|
</SettingProvider>
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderContent(): ReactNode {
|
||||||
|
return <Theme
|
||||||
|
className="app-root"
|
||||||
|
backgroundLevel={BackgroundLevel.Level5}
|
||||||
|
fontLevel={FontLevel.Level3}
|
||||||
|
>
|
||||||
|
<Popup/>
|
||||||
|
<HeaderBar height={45}/>
|
||||||
|
<div className="app-root-space" style={{
|
||||||
|
height: `calc( 100% - ${45}px)`
|
||||||
|
}}>
|
||||||
|
<CommandBar width={45}/>
|
||||||
|
<RootContainer />
|
||||||
|
</div>
|
||||||
|
</Theme>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry.renderComponent(SimulatorDesktop);
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, ReactNode } from "react";
|
import { Component, ReactNode } from "react";
|
||||||
import { SettingProvider, Setting } from "@Context/Setting";
|
import { SettingProvider, Setting, Platform } from "@Context/Setting";
|
||||||
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
|
import { Theme, BackgroundLevel, FontLevel } from "@Component/Theme/Theme";
|
||||||
import { StatusProvider, Status } from "@Context/Status";
|
import { StatusProvider, Status } from "@Context/Status";
|
||||||
import { ClassicRenderer } from "@GLRender/ClassicRenderer";
|
import { ClassicRenderer } from "@GLRender/ClassicRenderer";
|
||||||
@ -33,7 +33,7 @@ class SimulatorWeb extends Component {
|
|||||||
|
|
||||||
// TODO: 这里要读取设置
|
// TODO: 这里要读取设置
|
||||||
this.setting = new Setting();
|
this.setting = new Setting();
|
||||||
(window as any).setting = (this.setting as any);
|
this.setting.platform = Platform.web;
|
||||||
|
|
||||||
// TODO: 这里要读取存档
|
// TODO: 这里要读取存档
|
||||||
const classicRender = new ClassicRenderer().onLoad();
|
const classicRender = new ClassicRenderer().onLoad();
|
||||||
|
@ -7,3 +7,16 @@ div.render-view {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.render-view-fps {
|
||||||
|
height: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
div.fps-view {
|
||||||
|
position: relative;
|
||||||
|
opacity: .5;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,85 @@
|
|||||||
import { Component, ReactNode, createRef } from "react";
|
import { Component, ReactNode, createRef } from "react";
|
||||||
import { useStatus, IMixinStatusProps } from "@Context/Status";
|
import { useStatus, IMixinStatusProps } from "@Context/Status";
|
||||||
import { useSetting, IMixinSettingProps, Themes } from "@Context/Setting";
|
import { useSetting, IMixinSettingProps, Themes, Platform } from "@Context/Setting";
|
||||||
import { ClassicRenderer } from "@GLRender/ClassicRenderer";
|
import { ClassicRenderer } from "@GLRender/ClassicRenderer";
|
||||||
|
import { FontLevel, Theme } from "@Component/Theme/Theme";
|
||||||
|
import { Localization } from "@Component/Localization/Localization";
|
||||||
import "./RenderView.scss";
|
import "./RenderView.scss";
|
||||||
|
|
||||||
|
interface IRendererFpsViewProps {
|
||||||
|
renderFps: number;
|
||||||
|
physicsFps: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@useStatus
|
||||||
|
class RendererFpsView extends Component<IMixinStatusProps, IRendererFpsViewProps> {
|
||||||
|
|
||||||
|
public state = {
|
||||||
|
renderFps: 0,
|
||||||
|
physicsFps: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateTime: number = 0;
|
||||||
|
|
||||||
|
private renderFpsCalc: (t: number) => void = () => {};
|
||||||
|
private physicsFpsCalc: (t: number) => void = () => {};
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
const { status } = this.props;
|
||||||
|
this.renderFpsCalc = this.createFpsCalc("renderFps");
|
||||||
|
this.physicsFpsCalc = this.createFpsCalc("physicsFps");
|
||||||
|
if (status) {
|
||||||
|
status.on("physicsLoop", this.physicsFpsCalc);
|
||||||
|
status.on("renderLoop", this.renderFpsCalc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
const { status } = this.props;
|
||||||
|
if (status) {
|
||||||
|
status.off("physicsLoop", this.physicsFpsCalc);
|
||||||
|
status.off("renderLoop", this.renderFpsCalc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createFpsCalc(type: "renderFps" | "physicsFps") {
|
||||||
|
return (t: number) => {
|
||||||
|
if (t === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let newState: IRendererFpsViewProps = {} as any;
|
||||||
|
newState[type] = 1 / t;
|
||||||
|
if (this.updateTime > 20) {
|
||||||
|
this.updateTime = 0;
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
this.updateTime ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
|
||||||
|
const fpsInfo = {
|
||||||
|
renderFps: Math.floor(this.state.renderFps).toString(),
|
||||||
|
physicsFps: Math.floor(this.state.physicsFps).toString()
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Theme
|
||||||
|
className="render-view-fps"
|
||||||
|
fontLevel={FontLevel.normal}
|
||||||
|
>
|
||||||
|
<div className="fps-view">
|
||||||
|
<Localization i18nKey="Header.Bar.Fps.Render.Info" options={{
|
||||||
|
fps: fpsInfo.renderFps
|
||||||
|
}}/><br/>
|
||||||
|
<Localization i18nKey="Header.Bar.Fps.Simulate.Info" options={{
|
||||||
|
fps: fpsInfo.physicsFps
|
||||||
|
}}/>
|
||||||
|
</div>
|
||||||
|
</Theme>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@useSetting
|
@useSetting
|
||||||
@useStatus
|
@useStatus
|
||||||
class RenderView extends Component<IMixinStatusProps & IMixinSettingProps> {
|
class RenderView extends Component<IMixinStatusProps & IMixinSettingProps> {
|
||||||
@ -23,12 +99,17 @@ class RenderView extends Component<IMixinStatusProps & IMixinSettingProps> {
|
|||||||
[190 / 255, 187 / 255, 184 / 255, 1]
|
[190 / 255, 187 / 255, 184 / 255, 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div ref={this.rootEle} className={classList.join(" ")}/>;
|
return <>
|
||||||
|
{
|
||||||
|
this.props.setting?.platform === Platform.desktop ?
|
||||||
|
<RendererFpsView/> : null
|
||||||
|
}
|
||||||
|
<div ref={this.rootEle} className={classList.join(" ")}/>;
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
let div = this.rootEle.current;
|
let div = this.rootEle.current;
|
||||||
// console.log(div, div?.childNodes, this.props.status, this.props.status?.renderer.dom)
|
|
||||||
if (div && (!div.childNodes || div.childNodes.length <= 0) && this.props.status) {
|
if (div && (!div.childNodes || div.childNodes.length <= 0) && this.props.status) {
|
||||||
div.appendChild(this.props.status.renderer.dom);
|
div.appendChild(this.props.status.renderer.dom);
|
||||||
}
|
}
|
||||||
|
8
source/Service/Runner.ts
Normal file
8
source/Service/Runner.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Service } from "@Service/Service";
|
||||||
|
import * as minimist from "minimist";
|
||||||
|
|
||||||
|
const args = minimist(process.argv.slice(2));
|
||||||
|
|
||||||
|
if (args.run) {
|
||||||
|
new Service().run(args.path, args.port);
|
||||||
|
}
|
49
source/Service/Service.ts
Normal file
49
source/Service/Service.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { Express } from "express";
|
||||||
|
import * as express from "express";
|
||||||
|
import * as detect from "detect-port";
|
||||||
|
|
||||||
|
class Service {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认端口
|
||||||
|
*/
|
||||||
|
public servicePort: number = 12100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Express 实例
|
||||||
|
*/
|
||||||
|
public app!: Express;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取空闲接口
|
||||||
|
*/
|
||||||
|
private async getFreePort(): Promise<number> {
|
||||||
|
|
||||||
|
let freePort: number = this.servicePort;
|
||||||
|
|
||||||
|
try {
|
||||||
|
freePort = await detect(this.servicePort);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return freePort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run(url?: string, port?: number) {
|
||||||
|
|
||||||
|
this.servicePort = port ?? await this.getFreePort();
|
||||||
|
|
||||||
|
this.app = express();
|
||||||
|
|
||||||
|
this.app.use("/", express.static(url ?? "./"));
|
||||||
|
|
||||||
|
this.app.listen(this.servicePort);
|
||||||
|
|
||||||
|
console.log("Service: service run in port " + this.servicePort);
|
||||||
|
|
||||||
|
return "http://127.0.0.1:" + this.servicePort + "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Service };
|
@ -40,7 +40,13 @@
|
|||||||
],
|
],
|
||||||
"@Panel/*": [
|
"@Panel/*": [
|
||||||
"./source/Panel/*"
|
"./source/Panel/*"
|
||||||
]
|
],
|
||||||
|
"@Service/*": [
|
||||||
|
"./source/Service/*"
|
||||||
|
],
|
||||||
|
"@Electron/*": [
|
||||||
|
"./source/Electron/*"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
Loading…
Reference in New Issue
Block a user