2019-01-15 03:58:34 +07:00
|
|
|
import { Emitter } from "@coder/events";
|
2019-01-08 07:46:19 +07:00
|
|
|
|
|
|
|
import "./dialog.scss";
|
|
|
|
|
|
|
|
export interface IDialogOptions {
|
|
|
|
message?: string;
|
|
|
|
detail?: string;
|
|
|
|
buttons?: string[];
|
|
|
|
input?: {
|
|
|
|
value: string;
|
|
|
|
selection?: {
|
|
|
|
start: number;
|
|
|
|
end: number;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface IDialogAction {
|
|
|
|
buttonIndex?: number;
|
|
|
|
key?: IKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum IKey {
|
|
|
|
Enter = "Enter",
|
|
|
|
Escape = "Escape",
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Dialog {
|
2019-02-07 00:53:23 +07:00
|
|
|
private readonly overlay: HTMLElement;
|
2019-01-15 03:58:34 +07:00
|
|
|
private cachedActiveElement: HTMLElement | undefined;
|
|
|
|
private input: HTMLInputElement | undefined;
|
2019-01-08 07:46:19 +07:00
|
|
|
private errors: HTMLElement;
|
2019-01-15 03:58:34 +07:00
|
|
|
private buttons: HTMLElement[] | undefined;
|
2019-01-08 07:46:19 +07:00
|
|
|
|
2019-02-07 00:53:23 +07:00
|
|
|
private actionEmitter = new Emitter<IDialogAction>();
|
|
|
|
public onAction = this.actionEmitter.event;
|
2019-01-08 07:46:19 +07:00
|
|
|
|
2019-02-07 00:53:23 +07:00
|
|
|
public constructor(private readonly options: IDialogOptions) {
|
2019-01-08 07:46:19 +07:00
|
|
|
const msgBox = document.createElement("div");
|
|
|
|
msgBox.classList.add("msgbox");
|
|
|
|
|
|
|
|
if (this.options.message) {
|
|
|
|
const messageDiv = document.createElement("div");
|
|
|
|
messageDiv.classList.add("msg");
|
|
|
|
messageDiv.innerText = this.options.message;
|
|
|
|
msgBox.appendChild(messageDiv);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.options.detail) {
|
|
|
|
const detailDiv = document.createElement("div");
|
|
|
|
detailDiv.classList.add("detail");
|
|
|
|
detailDiv.innerText = this.options.detail;
|
|
|
|
msgBox.appendChild(detailDiv);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.options.input) {
|
|
|
|
msgBox.classList.add("input");
|
|
|
|
this.input = document.createElement("input");
|
|
|
|
this.input.classList.add("input");
|
|
|
|
this.input.value = this.options.input.value;
|
|
|
|
this.input.addEventListener("keydown", (event) => {
|
|
|
|
if (event.key === IKey.Enter) {
|
|
|
|
event.preventDefault();
|
|
|
|
this.actionEmitter.emit({
|
|
|
|
buttonIndex: undefined,
|
|
|
|
key: IKey.Enter,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
msgBox.appendChild(this.input);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.errors = document.createElement("div");
|
|
|
|
this.errors.classList.add("errors");
|
|
|
|
msgBox.appendChild(this.errors);
|
|
|
|
|
|
|
|
if (this.options.buttons && this.options.buttons.length > 0) {
|
|
|
|
this.buttons = this.options.buttons.map((buttonText, buttonIndex) => {
|
|
|
|
const button = document.createElement("button");
|
2019-01-26 07:18:21 +07:00
|
|
|
// TODO: support mnemonics.
|
|
|
|
button.innerText = buttonText.replace("_", "");
|
2019-01-08 07:46:19 +07:00
|
|
|
button.addEventListener("click", () => {
|
|
|
|
this.actionEmitter.emit({
|
|
|
|
buttonIndex,
|
|
|
|
key: undefined,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return button;
|
|
|
|
});
|
|
|
|
|
|
|
|
const buttonWrapper = document.createElement("div");
|
|
|
|
buttonWrapper.classList.add("button-wrapper");
|
|
|
|
this.buttons.forEach((b) => buttonWrapper.appendChild(b));
|
|
|
|
msgBox.appendChild(buttonWrapper);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.overlay = document.createElement("div");
|
2019-01-15 03:58:34 +07:00
|
|
|
this.overlay.className = "msgbox-overlay";
|
2019-01-08 07:46:19 +07:00
|
|
|
this.overlay.appendChild(msgBox);
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
this.overlay.style.opacity = "1";
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Input value if this dialog has an input.
|
|
|
|
*/
|
2019-01-15 03:58:34 +07:00
|
|
|
public get inputValue(): string | undefined {
|
2019-01-08 07:46:19 +07:00
|
|
|
return this.input ? this.input.value : undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Display or remove an error.
|
|
|
|
*/
|
2019-01-19 04:46:40 +07:00
|
|
|
public set error(error: string | undefined) {
|
2019-01-08 07:46:19 +07:00
|
|
|
while (this.errors.lastChild) {
|
|
|
|
this.errors.removeChild(this.errors.lastChild);
|
|
|
|
}
|
|
|
|
if (error) {
|
|
|
|
const errorDiv = document.createElement("error");
|
|
|
|
errorDiv.innerText = error;
|
|
|
|
this.errors.appendChild(errorDiv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show the dialog.
|
|
|
|
*/
|
|
|
|
public show(): void {
|
|
|
|
if (!this.cachedActiveElement) {
|
|
|
|
this.cachedActiveElement = document.activeElement as HTMLElement;
|
|
|
|
document.body.appendChild(this.overlay);
|
|
|
|
document.addEventListener("keydown", this.onKeydown);
|
|
|
|
if (this.input) {
|
|
|
|
this.input.focus();
|
2019-01-15 03:58:34 +07:00
|
|
|
if (this.options.input && this.options.input.selection) {
|
2019-01-08 07:46:19 +07:00
|
|
|
this.input.setSelectionRange(
|
|
|
|
this.options.input.selection.start,
|
2019-01-15 03:58:34 +07:00
|
|
|
this.options.input.selection.end,
|
2019-01-08 07:46:19 +07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} else if (this.buttons) {
|
|
|
|
this.buttons[0].focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the dialog and clean up.
|
|
|
|
*/
|
|
|
|
public hide(): void {
|
|
|
|
if (this.cachedActiveElement) {
|
|
|
|
this.overlay.remove();
|
|
|
|
document.removeEventListener("keydown", this.onKeydown);
|
|
|
|
this.cachedActiveElement.focus();
|
2019-01-15 03:58:34 +07:00
|
|
|
this.cachedActiveElement = undefined;
|
2019-01-08 07:46:19 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Capture escape.
|
|
|
|
*/
|
|
|
|
private onKeydown = (event: KeyboardEvent): void => {
|
|
|
|
if (event.key === "Escape") {
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
|
|
|
this.actionEmitter.emit({
|
|
|
|
buttonIndex: undefined,
|
|
|
|
key: IKey.Escape,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|