From e71fc15f383b6295310160032b00a848973f6935 Mon Sep 17 00:00:00 2001 From: librelad Date: Wed, 26 Aug 2026 01:57:57 +0100 Subject: [PATCH] fix(setup): use the shared eo-modal for drive details MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The details popup was a hand-rolled overlay instead of openEoModal, which every other modal in the WebUI uses. Two things were wrong with that. It was appended to the wizard container rather than document.body. The wizard has a backdrop-filter, which makes it a containing block, so the modal's position:fixed resolved against the wizard instead of the viewport — the backdrop covered part of the screen and the panel sat off-centre. And once switched to eo-modal it still didn't appear at all: .eo-modal is z-index 1100 while the wizard overlay is 9999, so it rendered behind the wizard and the Details button looked inert. Raised only under body.setup-wizard-open, so no other overlay's stacking is touched — the wizard is the exceptional full-screen surface, so it is the wizard's stylesheet that declares modals must sit above it. The bespoke modal chrome is gone from setup-wizard.css; what remains is the storage-specific content (spec table, checks list, fstab box) that renders inside .eo-modal-body. Header, sections, backdrop, Escape, click-outside and the footer action now all come from the shared helper. Co-Authored-By: Claude Opus 5 --- .../frontend/core/setup/css/setup-wizard.css | 37 ++----- .../frontend/core/setup/js/setup-wizard.js | 101 ++++++++++-------- 2 files changed, 62 insertions(+), 76 deletions(-) diff --git a/containers/libreportal/frontend/core/setup/css/setup-wizard.css b/containers/libreportal/frontend/core/setup/css/setup-wizard.css index 9f9e5b9..d8378ed 100755 --- a/containers/libreportal/frontend/core/setup/css/setup-wizard.css +++ b/containers/libreportal/frontend/core/setup/css/setup-wizard.css @@ -1268,36 +1268,15 @@ body.setup-wizard-open { .setup-storage-details:hover { background: rgba(255, 255, 255, 0.16); } .setup-storage-flags { opacity: 0.9; } -/* Modal */ -.setup-modal-backdrop { - position: fixed; inset: 0; z-index: 10000; - background: rgba(4, 20, 38, 0.62); - backdrop-filter: blur(3px); - display: flex; align-items: center; justify-content: center; - padding: 24px; -} -.setup-modal { - width: min(680px, 100%); - max-height: 82vh; - overflow: auto; - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.18); - background: rgba(12, 48, 84, 0.97); - box-shadow: 0 24px 64px rgba(0, 0, 0, 0.45); -} -.setup-modal-head { - display: flex; align-items: center; justify-content: space-between; - gap: 12px; padding: 16px 20px; - border-bottom: 1px solid rgba(255, 255, 255, 0.14); -} -.setup-modal-title { font-weight: 700; font-size: 1.05em; word-break: break-all; } -.setup-modal-close { - background: none; border: none; color: inherit; - font-size: 1.6em; line-height: 1; cursor: pointer; opacity: 0.75; -} -.setup-modal-close:hover { opacity: 1; } -.setup-modal-body { padding: 18px 20px 22px; } +/* The wizard is a full-screen overlay at z-index 9999; the shared modal sits at + 1100. A modal opened from inside the wizard therefore rendered BEHIND it and + looked like the Details button did nothing. Raise it only while the wizard is + up, so no other overlay's stacking is affected. */ +body.setup-wizard-open .eo-modal { z-index: 10000; } +/* Modal chrome comes from the shared eo-modal (core/overlays). Only the + storage-specific CONTENT styles below are ours — they render inside + .eo-modal-body. */ .setup-storage-spec { width: 100%; border-collapse: collapse; margin-bottom: 18px; } .setup-storage-spec th { text-align: left; font-weight: 500; opacity: 0.72; diff --git a/containers/libreportal/frontend/core/setup/js/setup-wizard.js b/containers/libreportal/frontend/core/setup/js/setup-wizard.js index d408710..99de880 100755 --- a/containers/libreportal/frontend/core/setup/js/setup-wizard.js +++ b/containers/libreportal/frontend/core/setup/js/setup-wizard.js @@ -504,14 +504,20 @@ class SetupWizard { } } - // Details modal: the technical spec, every check with its full explanation, - // and — when the drive isn\u2019t in fstab — the offer to make it permanent. + // Details modal — the technical spec, every check with its full explanation, + // and (when the drive isn't in fstab) the offer to make it permanent. + // + // Uses the shared openEoModal rather than a bespoke overlay. Two reasons: it + // is what every other modal in the WebUI looks like, and it appends to + // document.body — a hand-rolled position:fixed backdrop appended INSIDE the + // wizard container resolves against the wizard's containing block (it has a + // backdrop-filter), so it covered part of the screen instead of all of it. showStorageDetails(idx) { const c = this.storageCandidates[idx]; if (!c) return; const esc = (s) => this.escapeHtml(s); - const specRows = [ + const spec = [ ['Mount point', c.path], ['Device', c.device], ['Filesystem', c.fstype], @@ -523,7 +529,7 @@ class SetupWizard { ].map(([k, v]) => `${esc(k)}${esc(v)}`).join(''); const icon = { refuse: '\u26d4', warn: '\u26a0\ufe0f', info: '\u2705' }; - const checkRows = (c.checks || []).map(k => ` + const checks = (c.checks || []).map(k => `
  • ${icon[k.severity] || ''} @@ -532,64 +538,65 @@ class SetupWizard {
  • `).join(''); - const fstab = c.fstab_line ? ` -
    -
    Make this drive mount automatically
    -

    - The system doesn\u2019t mount this drive at boot, so apps stored on it won\u2019t start - until it is mounted again. LibrePortal can add it to /etc/fstab for you. -

    -
    ${esc(c.fstab_line)}
    - -

    - Written with nofail, so if the drive is missing the machine still boots - normally. Your current /etc/fstab is backed up first. -

    -
    ` : ''; + const parts = [ + window.eoSection ? window.eoSection('Drive', `${spec}
    `) + : `${spec}
    `, + window.eoSection ? window.eoSection('Checks', ``) + : ``, + ]; - const modal = document.createElement('div'); - modal.className = 'setup-modal-backdrop'; - modal.innerHTML = ` - `; + if (c.fstab_line) { + parts.push(` +
    +
    Make this drive mount automatically
    +

    + The system doesn\u2019t mount this drive at boot, so apps stored on it won\u2019t start + until it is mounted again. LibrePortal can add it to /etc/fstab for you. +

    +
    ${esc(c.fstab_line)}
    + +

    + Written with nofail, so if the drive is missing the machine still boots + normally. Your current /etc/fstab is backed up first. +

    +
    `); + } - const close = () => modal.remove(); - modal.addEventListener('click', (e) => { if (e.target === modal) close(); }); - modal.querySelector('.setup-modal-close').addEventListener('click', close); - document.addEventListener('keydown', function onKey(e) { - if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); } + // No openEoModal (shouldn't happen — index.html loads it before this file, + // but the wizard must not become unusable if it ever doesn't). + if (typeof window.openEoModal !== 'function') { + console.warn('[setup] openEoModal unavailable; skipping details modal'); + return; + } + + const m = window.openEoModal({ + id: 'lp-storage-details', + size: 'md', + title: c.path, + desc: `${c.size} drive \u00b7 ${c.free} free \u00b7 ${c.fstype}${c.removable ? ' \u00b7 removable' : ''}`, + body: parts.join(''), + actions: [{ label: 'Close', variant: 'secondary' }], }); - // Remember the fstab choice on the wizard, not in the DOM: the modal is - // destroyed on close and the choice has to survive to submit(). - const fstabBox = modal.querySelector('[data-storage-fstab]'); + // The choice is remembered on the wizard, not in the DOM: the modal is + // destroyed on close and this has to survive until submit(). + const fstabBox = m.bodyEl.querySelector('[data-storage-fstab]'); if (fstabBox) { fstabBox.addEventListener('change', () => { const path = fstabBox.dataset.storageFstab; this.fstabWanted = this.fstabWanted.filter(p => p !== path); if (fstabBox.checked) { this.fstabWanted.push(path); - // Adding it to fstab only makes sense if the drive is actually used. + // Mounting it at boot only means anything if the drive is used. const own = this.container.querySelector(`[data-storage-path="${CSS.escape(path)}"]`); if (own && !own.disabled) own.checked = true; } }); } - - this.container.appendChild(modal); } escapeHtml(s) {