diff --git a/packages/chili-core/src/base/pubsub.ts b/packages/chili-core/src/base/pubsub.ts index b4f72efe..769532fb 100644 --- a/packages/chili-core/src/base/pubsub.ts +++ b/packages/chili-core/src/base/pubsub.ts @@ -34,8 +34,9 @@ export interface PubSubEventMap { clearSelectionControl: () => void; openCommandContext: (command: ICommand) => void; closeCommandContext: () => void; - showHome: () => void; + displayHome: (show: boolean) => void; showToast: (message: I18nKeys, ...args: any[]) => void; + showPermanent: (action: () => Promise, message: I18nKeys, ...args: any[]) => void; showDialog: (title: I18nKeys, context: IPropertyChanged, callback: () => void) => void; } diff --git a/packages/chili-core/src/i18n/en.ts b/packages/chili-core/src/i18n/en.ts index 25bf7813..504fb8c2 100644 --- a/packages/chili-core/src/i18n/en.ts +++ b/packages/chili-core/src/i18n/en.ts @@ -94,6 +94,7 @@ export default { "toast.downloading": "Downloading", "toast.success": "Success", "toast.fail": "Fail", + "toast.excuting{0}": "Excuting {0}", "prompt.default": "Middle mouse button to pan the view, Shift + Middle button to rotate the view, Middle button to scroll the zoom view", "prompt.select.models": "Please select models", diff --git a/packages/chili-core/src/i18n/local.ts b/packages/chili-core/src/i18n/local.ts index 52de32e3..eb6a07ff 100644 --- a/packages/chili-core/src/i18n/local.ts +++ b/packages/chili-core/src/i18n/local.ts @@ -130,6 +130,7 @@ export type I18nKeys = | "toast.downloading" | "toast.success" | "toast.fail" + | "toast.excuting{0}" | "prompt.default" | "prompt.select.models" | "prompt.select.edges" diff --git a/packages/chili-core/src/i18n/zh-cn.ts b/packages/chili-core/src/i18n/zh-cn.ts index 59816951..171e570e 100644 --- a/packages/chili-core/src/i18n/zh-cn.ts +++ b/packages/chili-core/src/i18n/zh-cn.ts @@ -94,6 +94,7 @@ export default { "toast.downloading": "正在下载", "toast.success": "成功", "toast.fail": "失败", + "toast.excuting{0}": "正在执行{0}", "prompt.default": "鼠标中键平移视图,Shift + 中键旋转视图,中键滚动缩放视图", "prompt.polygon.close": "闭合", "prompt.select.models": "请选择模型", diff --git a/packages/chili-ui/src/home/home.ts b/packages/chili-ui/src/home/home.ts index 70f25bc6..6a4e73de 100644 --- a/packages/chili-ui/src/home/home.ts +++ b/packages/chili-ui/src/home/home.ts @@ -54,9 +54,20 @@ export const Home = async (app: IApplication) => { div( { className: style.document, - onclick: async () => { - let document = await app.openDocument(item.id); - document?.visual.viewer.activeView?.cameraController.fitContent(); + onclick: () => { + if (item.id === app.activeDocument?.id) { + PubSub.default.pub("displayHome", false); + } else { + PubSub.default.pub( + "showPermanent", + async () => { + let document = await app.openDocument(item.id); + document?.visual.viewer.activeView?.cameraController.fitContent(); + }, + "toast.excuting{0}", + I18n.translate("command.document.open"), + ); + } }, }, img({ className: style.img, src: item.image }), diff --git a/packages/chili-ui/src/mainWindow.ts b/packages/chili-ui/src/mainWindow.ts index fb11d2e8..eeada775 100644 --- a/packages/chili-ui/src/mainWindow.ts +++ b/packages/chili-ui/src/mainWindow.ts @@ -5,6 +5,7 @@ import { Dialog } from "./dialog"; import { Editor } from "./editor"; import { Home } from "./home"; import { Toast } from "./toast"; +import { Permanent } from "./permanent"; document.oncontextmenu = (e) => e.preventDefault(); @@ -26,8 +27,9 @@ export class MainWindow { const displayHome = debounce(this.displayHome, 100); PubSub.default.sub("showToast", Toast.show); PubSub.default.sub("showDialog", Dialog.show); + PubSub.default.sub("showPermanent", Permanent.show); PubSub.default.sub("activeDocumentChanged", (doc) => displayHome(app, doc === undefined)); - PubSub.default.sub("showHome", () => displayHome(app, true)); + PubSub.default.sub("displayHome", (show) => displayHome(app, show)); } private displayHome = (app: IApplication, displayHome: boolean) => { diff --git a/packages/chili-ui/src/permanent.module.css b/packages/chili-ui/src/permanent.module.css new file mode 100644 index 00000000..2514acf1 --- /dev/null +++ b/packages/chili-ui/src/permanent.module.css @@ -0,0 +1,46 @@ +dialog { + border: none; + box-shadow: 0px 1px 2px #999; + border-radius: 16px; + padding: 0px; +} + +dialog::backdrop { + background-color: rgba(0, 0, 0, 0.5); +} + +.loading { + position: relative; + width: 48px; + height: 48px; + border: 2px solid #000; + border-top-color: rgba(0, 0, 0, 0.2); + border-right-color: rgba(0, 0, 0, 0.2); + border-bottom-color: rgba(0, 0, 0, 0.2); + border-radius: 100%; + + animation: circle infinite 0.75s linear; +} + +@keyframes circle { + 0% { + transform: rotate(0); + } + + 100% { + transform: rotate(360deg); + } +} + +.container { + width: fit-content; + height: fit-content; + padding: 24px; + display: flex; + flex-direction: column; + align-items: center; +} + +.message { + margin-top: 12px; +} diff --git a/packages/chili-ui/src/permanent.ts b/packages/chili-ui/src/permanent.ts new file mode 100644 index 00000000..7b08190e --- /dev/null +++ b/packages/chili-ui/src/permanent.ts @@ -0,0 +1,25 @@ +// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. + +import { I18n, I18nKeys } from "chili-core"; +import { div, span } from "./controls"; +import style from "./permanent.module.css"; + +export class Permanent { + static show(action: () => Promise, message: I18nKeys, ...args: any[]) { + let dialog = document.createElement("dialog"); + dialog.appendChild( + div( + { className: style.container }, + div({ className: style.loading }), + span({ + className: style.message, + textContent: I18n.translate(message, ...args), + }), + ), + ); + document.body.appendChild(dialog); + action().finally(() => dialog.remove()); + + dialog.showModal(); + } +} diff --git a/packages/chili-ui/src/ribbon/ribbon.ts b/packages/chili-ui/src/ribbon/ribbon.ts index 10df34ab..7c18a514 100644 --- a/packages/chili-ui/src/ribbon/ribbon.ts +++ b/packages/chili-ui/src/ribbon/ribbon.ts @@ -152,7 +152,7 @@ export class Ribbon extends BindableElement { label({ textContent: localize("ribbon.tab.file"), className: style.startup, - onclick: () => PubSub.default.pub("showHome"), + onclick: () => PubSub.default.pub("displayHome", true), }), items({ sources: dataContent.ribbonTabs, diff --git a/packages/chili/src/commands/application/openDocument.ts b/packages/chili/src/commands/application/openDocument.ts index 1b65c9ca..2a93815a 100644 --- a/packages/chili/src/commands/application/openDocument.ts +++ b/packages/chili/src/commands/application/openDocument.ts @@ -1,6 +1,16 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IApplication, ICommand, IView, PubSub, Serialized, command, readFileAsync } from "chili-core"; +import { + AsyncController, + I18n, + IApplication, + ICommand, + IView, + PubSub, + Serialized, + command, + readFileAsync, +} from "chili-core"; @command({ name: "doc.open", @@ -9,11 +19,18 @@ import { IApplication, ICommand, IView, PubSub, Serialized, command, readFileAsy }) export class OpenDocument implements ICommand { async execute(app: IApplication): Promise { - let files = await readFileAsync(".cd", false); - if (files.status === "success") { - let json: Serialized = JSON.parse(files.value[0].data); - let document = await app.loadDocument(json); - document.visual.viewer.activeView?.cameraController.fitContent(); - } + PubSub.default.pub( + "showPermanent", + async () => { + let files = await readFileAsync(".cd", false); + if (files.status === "success") { + let json: Serialized = JSON.parse(files.value[0].data); + let document = await app.loadDocument(json); + document.visual.viewer.activeView?.cameraController.fitContent(); + } + }, + "toast.excuting{0}", + I18n.translate("command.document.open"), + ); } } diff --git a/packages/chili/src/commands/application/saveDocument.ts b/packages/chili/src/commands/application/saveDocument.ts index 57b8629b..98e5b3c6 100644 --- a/packages/chili/src/commands/application/saveDocument.ts +++ b/packages/chili/src/commands/application/saveDocument.ts @@ -1,6 +1,6 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { command, IApplication, ICommand, PubSub } from "chili-core"; +import { AsyncController, command, I18n, IApplication, ICommand, PubSub } from "chili-core"; @command({ name: "doc.save", @@ -9,11 +9,15 @@ import { command, IApplication, ICommand, PubSub } from "chili-core"; }) export class SaveDocument implements ICommand { async execute(app: IApplication): Promise { - if (app.activeDocument) { - await app.activeDocument.save(); - PubSub.default.pub("showToast", "toast.document.saved"); - } else { - PubSub.default.pub("showToast", "toast.document.noActived"); - } + if (!app.activeDocument) return; + PubSub.default.pub( + "showPermanent", + async () => { + await app.activeDocument!.save(); + PubSub.default.pub("showToast", "toast.document.saved"); + }, + "toast.excuting{0}", + I18n.translate("command.document.save"), + ); } } diff --git a/packages/chili/src/commands/application/toFile.ts b/packages/chili/src/commands/application/toFile.ts index 07ef839c..9a334bb4 100644 --- a/packages/chili/src/commands/application/toFile.ts +++ b/packages/chili/src/commands/application/toFile.ts @@ -1,6 +1,6 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { command, download, IApplication, ICommand, PubSub } from "chili-core"; +import { AsyncController, command, download, I18n, IApplication, ICommand, PubSub } from "chili-core"; @command({ name: "doc.saveToFile", @@ -9,10 +9,19 @@ import { command, download, IApplication, ICommand, PubSub } from "chili-core"; }) export class SaveDocumentToFile implements ICommand { async execute(app: IApplication): Promise { - if (app.activeDocument) { - let s = app.activeDocument.serialize(); - PubSub.default.pub("showToast", "toast.downloading"); - download([JSON.stringify(s)], `${app.activeDocument.name}.cd`); - } + if (!app.activeDocument) return; + PubSub.default.pub( + "showPermanent", + async () => { + await new Promise((r, j) => { + setTimeout(r, 100); + }); + let s = app.activeDocument!.serialize(); + PubSub.default.pub("showToast", "toast.downloading"); + download([JSON.stringify(s)], `${app.activeDocument!.name}.cd`); + }, + "toast.excuting{0}", + I18n.translate("command.document.saveToFile"), + ); } } diff --git a/packages/chili/src/commands/importExport.ts b/packages/chili/src/commands/importExport.ts index e1713600..060fffd3 100644 --- a/packages/chili/src/commands/importExport.ts +++ b/packages/chili/src/commands/importExport.ts @@ -3,6 +3,7 @@ import { AsyncController, GeometryModel, + I18n, IApplication, ICommand, IDocument, @@ -29,13 +30,19 @@ let count = 1; }) export class Import implements ICommand { async execute(application: IApplication): Promise { - let document = application.activeDocument; - if (!document) return; - let shape = await this.readShape(application); - Transaction.excute(document, "import model", () => { - this.addImportedShape(document!, shape); - }); - document.visual.viewer.activeView?.cameraController.fitContent(); + if (!application.activeDocument) return; + PubSub.default.pub( + "showPermanent", + async () => { + let shape = await this.readShape(application); + Transaction.excute(application.activeDocument!, "import model", () => { + this.addImportedShape(application.activeDocument!, shape); + }); + application.activeDocument!.visual.viewer.activeView?.cameraController.fitContent(); + }, + "toast.excuting{0}", + I18n.translate("command.import"), + ); } private addImportedShape = (document: IDocument, shape: [string | undefined, Result]) => { @@ -75,36 +82,44 @@ abstract class Export implements ICommand { async execute(application: IApplication): Promise { if (!application.activeDocument) return; let type = this.getType(); - let data = await this.convertShapeAsync(application, type); - if (data) { - PubSub.default.pub("showToast", "toast.downloading"); - download([data.data], `${data.name}.${type}`); - } - } - - abstract getType(): "iges" | "step"; - - protected async convertShapeAsync(application: IApplication, type: "iges" | "step") { let models = await this.selectModelsAsync(application); if (!models || models.length === 0) { PubSub.default.pub("showToast", "toast.select.noSelected"); return; } - let shapes = models.map((x) => x.shape()!); - let shapeString = - type === "iges" - ? application.shapeFactory.converter.convertToIGES(...shapes) - : application.shapeFactory.converter.convertToSTEP(...shapes); - if (!shapeString.success) { - PubSub.default.pub("showToast", "toast.converter.error"); - return; - } - return { - name: `${models[0].name}`, - data: shapeString.value, - }; + + PubSub.default.pub( + "showPermanent", + async () => { + let shapes = models!.map((x) => x.shape()!); + let shapeString = await this.convertAsync(application, type, ...shapes); + if (!shapeString.success) { + PubSub.default.pub("showToast", "toast.converter.error"); + return; + } + PubSub.default.pub("showToast", "toast.downloading"); + download([shapeString.value], `${models![0].name}.${type}`); + }, + "toast.excuting{0}", + "", + ); } + private async convertAsync( + application: IApplication, + type: string, + ...shapes: IShape[] + ): Promise> { + await new Promise((r, j) => { + setTimeout(r, 50); + }); // 等待弹窗完成 + return type === "iges" + ? application.shapeFactory.converter.convertToIGES(...shapes) + : application.shapeFactory.converter.convertToSTEP(...shapes); + } + + abstract getType(): "iges" | "step"; + private async selectModelsAsync(application: IApplication) { let models = application .activeDocument!.selection.getSelectedNodes()