feat: animate account sections and align header actions

This commit is contained in:
Sucukdeluxe
2026-08-11 01:03:05 +02:00
parent 52ee5e3b13
commit 203c0846c7
3 changed files with 115 additions and 26 deletions
+25 -20
View File
@@ -4936,12 +4936,13 @@ function _buildHosterSettingsHtml(name) {
const hs = (config.hosterSettings && config.hosterSettings[name]) || {};
const maxSpeedMbs = hs.maxSpeedKbs > 0 ? String(+(hs.maxSpeedKbs / 1024).toFixed(2)) : '0';
return `<div class="account-hoster-settings">
<div class="account-hoster-settings-header" data-hoster-settings-toggle="${name}">
<div class="account-hoster-settings-header" data-hoster-settings-toggle="${name}" aria-expanded="false">
<span class="panel-arrow">&#9654;</span>
<span>Upload-Einstellungen</span>
</div>
<div class="account-hoster-settings-body" style="display:none">
<div class="settings-grid-mini">
<div class="account-hoster-settings-body account-collapse" aria-hidden="true" inert>
<div class="account-collapse-content">
<div class="account-hoster-settings-body-inner settings-grid-mini">
<div class="settings-row">
<label>Retries</label>
<input type="number" class="hs-input" data-hoster="${name}" data-hs="retries" value="${hs.retries ?? 3}" min="0" max="500">
@@ -4984,6 +4985,7 @@ function _buildHosterSettingsHtml(name) {
<input type="checkbox" class="hs-input" data-hoster="${name}" data-hs="sizeMemoEnabled" ${hs.sizeMemoEnabled !== false ? 'checked' : ''}>
<span class="hint">Überspringt nach zwei verdächtigen Ablehnungen auf einem Account größere Dateien dort vorab ("Bekanntes Größen-Limit"). Abschalten = jede Datei wird immer wirklich versucht.</span>
</div>
</div>
</div>
</div>
</div>`;
@@ -4994,17 +4996,15 @@ function _buildAccountHosterGroupHtml(name, accounts) {
const isOpen = _hosterGroupOpenState(name, summary);
const dot = window.AccountStatus.getAccountGroupStatus(summary);
const countLabel = `${summary.ok}/${summary.total}`;
const arrow = isOpen ? '&#9660;' : '&#9654;';
let cardsHtml = '';
accounts.forEach((account, idx) => { cardsHtml += _buildAccountCardHtml(name, account, idx); });
const bodyStyle = isOpen ? '' : 'style="display:none"';
const lifeStat = _hosterLifetimeStat(name);
const lifeMeta = lifeStat && lifeStat.total > 0
? `<span class="account-hoster-group-meta" title="Erfolgsrate aus den letzten ${lifeStat.total} Uploads dieses Hosters">${Math.round(lifeStat.rate * 100)}% ok (${lifeStat.total})</span>`
: '';
return `<div class="account-hoster-group" data-hoster-group="${name}">
<div class="account-hoster-group-header" data-hoster-toggle="${name}">
<span class="panel-arrow">${arrow}</span>
<div class="account-hoster-group-header" data-hoster-toggle="${name}" aria-expanded="${isOpen}">
<span class="panel-arrow">&#9654;</span>
<span class="account-status-dot status-${dot}"></span>
<span class="account-hoster-group-title">${escapeHtml(getHosterLabel(name))}</span>
<span class="account-hoster-group-count">${countLabel}</span>
@@ -5012,7 +5012,9 @@ function _buildAccountHosterGroupHtml(name, accounts) {
${summary.error ? `<span class="account-hoster-group-meta error">${summary.error} Fehler</span>` : ''}
${lifeMeta}
</div>
<div class="account-hoster-group-body" ${bodyStyle}>${cardsHtml}</div>
<div class="account-hoster-group-body account-collapse${isOpen ? ' is-open' : ''}" aria-hidden="${!isOpen}" ${isOpen ? '' : 'inert'}>
<div class="account-collapse-content"><div class="account-hoster-group-body-inner">${cardsHtml}</div></div>
</div>
${_buildHosterSettingsHtml(name)}
</div>`;
}
@@ -5029,18 +5031,25 @@ function _invalidateHosterLifetimeCache() { _hosterLifetimeCache = null; }
function _allAccountGroupsOpen() {
const bodies = document.querySelectorAll('#accountsList .account-hoster-group-body');
if (!bodies.length) return false;
for (const b of bodies) if (b.style.display === 'none') return false;
for (const body of bodies) if (!body.classList.contains('is-open')) return false;
return true;
}
function _setAccountCollapseOpen(trigger, body, open) {
if (!body) return;
body.classList.toggle('is-open', open);
body.setAttribute('aria-hidden', String(!open));
body.toggleAttribute('inert', !open);
if (trigger) trigger.setAttribute('aria-expanded', String(open));
}
function _setAllAccountGroupsOpen(open) {
const groups = document.querySelectorAll('#accountsList .account-hoster-group');
groups.forEach(group => {
const name = group.dataset.hosterGroup;
const body = group.querySelector('.account-hoster-group-body');
const arrow = group.querySelector('.panel-arrow');
if (body) body.style.display = open ? '' : 'none';
if (arrow) arrow.innerHTML = open ? '&#9660;' : '&#9654;';
const header = group.querySelector('[data-hoster-toggle]');
_setAccountCollapseOpen(header, body, open);
if (name) {
const summary = _summarizeHosterGroup(config.hosters[name] || []);
_hosterGroupOpenMemory.set(name, { state: open ? 'open' : 'closed', errorsAtClose: summary.error });
@@ -5068,11 +5077,9 @@ function bindAccountListeners(container) {
const name = header.dataset.hosterToggle;
const group = header.closest('.account-hoster-group');
const body = group && group.querySelector('.account-hoster-group-body');
const arrow = header.querySelector('.panel-arrow');
if (body) {
const willOpen = body.style.display === 'none';
body.style.display = willOpen ? '' : 'none';
if (arrow) arrow.innerHTML = willOpen ? '&#9660;' : '&#9654;';
const willOpen = !body.classList.contains('is-open');
_setAccountCollapseOpen(header, body, willOpen);
const summary = _summarizeHosterGroup(config.hosters[name] || []);
_hosterGroupOpenMemory.set(name, { state: willOpen ? 'open' : 'closed', errorsAtClose: summary.error });
}
@@ -5081,11 +5088,9 @@ function bindAccountListeners(container) {
const settingsHeader = e.target.closest('[data-hoster-settings-toggle]');
if (settingsHeader) {
const body = settingsHeader.nextElementSibling;
const arrow = settingsHeader.querySelector('.panel-arrow');
if (body) {
const isOpen = body.style.display !== 'none';
body.style.display = isOpen ? 'none' : '';
if (arrow) arrow.innerHTML = isOpen ? '&#9654;' : '&#9660;';
const willOpen = !body.classList.contains('is-open');
_setAccountCollapseOpen(settingsHeader, body, willOpen);
}
return;
}
+51 -3
View File
@@ -1275,7 +1275,7 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
}
.account-hoster-settings-header:hover { background: var(--bg-card-hover); color: var(--text); }
.account-hoster-settings-header .panel-arrow { font-size: 10px; color: var(--text-dim); }
.account-hoster-settings-body { padding: 4px 12px 10px; }
.account-hoster-settings-body-inner { padding: 4px 12px 10px; }
.settings-hoster-pointer {
margin-top: 12px;
@@ -1374,7 +1374,7 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
.batch-cat-tag.retryable { background: rgba(76, 175, 80, 0.18); color: #a5d6a7; }
.batch-cat-list { margin: 0; padding-left: 18px; font-size: 11px; color: var(--text-muted); }
.batch-cat-list em { color: var(--text-muted); font-style: italic; }
.account-hoster-group-body {
.account-hoster-group-body-inner {
padding: 8px;
border-top: 1px solid var(--border);
}
@@ -1392,6 +1392,38 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
display: inline-block;
}
.account-collapse {
display: grid;
grid-template-rows: 0fr;
opacity: 0;
visibility: hidden;
transition: grid-template-rows 220ms cubic-bezier(.2, .8, .2, 1), opacity 160ms ease, visibility 0s linear 220ms;
}
.account-collapse.is-open {
grid-template-rows: 1fr;
opacity: 1;
visibility: visible;
transition-delay: 0s;
}
.account-collapse-content {
min-height: 0;
overflow: hidden;
}
.account-hoster-group-header .panel-arrow,
.account-hoster-settings-header .panel-arrow {
display: inline-block;
transform-origin: center;
transition: transform 180ms cubic-bezier(.2, .8, .2, 1);
}
.account-hoster-group-header[aria-expanded="true"] .panel-arrow,
.account-hoster-settings-header[aria-expanded="true"] .panel-arrow {
transform: rotate(90deg);
}
.accounts-empty {
width: min(520px, 100%);
margin: clamp(56px, 12vh, 120px) auto 0;
@@ -1716,6 +1748,15 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
.menu-closing {
animation-duration: 160ms !important;
}
.account-collapse {
transition-duration: 220ms, 160ms, 0s !important;
}
.account-hoster-group-header .panel-arrow,
.account-hoster-settings-header .panel-arrow {
transition-duration: 180ms !important;
}
}
.online-backup-panel {
@@ -3186,10 +3227,17 @@ input[type="checkbox"] {
}
.accounts-auto-check {
min-height: 38px;
min-height: 34px;
height: 34px;
padding: 0 12px;
border-radius: 6px;
background: var(--bg-raised);
box-sizing: border-box;
}
.accounts-header-actions > .btn {
min-height: 34px;
height: 34px;
}
.account-health-results {