import { html } from "../util.js"; import AuthSession from "../sessionAuth.js"; import buttonCSS from "../buttonStyles.js"; import { escapeHtml } from "../escapeHtml.js"; import { fallbackToRedirect, openAuthPopup, waitForAuthPopup, } from "../authPopup.js"; class PxmeHeader extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); } static get observedAttributes() { return ["active"]; } attributeChangedCallback() { if (this._rendered) this._updateActive(); } connectedCallback() { this._render(); this._initThemeToggle(); this._initAuth(); } _render() { const active = this.getAttribute("active") || "gallery"; this.shadowRoot.innerHTML = html`

`; this._rendered = true; this._updateActive(); } _initThemeToggle() { const toggle = this.shadowRoot.getElementById("theme-toggle"); const sunIcon = this.shadowRoot.getElementById("theme-icon-sun"); const moonIcon = this.shadowRoot.getElementById("theme-icon-moon"); const logo = this.shadowRoot.getElementById("header-logo"); const updateThemeUI = () => { const isDark = document.documentElement.getAttribute("data-theme") === "dark"; sunIcon.classList.toggle("pxme-theme-toggle__icon--hidden", !isDark); moonIcon.classList.toggle("pxme-theme-toggle__icon--hidden", isDark); logo.src = isDark ? "./img/pixme-dark.svg" : "./img/pixme.svg"; toggle.setAttribute( "aria-label", isDark ? "Switch to light mode" : "Switch to dark mode" ); }; updateThemeUI(); toggle.addEventListener("click", () => { const current = document.documentElement.getAttribute("data-theme"); const next = current === "dark" ? "light" : "dark"; document.documentElement.setAttribute("data-theme", next); localStorage.setItem("pxme-theme", next); updateThemeUI(); }); } _updateActive() { const active = this.getAttribute("active") || "gallery"; const links = this.shadowRoot.querySelectorAll(".pxme-nav__link[data-page]"); links.forEach((link) => { link.classList.toggle( "pxme-nav__link--active", link.dataset.page === active ); }); } async _initAuth() { const { isLoggedIn, getSession, isAdmin } = AuthSession({ authUri: "/auth/session", }); if (await isLoggedIn()) { const session = await getSession(); const loginItem = this.shadowRoot.getElementById("login-item"); const navLinks = this.shadowRoot.getElementById("nav-links"); loginItem.innerHTML = ""; const profileDiv = document.createElement("div"); profileDiv.className = "pxme-profile"; const trigger = document.createElement("button"); trigger.className = "pxme-profile__trigger"; trigger.setAttribute("aria-expanded", "false"); trigger.setAttribute("aria-haspopup", "true"); const avatar = document.createElement("img"); avatar.className = "pxme-profile__avatar"; avatar.src = session.user.image || "/img/profile-user.png"; avatar.alt = "Profile"; avatar.width = 40; avatar.height = 40; trigger.appendChild(avatar); const dropdown = document.createElement("div"); dropdown.className = "pxme-profile__dropdown"; dropdown.hidden = true; dropdown.innerHTML = html`
${this._esc(session.user.name || "")} ${this._esc(session.user.email || "")}

`; trigger.addEventListener("click", (e) => { e.stopPropagation(); const open = !dropdown.hidden; dropdown.hidden = open; trigger.setAttribute("aria-expanded", String(!open)); }); document.addEventListener("click", () => { dropdown.hidden = true; trigger.setAttribute("aria-expanded", "false"); }); // Sign out via POST dropdown.querySelector("#signout-btn").addEventListener("click", async () => { await fetch("/auth/signout", { method: "POST" }); window.location.href = "/"; }); profileDiv.appendChild(trigger); profileDiv.appendChild(dropdown); loginItem.appendChild(profileDiv); if (await isAdmin()) { const adminLi = document.createElement("li"); adminLi.className = "pxme-nav__item"; const adminLink = document.createElement("a"); adminLink.href = "/admin.html"; adminLink.textContent = "Admin"; adminLink.className = "pxme-nav__link"; adminLink.dataset.page = "admin"; adminLi.appendChild(adminLink); navLinks.insertBefore(adminLi, loginItem); const facesLi = document.createElement("li"); facesLi.className = "pxme-nav__item"; const facesLink = document.createElement("a"); facesLink.href = "/faces.html"; facesLink.textContent = "Faces"; facesLink.className = "pxme-nav__link"; facesLink.dataset.page = "faces"; facesLi.appendChild(facesLink); navLinks.insertBefore(facesLi, loginItem); this._updateActive(); } return; } const signInLink = this.shadowRoot.querySelector('#login-item a[href="/auth/signin"]'); if (signInLink) { signInLink.addEventListener("click", async (event) => { event.preventDefault(); const popup = openAuthPopup(); if (!popup) { signInLink.textContent = "Popup blocked, redirecting..."; fallbackToRedirect(); return; } signInLink.textContent = "Waiting for sign-in..."; signInLink.setAttribute("aria-disabled", "true"); try { await waitForAuthPopup(popup); window.location.reload(); } catch (error) { const code = error instanceof Error ? error.message : "auth_error"; signInLink.textContent = "Sign In"; signInLink.removeAttribute("aria-disabled"); if (code === "popup_blocked") { signInLink.textContent = "Popup blocked, redirecting..."; fallbackToRedirect(); return; } if (code === "auth_timeout") { signInLink.textContent = "Redirecting to sign-in..."; fallbackToRedirect(); } } }); } } _esc(str) { return escapeHtml(str); } } customElements.define("pxme-header", PxmeHeader);