From 03f0b9fd16917eb25c8ad9633a45bcc426dc435c Mon Sep 17 00:00:00 2001 From: MrKBear Date: Sat, 23 Apr 2022 23:28:11 +0800 Subject: [PATCH 1/4] Add file download function --- package-lock.json | 24 ++++ package.json | 2 + source/Component/CommandBar/CommandBar.tsx | 112 +++++++++++------- source/Component/HeaderBar/HeaderBar.tsx | 2 +- source/Context/Status.tsx | 30 ++++- source/Localization/EN-US.ts | 2 +- source/Localization/ZH-CN.ts | 2 +- source/Model/Archive.ts | 24 ++-- .../SimulatorDesktop/SimulatorDesktop.tsx | 4 +- source/Page/SimulatorWeb/SimulatorWeb.tsx | 4 +- 10 files changed, 146 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4288663..c38adc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@fluentui/react": "^8.56.0", "@juggle/resize-observer": "^3.3.1", "detect-port": "^1.3.0", + "downloadjs": "^1.4.7", "express": "^4.17.3", "gl-matrix": "^3.4.3", "react": "^17.0.2", @@ -21,6 +22,7 @@ "devDependencies": { "@atao60/fse-cli": "^0.1.7", "@types/detect-port": "^1.3.2", + "@types/downloadjs": "^1.4.3", "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", "@types/uuid": "^8.3.4", @@ -1095,6 +1097,12 @@ "integrity": "sha512-xxgAGA2SAU4111QefXPSp5eGbDm/hW6zhvYl9IeEPZEry9F4d66QAHm5qpUXjb6IsevZV/7emAEx5MhP6O192g==", "dev": true }, + "node_modules/@types/downloadjs": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/@types/downloadjs/-/downloadjs-1.4.3.tgz", + "integrity": "sha512-MjJepFle/tLtT2/jmDNth6ZnwWzEhm40L+olE5HKR70ISUCfgT55eqreeHldAzFLY2HDUGsn8zgyto8KygN0CA==", + "dev": true + }, "node_modules/@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -2908,6 +2916,11 @@ "tslib": "^2.0.3" } }, + "node_modules/downloadjs": { + "version": "1.4.7", + "resolved": "https://registry.npmmirror.com/downloadjs/-/downloadjs-1.4.7.tgz", + "integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==" + }, "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmmirror.com/duplexer3/-/duplexer3-0.1.4.tgz", @@ -9065,6 +9078,12 @@ "integrity": "sha512-xxgAGA2SAU4111QefXPSp5eGbDm/hW6zhvYl9IeEPZEry9F4d66QAHm5qpUXjb6IsevZV/7emAEx5MhP6O192g==", "dev": true }, + "@types/downloadjs": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/@types/downloadjs/-/downloadjs-1.4.3.tgz", + "integrity": "sha512-MjJepFle/tLtT2/jmDNth6ZnwWzEhm40L+olE5HKR70ISUCfgT55eqreeHldAzFLY2HDUGsn8zgyto8KygN0CA==", + "dev": true + }, "@types/eslint": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", @@ -10550,6 +10569,11 @@ "tslib": "^2.0.3" } }, + "downloadjs": { + "version": "1.4.7", + "resolved": "https://registry.npmmirror.com/downloadjs/-/downloadjs-1.4.7.tgz", + "integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==" + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmmirror.com/duplexer3/-/duplexer3-0.1.4.tgz", diff --git a/package.json b/package.json index fcb77ec..9ccdc33 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "devDependencies": { "@atao60/fse-cli": "^0.1.7", "@types/detect-port": "^1.3.2", + "@types/downloadjs": "^1.4.3", "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", "@types/uuid": "^8.3.4", @@ -70,6 +71,7 @@ "@fluentui/react": "^8.56.0", "@juggle/resize-observer": "^3.3.1", "detect-port": "^1.3.0", + "downloadjs": "^1.4.7", "express": "^4.17.3", "gl-matrix": "^3.4.3", "react": "^17.0.2", diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index b6faf75..23a1bb6 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -1,22 +1,68 @@ import { Component, ReactNode } from "react"; import { DirectionalHint, IconButton } from "@fluentui/react"; -import { useSetting, IMixinSettingProps } from "@Context/Setting"; +import { useSetting, useSettingWithEvent, IMixinSettingProps } from "@Context/Setting"; import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { BackgroundLevel, Theme } from "@Component/Theme/Theme"; import { LocalizationTooltipHost } from "@Component/Localization/LocalizationTooltipHost"; -import { AllI18nKeys } from "@Component/Localization/Localization"; +import { AllI18nKeys, I18N } from "@Component/Localization/Localization"; import { SettingPopup } from "@Component/SettingPopup/SettingPopup"; import { BehaviorPopup } from "@Component/BehaviorPopup/BehaviorPopup"; import { MouseMod } from "@GLRender/ClassicRenderer"; +import * as download from "downloadjs"; import "./CommandBar.scss"; -interface ICommandBarProps { - width: number; +const COMMAND_BAR_WIDTH = 45; + +function getRenderButton(param: { + i18NKey: AllI18nKeys; + iconName?: string; + click?: () => void; + active?: boolean; +}): ReactNode { + return + + +} + +@useSettingWithEvent("language") +@useStatusWithEvent() +class SaveCommandView extends Component { + + public render(): ReactNode { + return getRenderButton({ + iconName: "Save", + i18NKey: "Command.Bar.Save.Info", + click: () => { + + let fileName: string = ""; + let isNewFile: boolean = true; + let isSaved: boolean = false; + + if (this.props.status) { + isNewFile = this.props.status.archive.isNewFile; + fileName = this.props.status.archive.fileName ?? ""; + isSaved = this.props.status.archive.isSaved; + } + + const file = this.props.status?.archive.save(this.props.status.model) ?? ""; + fileName = isNewFile ? I18N(this.props, "Header.Bar.New.File.Name") : fileName; + download(file, fileName, "text/json"); + } + }) + } } @useSetting @useStatusWithEvent("mouseModChange", "actuatorStartChange") -class CommandBar extends Component { +class CommandBar extends Component { render(): ReactNode { @@ -25,7 +71,7 @@ class CommandBar extends Component { if (this.props.setting) { this.props.setting.layout.focus(""); @@ -33,62 +79,65 @@ class CommandBar extends Component
- {this.getRenderButton({ - iconName: "Save", - i18NKey: "Command.Bar.Save.Info", - click: () => { - this.props.status?.archive.save(this.props.status.model); - } - })} - {this.getRenderButton({ + + + + {getRenderButton({ 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 })} - {this.getRenderButton({ + + {getRenderButton({ iconName: "HandsFree", i18NKey: "Command.Bar.Drag.Info", active: mouseMod === MouseMod.Drag, click: () => this.props.status ? this.props.status.setMouseMod(MouseMod.Drag) : undefined })} - {this.getRenderButton({ + + {getRenderButton({ iconName: "TouchPointer", i18NKey: "Command.Bar.Select.Info", active: mouseMod === MouseMod.click, click: () => this.props.status ? this.props.status.setMouseMod(MouseMod.click) : undefined })} - {this.getRenderButton({ + + {getRenderButton({ iconName: "WebAppBuilderFragmentCreate", i18NKey: "Command.Bar.Add.Group.Info", click: () => { this.props.status ? this.props.status.newGroup() : undefined; } })} - {this.getRenderButton({ + + {getRenderButton({ iconName: "ProductVariant", i18NKey: "Command.Bar.Add.Range.Info", click: () => { this.props.status ? this.props.status.newRange() : undefined; } })} - {this.getRenderButton({ + + {getRenderButton({ iconName: "Running", i18NKey: "Command.Bar.Add.Behavior.Info", click: () => { this.props.status?.popup.showPopup(BehaviorPopup, {}); } })} - {this.getRenderButton({ + + {getRenderButton({ iconName: "Tag", i18NKey: "Command.Bar.Add.Tag.Info", click: () => { this.props.status ? this.props.status.newLabel() : undefined; } })} - {this.getRenderButton({ iconName: "Camera", i18NKey: "Command.Bar.Camera.Info" })} + + {getRenderButton({ iconName: "Camera", i18NKey: "Command.Bar.Camera.Info" })}
- {this.getRenderButton({ + {getRenderButton({ iconName: "Settings", i18NKey: "Command.Bar.Setting.Info", click: () => { @@ -98,25 +147,6 @@ class CommandBar extends Component } - - private getRenderButton(param: { - i18NKey: AllI18nKeys; - iconName?: string; - click?: () => void; - active?: boolean; - }): ReactNode { - return - - - } } export { CommandBar }; \ No newline at end of file diff --git a/source/Component/HeaderBar/HeaderBar.tsx b/source/Component/HeaderBar/HeaderBar.tsx index 8322029..8009871 100644 --- a/source/Component/HeaderBar/HeaderBar.tsx +++ b/source/Component/HeaderBar/HeaderBar.tsx @@ -123,7 +123,7 @@ class HeaderWindowsAction extends Component { * 头部信息栏 */ @useSettingWithEvent("language") -@useStatusWithEvent("fileSave") +@useStatusWithEvent("fileSave", "fileChange", "fileLoad") class HeaderBar extends Component { public render(): ReactNode { diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index 69f0dea..ac88446 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -32,6 +32,7 @@ function randomColor(unNormal: boolean = false) { interface IStatusEvent { fileSave: void; fileLoad: void; + fileChange: void; renderLoop: number; physicsLoop: number; mouseModChange: void; @@ -128,11 +129,15 @@ class Status extends Emitter { this.popup.on("popupChange", () => this.emit("popupChange")); // 对象变换时执行渲染,更新渲染器数据 - this.on("objectChange", this.delayDraw); - this.model.on("individualChange", this.delayDraw); this.model.on("individualChange", () => { this.emit("individualChange"); }); + + // 渲染器重绘 + this.on("objectChange", this.delayDraw); + this.on("individualChange", this.delayDraw); + this.on("groupAttrChange", this.delayDraw); + this.on("rangeAttrChange", this.delayDraw); // 当模型中的标签和对象改变时,更新全部行为参数中的受控对象 const updateBehaviorParameter = () => { @@ -158,7 +163,24 @@ class Status extends Emitter { // 映射 this.emit("fileLoad"); - }) + }); + + + // 处理存档事件 + const handelFileChange = () => { + if (this.archive.isSaved) { + this.emit("fileChange"); + } + } + this.on("objectChange", handelFileChange); + this.on("behaviorChange", handelFileChange); + this.on("labelChange", handelFileChange); + this.on("individualChange", handelFileChange); + this.on("groupAttrChange", handelFileChange); + this.on("rangeAttrChange", handelFileChange); + this.on("labelAttrChange", handelFileChange); + this.on("behaviorAttrChange", handelFileChange); + this.on("fileChange", () => this.archive.emit("fileChange")); } public bindRenderer(renderer: AbstractRenderer) { @@ -200,7 +222,6 @@ class Status extends Emitter { if (range && range instanceof Range) { range[key] = val; this.emit("rangeAttrChange"); - this.model.draw(); } } @@ -213,7 +234,6 @@ class Status extends Emitter { if (group && group instanceof Group) { group[key] = val; this.emit("groupAttrChange"); - this.model.draw(); } } diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index cba84c0..0fb6cdd 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -4,7 +4,7 @@ const EN_US = { "Header.Bar.Title": "Living Together | Emulator", "Header.Bar.Title.Info": "Group Behavior Research Emulator", "Header.Bar.File.Name.Info": "{file} ({status})", - "Header.Bar.New.File.Name": "New File", + "Header.Bar.New.File.Name": "NewFile.ltss", "Header.Bar.File.Save.Status.Saved": "Saved", "Header.Bar.File.Save.Status.Unsaved": "UnSaved", "Header.Bar.Fps": "FPS: {renderFps} | {physicsFps}", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index f44fbc4..0933c15 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -4,7 +4,7 @@ const ZH_CN = { "Header.Bar.Title": "群生共进 | 仿真器", "Header.Bar.Title.Info": "群体行为研究仿真器", "Header.Bar.File.Name.Info": "{file} ({status})", - "Header.Bar.New.File.Name": "新存档", + "Header.Bar.New.File.Name": "新存档.ltss", "Header.Bar.File.Save.Status.Saved": "已保存", "Header.Bar.File.Save.Status.Unsaved": "未保存", "Header.Bar.Fps": "帧率: {renderFps} | {physicsFps}", diff --git a/source/Model/Archive.ts b/source/Model/Archive.ts index a0e1f84..edf5577 100644 --- a/source/Model/Archive.ts +++ b/source/Model/Archive.ts @@ -12,6 +12,7 @@ import { IArchiveParseFn, IObjectParamArchiveType, IRealObjectType } from "@Mode interface IArchiveEvent { fileSave: Archive; fileLoad: Archive; + fileChange: void; } interface IArchiveObject { @@ -243,25 +244,34 @@ class Archive extends Emitter { * 保存文件 * 模型转换为文件 */ - public save(model: Model): void { - - console.log(this.parseModel2Archive(model)); - + public save(model: Model): string { this.isSaved = true; this.emit("fileSave", this); + return this.parseModel2Archive(model); } /** * 加载文件为模型 * @return Model */ - public load(model: Model, data: string) { - - this.loadArchiveIntoModel(model, data); + public load(model: Model, data: string): string | undefined { + try { + this.loadArchiveIntoModel(model, data); + } catch (e) { + return e as string; + } + this.isSaved = true; this.emit("fileLoad", this); }; + + public constructor() { + super(); + this.on("fileChange", () => { + this.isSaved = false; + }) + } } export { Archive }; \ No newline at end of file diff --git a/source/Page/SimulatorDesktop/SimulatorDesktop.tsx b/source/Page/SimulatorDesktop/SimulatorDesktop.tsx index 7cb8347..b1f74a7 100644 --- a/source/Page/SimulatorDesktop/SimulatorDesktop.tsx +++ b/source/Page/SimulatorDesktop/SimulatorDesktop.tsx @@ -119,8 +119,8 @@ class SimulatorDesktop extends Component {
- - + +
} diff --git a/source/Page/SimulatorWeb/SimulatorWeb.tsx b/source/Page/SimulatorWeb/SimulatorWeb.tsx index 44b475d..09fab25 100644 --- a/source/Page/SimulatorWeb/SimulatorWeb.tsx +++ b/source/Page/SimulatorWeb/SimulatorWeb.tsx @@ -208,8 +208,8 @@ class SimulatorWeb extends Component {
- - + +
} From 56151c9e7535b1fa54e8cb548092394f64424fd3 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Sun, 24 Apr 2022 12:55:00 +0800 Subject: [PATCH 2/4] Add archive save component --- source/Component/CommandBar/CommandBar.tsx | 70 ++++++++--------- source/Context/Archive.tsx | 91 ++++++++++++++++++++++ source/Model/Archive.ts | 6 +- 3 files changed, 127 insertions(+), 40 deletions(-) create mode 100644 source/Context/Archive.tsx diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index 23a1bb6..d0550f8 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -1,24 +1,30 @@ import { Component, ReactNode } from "react"; import { DirectionalHint, IconButton } from "@fluentui/react"; -import { useSetting, useSettingWithEvent, IMixinSettingProps } from "@Context/Setting"; +import { useSetting, IMixinSettingProps } from "@Context/Setting"; import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { BackgroundLevel, Theme } from "@Component/Theme/Theme"; import { LocalizationTooltipHost } from "@Component/Localization/LocalizationTooltipHost"; -import { AllI18nKeys, I18N } from "@Component/Localization/Localization"; +import { AllI18nKeys } from "@Component/Localization/Localization"; import { SettingPopup } from "@Component/SettingPopup/SettingPopup"; import { BehaviorPopup } from "@Component/BehaviorPopup/BehaviorPopup"; import { MouseMod } from "@GLRender/ClassicRenderer"; -import * as download from "downloadjs"; +import { ArchiveSave } from "@Context/Archive"; import "./CommandBar.scss"; const COMMAND_BAR_WIDTH = 45; -function getRenderButton(param: { +interface IRenderButtonParameter { i18NKey: AllI18nKeys; iconName?: string; click?: () => void; active?: boolean; -}): ReactNode { +} + +interface ICommandBarState { + isSaveRunning: boolean; +} + +function getRenderButton(param: IRenderButtonParameter): ReactNode { return } - -@useSettingWithEvent("language") -@useStatusWithEvent() -class SaveCommandView extends Component { - - public render(): ReactNode { - return getRenderButton({ - iconName: "Save", - i18NKey: "Command.Bar.Save.Info", - click: () => { - - let fileName: string = ""; - let isNewFile: boolean = true; - let isSaved: boolean = false; - - if (this.props.status) { - isNewFile = this.props.status.archive.isNewFile; - fileName = this.props.status.archive.fileName ?? ""; - isSaved = this.props.status.archive.isSaved; - } - - const file = this.props.status?.archive.save(this.props.status.model) ?? ""; - fileName = isNewFile ? I18N(this.props, "Header.Bar.New.File.Name") : fileName; - download(file, fileName, "text/json"); - } - }) - } -} - @useSetting @useStatusWithEvent("mouseModChange", "actuatorStartChange") -class CommandBar extends Component { +class CommandBar extends Component { - render(): ReactNode { + public state: Readonly = { + isSaveRunning: false + }; + + public render(): ReactNode { const mouseMod = this.props.status?.mouseMod ?? MouseMod.Drag; @@ -80,7 +61,22 @@ class CommandBar extends Component { >
- + { + this.setState({ isSaveRunning: false }); + }} + /> + + {getRenderButton({ + iconName: "Save", + i18NKey: "Command.Bar.Save.Info", + click: () => { + this.setState({ + isSaveRunning: true + }); + } + })} {getRenderButton({ iconName: this.props.status?.actuator.start() ? "Pause" : "Play", diff --git a/source/Context/Archive.tsx b/source/Context/Archive.tsx new file mode 100644 index 0000000..cef0fe8 --- /dev/null +++ b/source/Context/Archive.tsx @@ -0,0 +1,91 @@ +import { FunctionComponent, useEffect } from "react"; +import * as download from "downloadjs"; +import { useSetting, IMixinSettingProps, Platform } from "@Context/Setting"; +import { useStatus, IMixinStatusProps } from "@Context/Status"; +import { I18N } from "@Component/Localization/Localization"; + +interface IFileInfo { + fileName: string; + isNewFile: boolean; + isSaved: boolean; + fileUrl?: string; + fileData: () => Promise; +} + +interface IRunnerProps { + running?: boolean; + afterRunning?: () => any; +} + +interface ICallBackProps { + then: () => any; +} + +const ArchiveSaveDownloadView: FunctionComponent = function ArchiveSave(props) { + + const runner = async () => { + const file = await props.fileData(); + setTimeout(() => { + download(file, props.fileName, "text/json"); + props.then(); + }, 100); + } + + useEffect(() => { runner() }, []); + + return <>; +} + +const ArchiveSaveDownload = ArchiveSaveDownloadView; + +/** + * 保存存档文件 + */ +const ArchiveSaveView: FunctionComponent = function ArchiveSave(props) { + + if (!props.running) { + return <>; + } + + const fileData: IFileInfo = { + fileName: "", + isNewFile: true, + isSaved: false, + fileUrl: undefined, + fileData: async () => `{"nextIndividualId":0,"objectPool":[],"labelPool":[],"behaviorPool":[]}` + } + + if (props.status) { + fileData.isNewFile = props.status.archive.isNewFile; + fileData.fileName = props.status.archive.fileName ?? ""; + fileData.isSaved = props.status.archive.isSaved; + fileData.fileUrl = props.status.archive.fileUrl; + } + + if (fileData.isNewFile) { + fileData.fileName = I18N(props, "Header.Bar.New.File.Name"); + } + + // 生成存档文件 + fileData.fileData = async () => { + return props.status?.archive.save(props.status.model) ?? ""; + }; + + const callBack = () => { + if (props.afterRunning) { + props.afterRunning(); + } + } + + return <> + { + props.setting?.platform === Platform.web ? + : + <> + } + +} + +const ArchiveSave = useSetting(useStatus(ArchiveSaveView)); + +export { ArchiveSave }; \ No newline at end of file diff --git a/source/Model/Archive.ts b/source/Model/Archive.ts index edf5577..c4328e4 100644 --- a/source/Model/Archive.ts +++ b/source/Model/Archive.ts @@ -22,7 +22,7 @@ interface IArchiveObject { behaviorPool: IArchiveBehavior[]; } -class Archive extends Emitter { +class Archive extends Emitter { /** * 是否为新文件 @@ -40,9 +40,9 @@ class Archive extends Emitter { public isSaved: boolean = false; /** - * 文件数据 + * 文件路径 */ - public fileData?: M; + public fileUrl?: string; /** * 将模型转换为存档对象 From 87ed15734020663b47cd296030e54c2d809c6090 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Sun, 24 Apr 2022 13:24:55 +0800 Subject: [PATCH 3/4] Add block shutdown hendel --- source/Component/HeaderBar/HeaderBar.tsx | 24 ++++++++++++++++++++++++ source/Context/Status.tsx | 2 ++ 2 files changed, 26 insertions(+) diff --git a/source/Component/HeaderBar/HeaderBar.tsx b/source/Component/HeaderBar/HeaderBar.tsx index 8009871..b69b679 100644 --- a/source/Component/HeaderBar/HeaderBar.tsx +++ b/source/Component/HeaderBar/HeaderBar.tsx @@ -126,6 +126,30 @@ class HeaderWindowsAction extends Component { @useStatusWithEvent("fileSave", "fileChange", "fileLoad") class HeaderBar extends Component { + private showCloseMessage = (e: BeforeUnloadEvent) => { + if (!this.props.status?.archive.isSaved) { + const message = I18N(this.props, "ZH_CH"); + (e || window.event).returnValue = message; // 兼容 Gecko + IE + return message; // 兼容 Gecko + Webkit, Safari, Chrome + } + } + + public componentDidMount() { + + if (this.props.setting?.platform === Platform.web) { + // 阻止页面关闭 + window.addEventListener("beforeunload", this.showCloseMessage); + } + } + + public componentWillUnmount() { + + if (this.props.setting?.platform === Platform.web) { + // 阻止页面关闭 + window.removeEventListener("beforeunload", this.showCloseMessage); + } + } + public render(): ReactNode { const { status, setting } = this.props; diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index ac88446..0d702ca 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -172,6 +172,8 @@ class Status extends Emitter { this.emit("fileChange"); } } + + // 设置文件修改状态 this.on("objectChange", handelFileChange); this.on("behaviorChange", handelFileChange); this.on("labelChange", handelFileChange); From b5ecf0bb0d76364b4f3cc86351a8a3af9c98de98 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Sun, 24 Apr 2022 14:10:39 +0800 Subject: [PATCH 4/4] Add loadfile layer --- source/Component/HeaderBar/HeaderBar.tsx | 2 +- source/Component/LoadFile/LoadFile.scss | 38 +++++++++++++++++++++++ source/Component/LoadFile/LoadFile.tsx | 31 ++++++++++++++++++ source/Localization/EN-US.ts | 3 ++ source/Localization/ZH-CN.ts | 3 ++ source/Page/SimulatorWeb/SimulatorWeb.tsx | 2 ++ 6 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 source/Component/LoadFile/LoadFile.scss create mode 100644 source/Component/LoadFile/LoadFile.tsx diff --git a/source/Component/HeaderBar/HeaderBar.tsx b/source/Component/HeaderBar/HeaderBar.tsx index b69b679..b85b21c 100644 --- a/source/Component/HeaderBar/HeaderBar.tsx +++ b/source/Component/HeaderBar/HeaderBar.tsx @@ -128,7 +128,7 @@ class HeaderBar extends Component { if (!this.props.status?.archive.isSaved) { - const message = I18N(this.props, "ZH_CH"); + const message = I18N(this.props, "Info.Hint.Save.After.Close"); (e || window.event).returnValue = message; // 兼容 Gecko + IE return message; // 兼容 Gecko + Webkit, Safari, Chrome } diff --git a/source/Component/LoadFile/LoadFile.scss b/source/Component/LoadFile/LoadFile.scss new file mode 100644 index 0000000..5d29e0e --- /dev/null +++ b/source/Component/LoadFile/LoadFile.scss @@ -0,0 +1,38 @@ +@import "../Theme/Theme.scss"; + +div.load-file-layer-root { + position: fixed; + z-index: 1000; + width: 100%; + height: 100%; + display: flex; + align-content: center; + justify-content: center; + align-items: center; + flex-wrap: wrap; + + div { + user-select: none; + text-align: center; + width: 100%; + } + + div.drag-icon { + font-weight: 200; + font-size: 2.8em; + } + + div.drag-title { + margin-top: 5px; + margin-bottom: 5px; + font-size: 1.5em; + } +} + +div.load-file-layer-root.light { + background-color: rgba($color: #FFFFFF, $alpha: .75); +} + +div.load-file-layer-root.dark { + background-color: rgba($color: #000000, $alpha: .75); +} \ No newline at end of file diff --git a/source/Component/LoadFile/LoadFile.tsx b/source/Component/LoadFile/LoadFile.tsx new file mode 100644 index 0000000..432b6bc --- /dev/null +++ b/source/Component/LoadFile/LoadFile.tsx @@ -0,0 +1,31 @@ +import { Localization } from "@Component/Localization/Localization"; +import { FontLevel, Theme } from "@Component/Theme/Theme"; +import { Icon } from "@fluentui/react"; +import { Component, ReactNode } from "react"; +import "./LoadFile.scss"; + +class LoadFile extends Component { + + private renderMask() { + return +
+ +
+
+ +
+
+ +
+
; + } + + public render(): ReactNode { + return <>; + } +} + +export { LoadFile }; \ No newline at end of file diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index 0fb6cdd..e8e756b 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -133,5 +133,8 @@ const EN_US = { "Panel.Info.Behavior.Details.Parameter.Key.Vec.X": "{key} X", "Panel.Info.Behavior.Details.Parameter.Key.Vec.Y": "{key} Y", "Panel.Info.Behavior.Details.Parameter.Key.Vec.Z": "{key} Z", + "Info.Hint.Save.After.Close": "Any unsaved progress will be lost. Are you sure you want to continue?", + "Info.Hint.Load.File.Title": "Load save", + "Info.Hint.Load.File.Intro": "Release to load the dragged save file", } export default EN_US; \ No newline at end of file diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index 0933c15..6b24c28 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -133,5 +133,8 @@ const ZH_CN = { "Panel.Info.Behavior.Details.Parameter.Key.Vec.X": "{key} X 坐标", "Panel.Info.Behavior.Details.Parameter.Key.Vec.Y": "{key} Y 坐标", "Panel.Info.Behavior.Details.Parameter.Key.Vec.Z": "{key} Z 坐标", + "Info.Hint.Save.After.Close": "任何未保存的进度都会丢失, 确定要继续吗?", + "Info.Hint.Load.File.Title": "加载存档", + "Info.Hint.Load.File.Intro": "释放以加载拽入的存档", } export default ZH_CN; \ No newline at end of file diff --git a/source/Page/SimulatorWeb/SimulatorWeb.tsx b/source/Page/SimulatorWeb/SimulatorWeb.tsx index 09fab25..53271a4 100644 --- a/source/Page/SimulatorWeb/SimulatorWeb.tsx +++ b/source/Page/SimulatorWeb/SimulatorWeb.tsx @@ -6,6 +6,7 @@ import { ClassicRenderer } from "@GLRender/ClassicRenderer"; 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"; @@ -203,6 +204,7 @@ class SimulatorWeb extends Component { backgroundLevel={BackgroundLevel.Level5} fontLevel={FontLevel.Level3} > +