import { get } from "@rails/request.js"; export interface ModalOptions { id?: string; url: string; size?: string; close?: boolean; } const loaded: Promise = new Promise((res) => { document.addEventListener("DOMContentLoaded", () => { res(); }); }); async function nextFrame(): Promise { return new Promise((res) => { setTimeout(res, 10); }); } async function getHTML(url: string): Promise { const response = await get(url, { headers: { "X-Shimmer": "true" } }); if (response.ok) { return await response.response.text(); } return ""; } function createElement(parent: HTMLElement, className: string): HTMLDivElement { const element = document.createElement("div"); element.className = className; parent.append(element); return element; } export class ModalPresenter { private modals: Record = {}; constructor() { loaded.then(this.prepareBlind); } async open(options: ModalOptions): Promise { const id = (options.id = options.id ?? "default-modal"); (this.modals[id] = new Modal({ presenter: this, id })).open(options); this.updateBlindStatus(); } async close({ id }: { id?: string } = {}): Promise { let promise: Promise | null = null; if (id) { promise = this.modals[id]?.close(); delete this.modals[id]; } else { promise = Promise.all(Object.values(this.modals).map((e) => e.close())); this.modals = {}; } this.updateBlindStatus(); await promise; } private updateBlindStatus(): void { const open = Object.keys(this.modals).length > 0; document.body.classList.toggle("modal-open", open); } private async prepareBlind(): Promise { createElement(document.body, "modal-blind"); } } export class Modal { private readonly root: HTMLDivElement; private readonly frame: HTMLDivElement; private readonly closeButton: HTMLDivElement; constructor({ presenter, id }: { presenter: ModalPresenter; id: string }) { this.root = createElement(document.body, "modal"); const content = createElement(this.root, "modal__content"); this.closeButton = createElement(content, "modal__close"); this.closeButton.addEventListener("click", () => { presenter.close({ id }); }); this.frame = createElement(content, "modal__frame"); } async open({ size, url, close }: ModalOptions): Promise { await nextFrame(); this.closeButton.style.display = close ?? true ? "block" : "none"; this.root.classList.add("modal--open"); this.root.classList.add("modal--loading"); this.root.classList.toggle("modal--small", size === "small"); this.frame.innerHTML = await getHTML(url); } async close(): Promise { this.root.classList.remove("modal--open"); } }