fix(ui): keep the auto-refresh badge on playlist rows with one provider icon

Showing one provider icon per row moved the auto-refresh badge into the M3U
branches only, so a restored Stalker playlist with a URL and auto-refresh
lost it although the URL is still re-fetched. The row now renders one icon
container: the provider icon from the shared precedence, then the badge for
any row with a URL or a local M3U, exactly the rows that showed it before.
The Xtream portal-status dot, used without source health, keeps that corner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-01 18:59:23 +02:00
1 parent fb293ea23d
commit 0268aa8667
3 files changed
+81 -64

No files matched your search

@@ -19,16 +19,18 @@
}
<!-- One provider icon per row, by the shared precedence: a restored
Stalker or Xtream playlist can also carry a URL. -->
@switch (sourceIconKey) {
@case ('stalker') {
<mat-icon
class="upload-type-icon"
[matTooltip]="'HOME.PLAYLISTS.STALKER_PORTAL' | translate"
>{{ sourceIcons.stalker }}</mat-icon
>
}
@case ('xtream') {
<div class="icon-container">
<div class="icon-container">
@switch (sourceIconKey) {
@case ('stalker') {
<mat-icon
class="upload-type-icon"
[matTooltip]="
'HOME.PLAYLISTS.STALKER_PORTAL' | translate
"
>{{ sourceIcons.stalker }}</mat-icon
>
}
@case ('xtream') {
<mat-icon
class="upload-type-icon"
[matTooltip]="
@@ -36,50 +38,45 @@
"
>{{ sourceIcons.xtream }}</mat-icon
>
@if (!runtime.supportsSourceHealth) {
<span
class="status-dot"
[class]="getStatusClass()"
[matTooltip]="'Portal status: ' + portalStatus"
[attr.aria-label]="'Portal status: ' + portalStatus"
></span>
}
</div>
}
@case ('m3u-url') {
<mat-icon
class="upload-type-icon"
[matTooltip]="
('HOME.PLAYLISTS.ADDED_VIA_URL' | translate) +
' ' +
item.url
"
>{{ sourceIcons['m3u-url'] }}</mat-icon
>
}
@default {
<mat-icon
class="upload-type-icon"
[matTooltip]="
'HOME.PLAYLISTS.ADDED_VIA_FILE' | translate
"
>{{ sourceIcons['m3u-local'] }}</mat-icon
>
}
}
@default {
<div class="icon-container">
@if (sourceIconKey === 'm3u-url') {
<mat-icon
class="upload-type-icon"
[matTooltip]="
('HOME.PLAYLISTS.ADDED_VIA_URL' | translate) +
' ' +
item.url
"
>{{ sourceIcons['m3u-url'] }}</mat-icon
>
} @else {
<mat-icon
class="upload-type-icon"
[matTooltip]="
'HOME.PLAYLISTS.ADDED_VIA_FILE' | translate
"
>{{ sourceIcons['m3u-local'] }}</mat-icon
>
}
@if (item.autoRefresh) {
<mat-icon
class="auto-refresh-indicator"
[matTooltip]="
'HOME.PLAYLISTS.AUTO_REFRESH_ENABLED'
| translate
"
>autorenew</mat-icon
>
}
</div>
@if (showsPortalStatusDot) {
<span
class="status-dot"
[class]="getStatusClass()"
[matTooltip]="'Portal status: ' + portalStatus"
[attr.aria-label]="'Portal status: ' + portalStatus"
></span>
} @else if (showsAutoRefresh) {
<mat-icon
class="auto-refresh-indicator"
[matTooltip]="
'HOME.PLAYLISTS.AUTO_REFRESH_ENABLED' | translate
"
>autorenew</mat-icon
>
}
}
</div>
</div>
<div class="playlist-content">
<div class="playlist-title">
@@ -226,16 +226,15 @@ describe('PlaylistItemComponent', () => {
[
{ macAddress: '00:1A:79:00:00:01', url: 'http://portal.test' },
'cast',
true,
],
[
{ serverUrl: 'http://xtream.test', url: 'http://xtream.test' },
'cloud',
],
[{ url: 'http://list.test/playlist.m3u' }, 'link'],
[{}, 'description'],
[{ macAddress: '00:1A:79:00:00:01' }, 'cast', false],
[{ serverUrl: 'http://xtream.test' }, 'cloud', false],
[{ url: 'http://list.test/playlist.m3u' }, 'link', true],
[{}, 'description', true],
])(
'shows one provider icon for %o, with the shared precedence',
(source, icon) => {
'shows one provider icon for %o (%s) and keeps the auto-refresh badge: %s',
(source, icon, autoRefreshBadge) => {
fixture.destroy();
fixture = TestBed.createComponent(PlaylistItemComponent);
fixture.componentInstance.item = {
@@ -243,18 +242,20 @@ describe('PlaylistItemComponent', () => {
_id: 'source',
count: 10,
importDate: Date.now().toString(),
autoRefresh: false,
autoRefresh: true,
...source,
};
fixture.detectChanges();
const row = fixture.nativeElement as HTMLElement;
const icons = Array.from(
(fixture.nativeElement as HTMLElement).querySelectorAll(
'.upload-type-icon'
),
row.querySelectorAll('.upload-type-icon'),
(element) => element.textContent?.trim()
);
expect(icons).toEqual([icon]);
expect(row.querySelector('.auto-refresh-indicator') !== null).toBe(
autoRefreshBadge
);
}
);
});
@@ -78,6 +78,25 @@ export class PlaylistItemComponent implements OnInit {
get sourceIconKey(): PlaylistSourceIconKey {
return resolvePlaylistSourceIconKey(this.item);
}
/** Without source health, Xtream rows badge the portal status instead. */
get showsPortalStatusDot(): boolean {
return (
this.sourceIconKey === 'xtream' &&
!this.runtime.supportsSourceHealth
);
}
/**
* Auto-refresh re-fetches a URL or a local file, so any row with a URL
* keeps the badge, whichever provider icon it shows.
*/
get showsAutoRefresh(): boolean {
return (
!!this.item.autoRefresh &&
(!!this.item.url || this.sourceIconKey === 'm3u-local')
);
}
private readonly translate = inject(TranslateService);
private readonly languageTick = toSignal(
this.translate.onLangChange.pipe(startWith(null)),