import { FolkShape } from "./folk-shape"; import { css, html } from "./tags"; const PIANO_URL = "https://musiclab.chromeexperiments.com/Shared-Piano/"; const styles = css` :host { background: #1e1e2e; border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); min-width: 400px; min-height: 300px; overflow: hidden; } .piano-container { width: 100%; height: 100%; position: relative; } .piano-iframe { width: 100%; height: 100%; border: none; } .loading { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #1e1e2e 0%, #2d2d44 100%); color: white; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 18px; gap: 12px; } .loading.hidden { display: none; } .error { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, #1e1e2e 0%, #2d2d44 100%); color: white; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; gap: 12px; } .error.hidden { display: none; } .error-message { font-size: 14px; color: #f87171; } .retry-btn { background: #6366f1; color: white; border: none; border-radius: 6px; padding: 8px 16px; cursor: pointer; font-size: 14px; } .retry-btn:hover { background: #4f46e5; } .controls { position: absolute; top: 8px; right: 8px; display: flex; gap: 4px; } .control-btn { background: rgba(0, 0, 0, 0.5); color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer; font-size: 14px; } .control-btn:hover { background: rgba(0, 0, 0, 0.7); } .minimized { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #1e1e2e 0%, #2d2d44 100%); color: white; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 24px; cursor: pointer; } .minimized.hidden { display: none; } `; declare global { interface HTMLElementTagNameMap { "folk-piano": FolkPiano; } } export class FolkPiano extends FolkShape { static override tagName = "folk-piano"; static { const sheet = new CSSStyleSheet(); const parentRules = Array.from(FolkShape.styles.cssRules) .map((r) => r.cssText) .join("\n"); const childRules = Array.from(styles.cssRules) .map((r) => r.cssText) .join("\n"); sheet.replaceSync(`${parentRules}\n${childRules}`); this.styles = sheet; } #isMinimized = false; #isLoading = true; #hasError = false; #iframe: HTMLIFrameElement | null = null; #loadingEl: HTMLElement | null = null; #errorEl: HTMLElement | null = null; #minimizedEl: HTMLElement | null = null; #containerEl: HTMLElement | null = null; get isMinimized() { return this.#isMinimized; } set isMinimized(value: boolean) { this.#isMinimized = value; this.#updateVisibility(); this.requestUpdate("isMinimized"); } override createRenderRoot() { const root = super.createRenderRoot(); const wrapper = document.createElement("div"); wrapper.innerHTML = html`
\u{1F3B9} Loading Shared Piano...
`; // Replace the container div (slot's parent) with our piano container const slot = root.querySelector("slot"); const containerDiv = slot?.parentElement as HTMLElement; const pianoContainer = wrapper.querySelector(".piano-container"); if (containerDiv && pianoContainer) { containerDiv.replaceWith(pianoContainer); } // Get references this.#containerEl = root.querySelector(".piano-container"); this.#loadingEl = root.querySelector(".loading"); this.#errorEl = root.querySelector(".error"); this.#minimizedEl = root.querySelector(".minimized"); this.#iframe = root.querySelector(".piano-iframe"); const minimizeBtn = root.querySelector(".minimize-btn") as HTMLButtonElement; const retryBtn = root.querySelector(".retry-btn") as HTMLButtonElement; // Iframe load handling this.#iframe?.addEventListener("load", () => { this.#isLoading = false; this.#hasError = false; if (this.#loadingEl) this.#loadingEl.classList.add("hidden"); if (this.#iframe) this.#iframe.style.opacity = "1"; }); this.#iframe?.addEventListener("error", () => { this.#isLoading = false; this.#hasError = true; if (this.#loadingEl) this.#loadingEl.classList.add("hidden"); if (this.#errorEl) this.#errorEl.classList.remove("hidden"); }); // Minimize toggle minimizeBtn?.addEventListener("click", (e) => { e.stopPropagation(); this.isMinimized = !this.#isMinimized; minimizeBtn.textContent = this.#isMinimized ? "\u{1F53D}" : "\u{1F53C}"; }); // Click minimized view to expand this.#minimizedEl?.addEventListener("click", (e) => { e.stopPropagation(); this.isMinimized = false; if (minimizeBtn) minimizeBtn.textContent = "\u{1F53C}"; }); // Retry button retryBtn?.addEventListener("click", (e) => { e.stopPropagation(); this.#retry(); }); // Suppress Chrome Music Lab console errors window.addEventListener("error", (e) => { if (e.message?.includes("musiclab") || e.filename?.includes("musiclab")) { e.preventDefault(); } }); return root; } #updateVisibility() { if (!this.#iframe || !this.#minimizedEl) return; if (this.#isMinimized) { this.#iframe.style.display = "none"; this.#minimizedEl.classList.remove("hidden"); } else { this.#iframe.style.display = "block"; this.#minimizedEl.classList.add("hidden"); } } #retry() { if (!this.#iframe || !this.#errorEl || !this.#loadingEl) return; this.#hasError = false; this.#isLoading = true; this.#errorEl.classList.add("hidden"); this.#loadingEl.classList.remove("hidden"); this.#iframe.style.opacity = "0"; this.#iframe.src = PIANO_URL; } override toJSON() { return { ...super.toJSON(), type: "folk-piano", isMinimized: this.isMinimized, }; } }