feat: add data-title attribute to series details elements and optimize series filtering with display:none for better performance
Add data-title="{{ group.title|lower }}" to details.series elements. Update applySeries() to read series title from detail.dataset.title instead of parsing summary textContent. Add explicit display:none style to hidden rows and details alongside hidden class for improved rendering performance
This commit is contained in:
@@ -72,7 +72,7 @@ if ('serviceWorker' in navigator) {
|
|||||||
{% for r in rows %}<tr data-title="{{ (r.title ~ ' ' ~ (r.year or ''))|lower }}" data-audio="{{ r.audio_languages|map(attribute='name')|join(',') }}" data-subs="{{ r.subtitle_languages|map(attribute='name')|join(',') }}" data-state="{{ r.state.label }}"><td><span class="title">{{ r.title }}</span><span class="year">{{ r.year or '' }}</span>{% if r.error %} ⚠{% endif %}</td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state {{ r.state.class }}">{{ r.state.label }}</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}
|
{% for r in rows %}<tr data-title="{{ (r.title ~ ' ' ~ (r.year or ''))|lower }}" data-audio="{{ r.audio_languages|map(attribute='name')|join(',') }}" data-subs="{{ r.subtitle_languages|map(attribute='name')|join(',') }}" data-state="{{ r.state.label }}"><td><span class="title">{{ r.title }}</span><span class="year">{{ r.year or '' }}</span>{% if r.error %} ⚠{% endif %}</td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state {{ r.state.class }}">{{ r.state.label }}</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}
|
||||||
</tbody></table></div></section>
|
</tbody></table></div></section>
|
||||||
{% if sonarr_enabled %}<section id="sonarr" class="view" data-source="sonarr"><div class="toolbar series-toolbar"><input id="seriesSearch" type="search" placeholder="Serie suchen …"><select id="seriesAudioFilter"><option value="">Alle Audio-Sprachen</option><option>Deutsch</option><option>Englisch</option><option>Japanisch</option><option>Koreanisch</option></select><select id="seriesSubFilter"><option value="">Alle Untertitel</option><option>Deutsch</option><option>Englisch</option><option>Japanisch</option><option>Koreanisch</option></select><select id="seriesStateFilter"><option value="">Alle Status</option><option>DE + EN</option><option>Deutsch</option><option>English only</option></select></div><div class="stats"><span class="stat" id="seriesCount">{{ series|length }} Serien</span><span class="stat scan" id="sonarrStatus">Scan wird vorbereitet …</span><span class="stat">Dashboard: {{ local_series_path }}</span></div><div class="tablewrap" id="seriesList">
|
{% if sonarr_enabled %}<section id="sonarr" class="view" data-source="sonarr"><div class="toolbar series-toolbar"><input id="seriesSearch" type="search" placeholder="Serie suchen …"><select id="seriesAudioFilter"><option value="">Alle Audio-Sprachen</option><option>Deutsch</option><option>Englisch</option><option>Japanisch</option><option>Koreanisch</option></select><select id="seriesSubFilter"><option value="">Alle Untertitel</option><option>Deutsch</option><option>Englisch</option><option>Japanisch</option><option>Koreanisch</option></select><select id="seriesStateFilter"><option value="">Alle Status</option><option>DE + EN</option><option>Deutsch</option><option>English only</option></select></div><div class="stats"><span class="stat" id="seriesCount">{{ series|length }} Serien</span><span class="stat scan" id="sonarrStatus">Scan wird vorbereitet …</span><span class="stat">Dashboard: {{ local_series_path }}</span></div><div class="tablewrap" id="seriesList">
|
||||||
{% for category in series_categories %}{% if category.series %}<section class="series-category"><div class="series-category-heading"><h2>{{ category.label }}</h2><span>{{ category.series|length }} Serien</span></div>{% for group in category.series %}<details class="series" open><summary>{{ group.title }} <span class="muted">({{ group.year or '—' }}) · {{ group.episodes|length }} Episoden</span></summary><table><thead><tr><th class="col-title">Episode</th><th class="col-quality">Quality</th><th class="col-state">Status</th><th class="col-audio">Audio</th><th class="col-subs">Untertitel</th><th class="col-video">Video</th><th class="col-size">Größe</th></tr></thead><tbody>{% for r in group.episodes %}<tr data-title="{{ (r.title ~ ' ' ~ (r.year or '') ~ ' ' ~ group.title)|lower }}" data-audio="{{ r.audio_languages|map(attribute='name')|join(',') }}" data-subs="{{ r.subtitle_languages|map(attribute='name')|join(',') }}" data-state="{{ r.state.label }}"><td><span class="title">{{ r.title }}</span></td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state {{ r.state.class }}">{{ r.state.label }}</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}</tbody></table></details>{% endfor %}</section>{% endif %}{% endfor %}
|
{% for category in series_categories %}{% if category.series %}<section class="series-category"><div class="series-category-heading"><h2>{{ category.label }}</h2><span>{{ category.series|length }} Serien</span></div>{% for group in category.series %}<details class="series" data-title="{{ group.title|lower }}" open><summary>{{ group.title }} <span class="muted">({{ group.year or '—' }}) · {{ group.episodes|length }} Episoden</span></summary><table><thead><tr><th class="col-title">Episode</th><th class="col-quality">Quality</th><th class="col-state">Status</th><th class="col-audio">Audio</th><th class="col-subs">Untertitel</th><th class="col-video">Video</th><th class="col-size">Größe</th></tr></thead><tbody>{% for r in group.episodes %}<tr data-title="{{ (r.title ~ ' ' ~ (r.year or '') ~ ' ' ~ group.title)|lower }}" data-audio="{{ r.audio_languages|map(attribute='name')|join(',') }}" data-subs="{{ r.subtitle_languages|map(attribute='name')|join(',') }}" data-state="{{ r.state.label }}"><td><span class="title">{{ r.title }}</span></td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state {{ r.state.class }}">{{ r.state.label }}</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}</tbody></table></details>{% endfor %}</section>{% endif %}{% endfor %}
|
||||||
</div></section>{% endif %}
|
</div></section>{% endif %}
|
||||||
<section id="missing" class="view" data-source="missing"><div class="stats"><span class="stat" id="missingCount">{{ missing_german_movies|length + (missing_german_series|map(attribute='episodes')|map('length')|sum) }} Einträge ohne Deutsch</span><span class="stat scan" id="missingStatus">Scan wird vorbereitet …</span><span class="stat scan" id="seasonZeroHint">Vorhanden oder wird gerade geladen · Staffel 0 ausgeblendet</span></div><div class="tablewrap missing-table">
|
<section id="missing" class="view" data-source="missing"><div class="stats"><span class="stat" id="missingCount">{{ missing_german_movies|length + (missing_german_series|map(attribute='episodes')|map('length')|sum) }} Einträge ohne Deutsch</span><span class="stat scan" id="missingStatus">Scan wird vorbereitet …</span><span class="stat scan" id="seasonZeroHint">Vorhanden oder wird gerade geladen · Staffel 0 ausgeblendet</span></div><div class="tablewrap missing-table">
|
||||||
<table id="missingMovies"><thead><tr><th class="col-title">Film</th><th class="col-quality">Qualität</th><th class="col-state">Status</th><th class="col-audio">Audio</th><th class="col-subs">Untertitel</th><th class="col-video">Video</th><th class="col-size">Größe</th></tr></thead><tbody>{% for r in missing_german_movies %}<tr><td><span class="title">{{ r.title }}</span><span class="year">{{ r.year or '' }}</span></td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state bad">Ohne Deutsch</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}</tbody></table>
|
<table id="missingMovies"><thead><tr><th class="col-title">Film</th><th class="col-quality">Qualität</th><th class="col-state">Status</th><th class="col-audio">Audio</th><th class="col-subs">Untertitel</th><th class="col-video">Video</th><th class="col-size">Größe</th></tr></thead><tbody>{% for r in missing_german_movies %}<tr><td><span class="title">{{ r.title }}</span><span class="year">{{ r.year or '' }}</span></td><td><span class="badge {% if not r.missing %}quality{% endif %}">{{ r.quality }}</span></td><td><span class="state bad">Ohne Deutsch</span></td><td><div class="badges">{% for l in r.audio_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td><div class="badges">{% for l in r.subtitle_languages %}<span class="badge">{{ l.flag }} {{ l.name }}</span>{% else %}<span class="muted">—</span>{% endfor %}</div></td><td>{{ r.video_codec }}</td><td>{{ r.size }}</td></tr>{% endfor %}</tbody></table>
|
||||||
@@ -86,7 +86,7 @@ if ('serviceWorker' in navigator) {
|
|||||||
const dashboardView=document.querySelector('#dashboard');if(dashboardView){dashboardView.classList.add('dashboard-view');const stats={{ dashboard_stats|tojson }};const insights=document.createElement('div');insights.className='dashboard-insights';insights.innerHTML=`<div class="insight-card"><span class="insight-icon">▣</span><div><strong>${stats.movie_files} von ${stats.movie_total} Filmen vorhanden</strong><small>${stats.movie_missing} fehlen · ${stats.movie_downloading} werden geladen</small></div></div><div class="insight-card"><span class="insight-icon">▤</span><div><strong>${stats.episode_files} von ${stats.episode_total} Episoden vorhanden</strong><small>${stats.episode_missing} fehlen in ${stats.series_total} Serien</small></div></div><div class="insight-card"><span class="insight-icon">◌</span><div><strong>Medienbestand geprüft</strong><small>Audio, Untertitel und Qualität werden überwacht</small></div></div><div class="insight-card"><span class="insight-icon">✓</span><div><strong>Live-Überwachung aktiv</strong><small>Systemwerte und Downloads aktualisieren sich automatisch</small></div></div>`;const columns=dashboardView.querySelector('.dashboard-columns');if(columns)dashboardView.insertBefore(insights,columns)}
|
const dashboardView=document.querySelector('#dashboard');if(dashboardView){dashboardView.classList.add('dashboard-view');const stats={{ dashboard_stats|tojson }};const insights=document.createElement('div');insights.className='dashboard-insights';insights.innerHTML=`<div class="insight-card"><span class="insight-icon">▣</span><div><strong>${stats.movie_files} von ${stats.movie_total} Filmen vorhanden</strong><small>${stats.movie_missing} fehlen · ${stats.movie_downloading} werden geladen</small></div></div><div class="insight-card"><span class="insight-icon">▤</span><div><strong>${stats.episode_files} von ${stats.episode_total} Episoden vorhanden</strong><small>${stats.episode_missing} fehlen in ${stats.series_total} Serien</small></div></div><div class="insight-card"><span class="insight-icon">◌</span><div><strong>Medienbestand geprüft</strong><small>Audio, Untertitel und Qualität werden überwacht</small></div></div><div class="insight-card"><span class="insight-icon">✓</span><div><strong>Live-Überwachung aktiv</strong><small>Systemwerte und Downloads aktualisieren sich automatisch</small></div></div>`;const columns=dashboardView.querySelector('.dashboard-columns');if(columns)dashboardView.insertBefore(insights,columns)}
|
||||||
const servicePanel=document.querySelector('.service-panel');let serviceList=null;const serviceSummary=document.createElement('span');serviceSummary.className='service-summary';if(servicePanel){servicePanel.querySelectorAll('.service-row').forEach(row=>row.remove());serviceList=document.createElement('div');serviceList.className='service-list';servicePanel.appendChild(serviceList);const heading=servicePanel.querySelector('.panel-heading');if(heading)heading.appendChild(serviceSummary)}function renderServices(data){if(!serviceList)return;const values=data.services||[];serviceSummary.textContent=`${data.online||0} / ${data.total||values.length} online`;serviceList.innerHTML=values.map(service=>`<div class="service-row service-${service.status}"><span class="service-logo service-icon-${service.key}">${service.key==='dashboard'?'⌂':service.name.slice(0,1)}</span><div><strong>${html(service.name)}</strong><small>${html(service.detail||'Keine Antwort')}</small></div><span class="service-status ${service.status}">${html(service.label)}</span></div>`).join('')}async function fetchServices(){try{renderServices(await (await fetch('/api/services')).json())}catch(error){serviceSummary.textContent='Status nicht verfügbar'}}if(servicePanel){fetchServices();setInterval(fetchServices,30000)}
|
const servicePanel=document.querySelector('.service-panel');let serviceList=null;const serviceSummary=document.createElement('span');serviceSummary.className='service-summary';if(servicePanel){servicePanel.querySelectorAll('.service-row').forEach(row=>row.remove());serviceList=document.createElement('div');serviceList.className='service-list';servicePanel.appendChild(serviceList);const heading=servicePanel.querySelector('.panel-heading');if(heading)heading.appendChild(serviceSummary)}function renderServices(data){if(!serviceList)return;const values=data.services||[];serviceSummary.textContent=`${data.online||0} / ${data.total||values.length} online`;serviceList.innerHTML=values.map(service=>`<div class="service-row service-${service.status}"><span class="service-logo service-icon-${service.key}">${service.key==='dashboard'?'⌂':service.name.slice(0,1)}</span><div><strong>${html(service.name)}</strong><small>${html(service.detail||'Keine Antwort')}</small></div><span class="service-status ${service.status}">${html(service.label)}</span></div>`).join('')}async function fetchServices(){try{renderServices(await (await fetch('/api/services')).json())}catch(error){serviceSummary.textContent='Status nicht verfügbar'}}if(servicePanel){fetchServices();setInterval(fetchServices,30000)}
|
||||||
const navigation=document.querySelector('.nav');if(navigation&&!navigation.querySelector('[data-view="datastore"]')){const datastoreButton=document.createElement('button');datastoreButton.dataset.view='datastore';datastoreButton.innerHTML='<img class="nav-icon" src="https://api.iconify.design/mdi:harddisk.svg?color=%2360a9e6" alt="" aria-hidden="true"><span data-de="Datastore" data-en="Datastore">Datastore</span>';navigation.appendChild(datastoreButton)}const rows=[...document.querySelectorAll('#movies tbody tr')], search=document.querySelector('#search'), audio=document.querySelector('#audioFilter'), subs=document.querySelector('#subFilter'), state=document.querySelector('#stateFilter'), count=document.querySelector('#visibleCount');
|
const navigation=document.querySelector('.nav');if(navigation&&!navigation.querySelector('[data-view="datastore"]')){const datastoreButton=document.createElement('button');datastoreButton.dataset.view='datastore';datastoreButton.innerHTML='<img class="nav-icon" src="https://api.iconify.design/mdi:harddisk.svg?color=%2360a9e6" alt="" aria-hidden="true"><span data-de="Datastore" data-en="Datastore">Datastore</span>';navigation.appendChild(datastoreButton)}const rows=[...document.querySelectorAll('#movies tbody tr')], search=document.querySelector('#search'), audio=document.querySelector('#audioFilter'), subs=document.querySelector('#subFilter'), state=document.querySelector('#stateFilter'), count=document.querySelector('#visibleCount');
|
||||||
function apply(){const q=search.value.trim().toLowerCase();let n=0;rows.forEach(r=>{const ok=(!q||r.dataset.title.includes(q))&&(!audio.value||r.dataset.audio.includes(audio.value))&&(!subs.value||r.dataset.subs.includes(subs.value))&&(!state.value||r.dataset.state===state.value);r.classList.toggle('hidden',!ok);if(ok)n++});count.textContent=`${n} Filme`};[search,audio,subs,state].forEach(x=>x.addEventListener(x===search?'input':'change',apply));apply();const seriesRows=[...document.querySelectorAll('#seriesList details.series')],seriesSearch=document.querySelector('#seriesSearch'),seriesAudio=document.querySelector('#seriesAudioFilter'),seriesSubs=document.querySelector('#seriesSubFilter'),seriesState=document.querySelector('#seriesStateFilter'),seriesCount=document.querySelector('#seriesCount');function applySeries(){if(!seriesSearch||!seriesAudio||!seriesSubs||!seriesState)return;const q=seriesSearch.value.trim().toLowerCase();let visible=0;seriesRows.forEach(detail=>{const summary=detail.querySelector('summary')?.textContent.toLowerCase()||'';let matches=0;detail.querySelectorAll('tbody tr:not(.season-divider)').forEach(row=>{const ok=(!q||summary.includes(q)||row.dataset.title.includes(q))&&(!seriesAudio.value||row.dataset.audio.includes(seriesAudio.value))&&(!seriesSubs.value||row.dataset.subs.includes(seriesSubs.value))&&(!seriesState.value||row.dataset.state===seriesState.value);row.classList.toggle('hidden',!ok);if(ok)matches++});detail.classList.toggle('hidden',matches===0);if(matches)visible++});document.querySelectorAll('#seriesList .series-category').forEach(category=>{const hasVisible=[...category.querySelectorAll('details.series')].some(detail=>!detail.classList.contains('hidden'));category.classList.toggle('hidden',!hasVisible)});if(seriesCount)seriesCount.textContent=`${visible} Serien`};[seriesSearch,seriesAudio,seriesSubs,seriesState].forEach(x=>x?.addEventListener(x===seriesSearch?'input':'change',applySeries));applySeries();
|
function apply(){const q=search.value.trim().toLowerCase();let n=0;rows.forEach(r=>{const ok=(!q||r.dataset.title.includes(q))&&(!audio.value||r.dataset.audio.includes(audio.value))&&(!subs.value||r.dataset.subs.includes(subs.value))&&(!state.value||r.dataset.state===state.value);r.classList.toggle('hidden',!ok);if(ok)n++});count.textContent=`${n} Filme`};[search,audio,subs,state].forEach(x=>x.addEventListener(x===search?'input':'change',apply));apply();const seriesRows=[...document.querySelectorAll('#seriesList details.series')],seriesSearch=document.querySelector('#seriesSearch'),seriesAudio=document.querySelector('#seriesAudioFilter'),seriesSubs=document.querySelector('#seriesSubFilter'),seriesState=document.querySelector('#seriesStateFilter'),seriesCount=document.querySelector('#seriesCount');function applySeries(){if(!seriesSearch||!seriesAudio||!seriesSubs||!seriesState)return;const q=seriesSearch.value.trim().toLowerCase();let visible=0;seriesRows.forEach(detail=>{const summary=detail.dataset.title||detail.querySelector('summary')?.textContent.toLowerCase()||'';let matches=0;detail.querySelectorAll('tbody tr:not(.season-divider)').forEach(row=>{const ok=(!q||summary.includes(q)||row.dataset.title.includes(q))&&(!seriesAudio.value||row.dataset.audio.includes(seriesAudio.value))&&(!seriesSubs.value||row.dataset.subs.includes(seriesSubs.value))&&(!seriesState.value||row.dataset.state===seriesState.value);row.classList.toggle('hidden',!ok);row.style.display=ok?'':'none';if(ok)matches++});detail.classList.toggle('hidden',matches===0);detail.style.display=matches?'':'none';if(matches)visible++});document.querySelectorAll('#seriesList .series-category').forEach(category=>{const hasVisible=[...category.querySelectorAll('details.series')].some(detail=>!detail.classList.contains('hidden'));category.classList.toggle('hidden',!hasVisible)});if(seriesCount)seriesCount.textContent=`${visible} Serien`};[seriesSearch,seriesAudio,seriesSubs,seriesState].forEach(x=>x?.addEventListener(x===seriesSearch?'input':'change',applySeries));applySeries();
|
||||||
document.querySelectorAll('.nav button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.nav button').forEach(x=>x.classList.remove('active'));document.querySelectorAll('.view').forEach(x=>x.classList.remove('active'));b.classList.add('active');document.querySelector('#'+b.dataset.view).classList.add('active');const rescan=document.querySelector('#rescan'),sourceSelect=document.querySelector('#scanSource'),canRescan=['radarr','sonarr','missing'].includes(b.dataset.view);if(canRescan)sourceSelect.value=b.dataset.view;rescan.disabled=false;rescan.title='Bibliothek neu scannen';sessionStorage.setItem('dashboard-active-view',b.dataset.view)});const rememberedView=sessionStorage.getItem('dashboard-active-view');if(rememberedView)document.querySelector(`.nav button[data-view="${rememberedView}"]`)?.click();
|
document.querySelectorAll('.nav button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.nav button').forEach(x=>x.classList.remove('active'));document.querySelectorAll('.view').forEach(x=>x.classList.remove('active'));b.classList.add('active');document.querySelector('#'+b.dataset.view).classList.add('active');const rescan=document.querySelector('#rescan'),sourceSelect=document.querySelector('#scanSource'),canRescan=['radarr','sonarr','missing'].includes(b.dataset.view);if(canRescan)sourceSelect.value=b.dataset.view;rescan.disabled=false;rescan.title='Bibliothek neu scannen';sessionStorage.setItem('dashboard-active-view',b.dataset.view)});const rememberedView=sessionStorage.getItem('dashboard-active-view');if(rememberedView)document.querySelector(`.nav button[data-view="${rememberedView}"]`)?.click();
|
||||||
function statusText(s){if(s.state==='running'){const pct=s.total?Math.round(s.done/s.total*100):0;return `Scan läuft · ${s.done}/${s.total||'?'} Dateien · ${pct}%`}if(s.state==='done')return 'Scan abgeschlossen ✓';if(s.state==='error')return 'Scanfehler: '+s.error;return 'Scan wird vorbereitet …'}
|
function statusText(s){if(s.state==='running'){const pct=s.total?Math.round(s.done/s.total*100):0;return `Scan läuft · ${s.done}/${s.total||'?'} Dateien · ${pct}%`}if(s.state==='done')return 'Scan abgeschlossen ✓';if(s.state==='error')return 'Scanfehler: '+s.error;return 'Scan wird vorbereitet …'}
|
||||||
function updateScanStatus(selector,s){const el=document.querySelector(selector);if(!el)return;const pct=s.total?Math.min(100,Math.round(s.done/s.total*100)):0;el.textContent=statusText(s);el.style.setProperty('--scan-progress',`${pct}%`);el.classList.toggle('scan-running',s.state==='running')}
|
function updateScanStatus(selector,s){const el=document.querySelector(selector);if(!el)return;const pct=s.total?Math.min(100,Math.round(s.done/s.total*100)):0;el.textContent=statusText(s);el.style.setProperty('--scan-progress',`${pct}%`);el.classList.toggle('scan-running',s.state==='running')}
|
||||||
|
|||||||
Reference in New Issue
Block a user