fix(setup): use the shared eo-modal for drive details
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 <noreply@anthropic.com>
This commit is contained in:
parent
7eb6d36d55
commit
e71fc15f38
@ -1268,36 +1268,15 @@ body.setup-wizard-open {
|
|||||||
.setup-storage-details:hover { background: rgba(255, 255, 255, 0.16); }
|
.setup-storage-details:hover { background: rgba(255, 255, 255, 0.16); }
|
||||||
.setup-storage-flags { opacity: 0.9; }
|
.setup-storage-flags { opacity: 0.9; }
|
||||||
|
|
||||||
/* Modal */
|
/* The wizard is a full-screen overlay at z-index 9999; the shared modal sits at
|
||||||
.setup-modal-backdrop {
|
1100. A modal opened from inside the wizard therefore rendered BEHIND it and
|
||||||
position: fixed; inset: 0; z-index: 10000;
|
looked like the Details button did nothing. Raise it only while the wizard is
|
||||||
background: rgba(4, 20, 38, 0.62);
|
up, so no other overlay's stacking is affected. */
|
||||||
backdrop-filter: blur(3px);
|
body.setup-wizard-open .eo-modal { z-index: 10000; }
|
||||||
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; }
|
|
||||||
|
|
||||||
|
/* 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 { width: 100%; border-collapse: collapse; margin-bottom: 18px; }
|
||||||
.setup-storage-spec th {
|
.setup-storage-spec th {
|
||||||
text-align: left; font-weight: 500; opacity: 0.72;
|
text-align: left; font-weight: 500; opacity: 0.72;
|
||||||
|
|||||||
@ -504,14 +504,20 @@ class SetupWizard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Details modal: the technical spec, every check with its full explanation,
|
// 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.
|
// 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) {
|
showStorageDetails(idx) {
|
||||||
const c = this.storageCandidates[idx];
|
const c = this.storageCandidates[idx];
|
||||||
if (!c) return;
|
if (!c) return;
|
||||||
const esc = (s) => this.escapeHtml(s);
|
const esc = (s) => this.escapeHtml(s);
|
||||||
|
|
||||||
const specRows = [
|
const spec = [
|
||||||
['Mount point', c.path],
|
['Mount point', c.path],
|
||||||
['Device', c.device],
|
['Device', c.device],
|
||||||
['Filesystem', c.fstype],
|
['Filesystem', c.fstype],
|
||||||
@ -523,7 +529,7 @@ class SetupWizard {
|
|||||||
].map(([k, v]) => `<tr><th>${esc(k)}</th><td><code>${esc(v)}</code></td></tr>`).join('');
|
].map(([k, v]) => `<tr><th>${esc(k)}</th><td><code>${esc(v)}</code></td></tr>`).join('');
|
||||||
|
|
||||||
const icon = { refuse: '\u26d4', warn: '\u26a0\ufe0f', info: '\u2705' };
|
const icon = { refuse: '\u26d4', warn: '\u26a0\ufe0f', info: '\u2705' };
|
||||||
const checkRows = (c.checks || []).map(k => `
|
const checks = (c.checks || []).map(k => `
|
||||||
<li class="setup-storage-check setup-storage-check-${esc(k.severity)}">
|
<li class="setup-storage-check setup-storage-check-${esc(k.severity)}">
|
||||||
<span class="setup-storage-check-icon">${icon[k.severity] || ''}</span>
|
<span class="setup-storage-check-icon">${icon[k.severity] || ''}</span>
|
||||||
<span>
|
<span>
|
||||||
@ -532,64 +538,65 @@ class SetupWizard {
|
|||||||
</span>
|
</span>
|
||||||
</li>`).join('');
|
</li>`).join('');
|
||||||
|
|
||||||
const fstab = c.fstab_line ? `
|
const parts = [
|
||||||
<div class="setup-storage-fstab">
|
window.eoSection ? window.eoSection('Drive', `<table class="setup-storage-spec">${spec}</table>`)
|
||||||
<div class="setup-storage-fstab-title">Make this drive mount automatically</div>
|
: `<table class="setup-storage-spec">${spec}</table>`,
|
||||||
<p>
|
window.eoSection ? window.eoSection('Checks', `<ul class="setup-storage-checks">${checks}</ul>`)
|
||||||
The system doesn\u2019t mount this drive at boot, so apps stored on it won\u2019t start
|
: `<ul class="setup-storage-checks">${checks}</ul>`,
|
||||||
until it is mounted again. LibrePortal can add it to <code>/etc/fstab</code> for you.
|
];
|
||||||
</p>
|
|
||||||
<pre><code>${esc(c.fstab_line)}</code></pre>
|
|
||||||
<label class="setup-storage-fstab-opt">
|
|
||||||
<input type="checkbox" data-storage-fstab="${esc(c.path)}"
|
|
||||||
${this.fstabWanted.includes(c.path) ? 'checked' : ''}>
|
|
||||||
<span>Add this line during install</span>
|
|
||||||
</label>
|
|
||||||
<p class="setup-storage-fstab-note">
|
|
||||||
Written with <code>nofail</code>, so if the drive is missing the machine still boots
|
|
||||||
normally. Your current <code>/etc/fstab</code> is backed up first.
|
|
||||||
</p>
|
|
||||||
</div>` : '';
|
|
||||||
|
|
||||||
const modal = document.createElement('div');
|
if (c.fstab_line) {
|
||||||
modal.className = 'setup-modal-backdrop';
|
parts.push(`
|
||||||
modal.innerHTML = `
|
<div class="setup-storage-fstab">
|
||||||
<div class="setup-modal" role="dialog" aria-modal="true" aria-label="Drive details">
|
<div class="setup-storage-fstab-title">Make this drive mount automatically</div>
|
||||||
<div class="setup-modal-head">
|
<p>
|
||||||
<span class="setup-modal-title">${esc(c.path)}</span>
|
The system doesn\u2019t mount this drive at boot, so apps stored on it won\u2019t start
|
||||||
<button type="button" class="setup-modal-close" aria-label="Close">×</button>
|
until it is mounted again. LibrePortal can add it to <code>/etc/fstab</code> for you.
|
||||||
</div>
|
</p>
|
||||||
<div class="setup-modal-body">
|
<pre><code>${esc(c.fstab_line)}</code></pre>
|
||||||
<table class="setup-storage-spec">${specRows}</table>
|
<label class="setup-storage-fstab-opt">
|
||||||
<ul class="setup-storage-checks">${checkRows}</ul>
|
<input type="checkbox" data-storage-fstab="${esc(c.path)}"
|
||||||
${fstab}
|
${this.fstabWanted.includes(c.path) ? 'checked' : ''}>
|
||||||
</div>
|
<span>Add this line during install</span>
|
||||||
</div>`;
|
</label>
|
||||||
|
<p class="setup-storage-fstab-note">
|
||||||
|
Written with <code>nofail</code>, so if the drive is missing the machine still boots
|
||||||
|
normally. Your current <code>/etc/fstab</code> is backed up first.
|
||||||
|
</p>
|
||||||
|
</div>`);
|
||||||
|
}
|
||||||
|
|
||||||
const close = () => modal.remove();
|
// No openEoModal (shouldn't happen — index.html loads it before this file,
|
||||||
modal.addEventListener('click', (e) => { if (e.target === modal) close(); });
|
// but the wizard must not become unusable if it ever doesn't).
|
||||||
modal.querySelector('.setup-modal-close').addEventListener('click', close);
|
if (typeof window.openEoModal !== 'function') {
|
||||||
document.addEventListener('keydown', function onKey(e) {
|
console.warn('[setup] openEoModal unavailable; skipping details modal');
|
||||||
if (e.key === 'Escape') { close(); document.removeEventListener('keydown', onKey); }
|
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
|
// The choice is remembered on the wizard, not in the DOM: the modal is
|
||||||
// destroyed on close and the choice has to survive to submit().
|
// destroyed on close and this has to survive until submit().
|
||||||
const fstabBox = modal.querySelector('[data-storage-fstab]');
|
const fstabBox = m.bodyEl.querySelector('[data-storage-fstab]');
|
||||||
if (fstabBox) {
|
if (fstabBox) {
|
||||||
fstabBox.addEventListener('change', () => {
|
fstabBox.addEventListener('change', () => {
|
||||||
const path = fstabBox.dataset.storageFstab;
|
const path = fstabBox.dataset.storageFstab;
|
||||||
this.fstabWanted = this.fstabWanted.filter(p => p !== path);
|
this.fstabWanted = this.fstabWanted.filter(p => p !== path);
|
||||||
if (fstabBox.checked) {
|
if (fstabBox.checked) {
|
||||||
this.fstabWanted.push(path);
|
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)}"]`);
|
const own = this.container.querySelector(`[data-storage-path="${CSS.escape(path)}"]`);
|
||||||
if (own && !own.disabled) own.checked = true;
|
if (own && !own.disabled) own.checked = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.appendChild(modal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
escapeHtml(s) {
|
escapeHtml(s) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user