Dialogs are now properly disposed.

This commit is contained in:
Daan Vanden Bosch 2020-01-08 21:42:12 +01:00
parent b93b22a223
commit b2e0a612f8
5 changed files with 93 additions and 44 deletions

View File

@ -8,29 +8,52 @@ import "./Dialog.css";
const DIALOG_WIDTH = 500;
const DIALOG_MAX_HEIGHT = 500;
/**
* A popup window with a title, description, body and dismiss button.
*/
export class Dialog extends ResizableWidget {
private x = 0;
private y = 0;
private prev_mouse_x = 0;
private prev_mouse_y = 0;
private readonly overlay_element: HTMLElement;
private readonly header_element = h1();
private readonly description_element = div({ className: "core_Dialog_description" });
private readonly content_element = div({ className: "core_Dialog_body" });
private readonly dismiss_button = this.disposable(new Button({ text: "Dismiss" }));
private readonly footer_element = div(
{ className: "core_Dialog_footer" },
this.dismiss_button.element,
);
readonly element: HTMLElement;
readonly element: HTMLElement = section(
{ className: "core_Dialog", tabIndex: 0 },
this.header_element,
this.description_element,
this.content_element,
this.footer_element,
);
readonly children: readonly Widget[] = [];
readonly dismiss_button = this.disposable(new Button({ text: "Dismiss" }));
constructor(title: string, description: string, content: Node | string) {
set title(title: string) {
this.header_element.textContent = title;
}
set description(description: string) {
this.description_element.textContent = description;
}
set content(content: Node | string) {
this.content_element.textContent = "";
this.content_element.append(content);
}
constructor(title: string = "", description: string = "", content: Node | string = "") {
super();
let header_element: HTMLElement;
this.element = section(
{ className: "core_Dialog", tabIndex: 0 },
(header_element = h1(title)),
div({ className: "core_Dialog_description" }, description),
div({ className: "core_Dialog_body" }, content),
div({ className: "core_Dialog_footer" }, this.dismiss_button.element),
);
this.title = title;
this.description = description;
this.content = content;
this.element.style.width = `${DIALOG_WIDTH}px`;
this.element.style.maxHeight = `${DIALOG_MAX_HEIGHT}px`;
@ -41,13 +64,12 @@ export class Dialog extends ResizableWidget {
);
this.element.addEventListener("keydown", this.keydown);
header_element.addEventListener("mousedown", this.mousedown);
this.header_element.addEventListener("mousedown", this.mousedown);
this.overlay_element = div({ className: "core_Dialog_modal_overlay", tabIndex: -1 });
this.overlay_element.addEventListener("focus", () => this.element.focus());
document.body.append(this.overlay_element);
this.disposables(this.dismiss_button.onclick.observe(() => this.dispose()));
this.disposables(this.dismiss_button.onclick.observe(() => this.hide()));
this.finalize_construction();
}
@ -57,6 +79,17 @@ export class Dialog extends ResizableWidget {
this.overlay_element.remove();
}
show(): void {
document.body.append(this.overlay_element);
document.body.append(this.element);
this.focus();
}
hide(): void {
this.overlay_element.remove();
this.element.remove();
}
set_position(x: number, y: number): void {
this.x = x;
this.y = y;
@ -88,34 +121,35 @@ export class Dialog extends ResizableWidget {
private keydown = (evt: KeyboardEvent): void => {
if (evt.key === "Escape") {
this.dispose();
this.hide();
}
};
}
/**
* Shows a dialog window if `result` failed or succeeded with problems.
* Shows the details of a result in a dialog window if the result failed or succeeded with problems.
*
* @param dialog
* @param result
* @param problems_message - Message to show if problems occurred when result is successful.
* @param error_message - Message to show if result failed.
*/
export function show_result_dialog(
export function show_result_in_dialog(
dialog: Dialog,
result: Result<unknown>,
problems_message: string,
error_message: string,
): void {
let dialog: Dialog | undefined;
dialog.content = create_result_body(result);
if (!result.success) {
dialog = new Dialog("Error", error_message, create_result_body(result));
dialog.title = "Error";
dialog.description = error_message;
dialog.show();
} else if (result.problems.length) {
dialog = new Dialog("Problems", problems_message, create_result_body(result));
}
if (dialog) {
document.body.append(dialog.element);
dialog.focus();
dialog.title = "Problems";
dialog.description = problems_message;
dialog.show();
}
}

View File

@ -12,7 +12,6 @@ import { ListProperty } from "../../core/observable/property/list/ListProperty";
import { prs_decompress } from "../../core/data_formats/compression/prs/decompress";
import { failure, Result, result_builder } from "../../core/Result";
import { Severity } from "../../core/Severity";
import { show_result_dialog } from "../../core/gui/Dialog";
const logger = LogManager.get("viewer/controllers/TextureController");
@ -20,7 +19,7 @@ export class TextureController extends Controller {
private readonly _textures: WritableListProperty<XvrTexture> = list_property();
readonly textures: ListProperty<XvrTexture> = this._textures;
load_file = async (file: File): Promise<void> => {
load_file = async (file: File): Promise<Result<unknown>> => {
let result: Result<unknown>;
try {
@ -75,10 +74,6 @@ export class TextureController extends Controller {
result = failure();
}
show_result_dialog(
result,
`Encountered some problems while opening "${file.name}".`,
`Couldn't open "${file.name}".`,
);
return result;
};
}

View File

@ -12,7 +12,6 @@ import { LogManager } from "../../../core/Logger";
import { prs_decompress } from "../../../core/data_formats/compression/prs/decompress";
import { failure, Result, result_builder, success } from "../../../core/Result";
import { Severity } from "../../../core/Severity";
import { show_result_dialog } from "../../../core/gui/Dialog";
const logger = LogManager.get("viewer/controllers/model/ModelToolBarController");
@ -53,7 +52,7 @@ export class ModelToolBarController extends Controller {
this.store.set_animation_frame(frame);
};
load_file = async (file: File): Promise<void> => {
load_file = async (file: File): Promise<Result<unknown>> => {
let result: Result<unknown>;
try {
@ -136,10 +135,6 @@ export class ModelToolBarController extends Controller {
result = failure();
}
show_result_dialog(
result,
`Encountered some problems while opening "${file.name}".`,
`Couldn't open "${file.name}".`,
);
return result;
};
}

View File

@ -5,6 +5,7 @@ import { RendererWidget } from "../../core/gui/RendererWidget";
import { TextureRenderer } from "../rendering/TextureRenderer";
import { ResizableView } from "../../core/gui/ResizableView";
import { TextureController } from "../controllers/TextureController";
import { Dialog, show_result_in_dialog } from "../../core/gui/Dialog";
export class TextureView extends ResizableView {
readonly element = div({ className: "viewer_TextureView" });
@ -26,9 +27,21 @@ export class TextureView extends ResizableView {
this.element.append(this.tool_bar.element, this.renderer_view.element);
const dialog = this.disposable(new Dialog());
this.disposables(
this.open_file_button.files.observe(({ value: files }) => {
if (files.length) ctrl.load_file(files[0]);
this.open_file_button.files.observe(async ({ value: files }) => {
if (files.length) {
const file = files[0];
const result = await ctrl.load_file(file);
show_result_in_dialog(
dialog,
result,
`Encountered some problems while opening "${file.name}".`,
`Couldn't open "${file.name}".`,
);
}
}),
);

View File

@ -7,6 +7,7 @@ import { Label } from "../../../core/gui/Label";
import { Icon } from "../../../core/gui/dom";
import { View } from "../../../core/gui/View";
import { ModelToolBarController } from "../../controllers/model/ModelToolBarController";
import { Dialog, show_result_in_dialog } from "../../../core/gui/Dialog";
export class ModelToolBarView extends View {
private readonly toolbar: ToolBar;
@ -54,10 +55,21 @@ export class ModelToolBarView extends View {
),
);
const dialog = this.disposable(new Dialog());
// Always-enabled controls.
this.disposables(
open_file_button.files.observe(({ value: files }) => {
if (files.length) ctrl.load_file(files[0]);
open_file_button.files.observe(async ({ value: files }) => {
if (files.length) {
const file = files[0];
const result = await ctrl.load_file(file);
show_result_in_dialog(
dialog,
result,
`Encountered some problems while opening "${file.name}".`,
`Couldn't open "${file.name}".`,
);
}
}),
skeleton_checkbox.checked.observe(({ value }) => ctrl.set_show_skeleton(value)),