mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 20:16:15 -08:00
fix(dashboard): address rail review feedback
This commit is contained in:
1 parent
101d9ba162
commit
97678d4a66
3 files changed
+167
-17
No files matched your search
+1
-7
@@ -240,13 +240,7 @@
|
||||
/>
|
||||
}
|
||||
|
||||
@if (
|
||||
dashboardRails().continueWatching &&
|
||||
dashboardRails().recentlyWatchedLive &&
|
||||
continueWatchingCards().length === 0 &&
|
||||
recentLiveCards().length === 0 &&
|
||||
data.globalRecentLoading()
|
||||
) {
|
||||
@if (showRecentContentSkeleton()) {
|
||||
<section
|
||||
class="rails-page__skeleton-rail"
|
||||
aria-hidden="true"
|
||||
|
||||
+105
@@ -1,7 +1,9 @@
|
||||
import type { EpgProgram, PlaylistMeta } from '@iptvnator/shared/interfaces';
|
||||
import { DEFAULT_DASHBOARD_RAILS_SETTINGS } from '@iptvnator/shared/interfaces';
|
||||
import {
|
||||
buildDashboardCollectionViewState,
|
||||
buildDashboardLiveEpgDetails,
|
||||
buildLiveEpgCardsForEnabledRails,
|
||||
buildLiveEpgLookupKeys,
|
||||
buildPlaybackPositionReloadKey,
|
||||
buildDashboardSourceActions,
|
||||
@@ -14,6 +16,7 @@ import {
|
||||
playbackProgressPercent,
|
||||
resolveDashboardHeroArtwork,
|
||||
buildDashboardRailSeeAllState,
|
||||
shouldShowRecentContentSkeleton,
|
||||
} from './workspace-dashboard-rails.component';
|
||||
import type { DashboardRailCard } from './dashboard-rail.component';
|
||||
import { COLLECTION_VIEW_STATE_KEY } from '@iptvnator/portal/shared/util';
|
||||
@@ -354,6 +357,108 @@ describe('Live rail helpers', () => {
|
||||
expect(first).toBe(second);
|
||||
expect(first).toBe('a::movie::10|b::series::20');
|
||||
});
|
||||
|
||||
it('omits hero cards from live EPG lookup sources when the hero rail is disabled', () => {
|
||||
const hero = channelCard({ id: 'hero', epgLookupKey: 'hero' });
|
||||
const favorite = channelCard({
|
||||
id: 'favorite',
|
||||
epgLookupKey: 'favorite',
|
||||
});
|
||||
const recent = channelCard({ id: 'recent', epgLookupKey: 'recent' });
|
||||
|
||||
expect(
|
||||
buildLiveEpgCardsForEnabledRails(
|
||||
{
|
||||
...DEFAULT_DASHBOARD_RAILS_SETTINGS,
|
||||
hero: false,
|
||||
},
|
||||
hero,
|
||||
[favorite],
|
||||
[recent]
|
||||
).map((card) => card.id)
|
||||
).toEqual(['favorite', 'recent']);
|
||||
});
|
||||
|
||||
it('omits disabled live rails from live EPG lookup sources', () => {
|
||||
const hero = channelCard({ id: 'hero', epgLookupKey: 'hero' });
|
||||
const favorite = channelCard({
|
||||
id: 'favorite',
|
||||
epgLookupKey: 'favorite',
|
||||
});
|
||||
const recent = channelCard({ id: 'recent', epgLookupKey: 'recent' });
|
||||
|
||||
expect(
|
||||
buildLiveEpgCardsForEnabledRails(
|
||||
{
|
||||
...DEFAULT_DASHBOARD_RAILS_SETTINGS,
|
||||
liveFavorites: false,
|
||||
recentlyWatchedLive: false,
|
||||
},
|
||||
hero,
|
||||
[favorite],
|
||||
[recent]
|
||||
).map((card) => card.id)
|
||||
).toEqual(['hero']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recent content skeleton helper', () => {
|
||||
it('shows the loading skeleton for Continue Watching when recently watched live is disabled', () => {
|
||||
expect(
|
||||
shouldShowRecentContentSkeleton(
|
||||
{
|
||||
...DEFAULT_DASHBOARD_RAILS_SETTINGS,
|
||||
recentlyWatchedLive: false,
|
||||
},
|
||||
{
|
||||
continueWatchingCount: 0,
|
||||
recentLiveCount: 0,
|
||||
globalRecentLoading: true,
|
||||
}
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('shows the loading skeleton for recently watched live when Continue Watching is disabled', () => {
|
||||
expect(
|
||||
shouldShowRecentContentSkeleton(
|
||||
{
|
||||
...DEFAULT_DASHBOARD_RAILS_SETTINGS,
|
||||
continueWatching: false,
|
||||
},
|
||||
{
|
||||
continueWatchingCount: 0,
|
||||
recentLiveCount: 0,
|
||||
globalRecentLoading: true,
|
||||
}
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('hides the loading skeleton when no enabled recent rail is waiting for data', () => {
|
||||
expect(
|
||||
shouldShowRecentContentSkeleton(
|
||||
{
|
||||
...DEFAULT_DASHBOARD_RAILS_SETTINGS,
|
||||
continueWatching: false,
|
||||
recentlyWatchedLive: false,
|
||||
},
|
||||
{
|
||||
continueWatchingCount: 0,
|
||||
recentLiveCount: 0,
|
||||
globalRecentLoading: true,
|
||||
}
|
||||
)
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
shouldShowRecentContentSkeleton(DEFAULT_DASHBOARD_RAILS_SETTINGS, {
|
||||
continueWatchingCount: 1,
|
||||
recentLiveCount: 1,
|
||||
globalRecentLoading: true,
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('playback-position helpers', () => {
|
||||
|
||||
+61
-10
@@ -11,7 +11,8 @@ import { toObservable, toSignal } from '@angular/core/rxjs-interop';
|
||||
import { interval, of, startWith, switchMap } from 'rxjs';
|
||||
import { EpgService } from '@iptvnator/epg/data-access';
|
||||
import {
|
||||
EpgProgram,
|
||||
type DashboardRailsSettings,
|
||||
type EpgProgram,
|
||||
normalizeDashboardRailsSettings,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -323,6 +324,24 @@ export function buildLiveEpgLookupKeys(
|
||||
return keys;
|
||||
}
|
||||
|
||||
type DashboardLiveEpgRailSettings = Pick<
|
||||
DashboardRailsSettings,
|
||||
'hero' | 'liveFavorites' | 'recentlyWatchedLive'
|
||||
>;
|
||||
|
||||
export function buildLiveEpgCardsForEnabledRails(
|
||||
rails: DashboardLiveEpgRailSettings,
|
||||
heroLiveCard: DashboardRailCard | null,
|
||||
liveFavoriteCards: readonly DashboardRailCard[],
|
||||
recentLiveCards: readonly DashboardRailCard[]
|
||||
): DashboardRailCard[] {
|
||||
return [
|
||||
...(rails.hero && heroLiveCard ? [heroLiveCard] : []),
|
||||
...(rails.liveFavorites ? liveFavoriteCards : []),
|
||||
...(rails.recentlyWatchedLive ? recentLiveCards : []),
|
||||
];
|
||||
}
|
||||
|
||||
export function getLiveEpgProgramForCard(
|
||||
card: DashboardRailCard,
|
||||
epgMap: ReadonlyMap<string, EpgProgram | null>
|
||||
@@ -393,6 +412,31 @@ export function isContinueWatchingRecentItem(
|
||||
return item.type === 'movie' || item.type === 'series';
|
||||
}
|
||||
|
||||
type DashboardRecentRailSettings = Pick<
|
||||
DashboardRailsSettings,
|
||||
'continueWatching' | 'recentlyWatchedLive'
|
||||
>;
|
||||
|
||||
export interface DashboardRecentContentSkeletonInput {
|
||||
readonly continueWatchingCount: number;
|
||||
readonly globalRecentLoading: boolean;
|
||||
readonly recentLiveCount: number;
|
||||
}
|
||||
|
||||
export function shouldShowRecentContentSkeleton(
|
||||
rails: DashboardRecentRailSettings,
|
||||
input: DashboardRecentContentSkeletonInput
|
||||
): boolean {
|
||||
if (!input.globalRecentLoading) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
(rails.continueWatching && input.continueWatchingCount === 0) ||
|
||||
(rails.recentlyWatchedLive && input.recentLiveCount === 0)
|
||||
);
|
||||
}
|
||||
|
||||
// ── Playback-position helpers (used by both hero + Continue Watching cards)
|
||||
// — kept as plain functions so they're easy to unit-test without mounting the
|
||||
// component or the data service.
|
||||
@@ -592,21 +636,28 @@ export class WorkspaceDashboardRailsComponent {
|
||||
.map((item) => this.toRecentCard(item))
|
||||
);
|
||||
|
||||
readonly showRecentContentSkeleton = computed(() =>
|
||||
shouldShowRecentContentSkeleton(this.dashboardRails(), {
|
||||
continueWatchingCount: this.continueWatchingCards().length,
|
||||
globalRecentLoading: this.data.globalRecentLoading(),
|
||||
recentLiveCount: this.recentLiveCards().length,
|
||||
})
|
||||
);
|
||||
|
||||
// Best-effort EPG lookup keyed by the app-wide M3U XMLTV chain
|
||||
// (tvg-id -> tvg-name -> name), with the card title as a final fallback.
|
||||
// Xtream/Stalker live items often have no XMLTV side-channel and will
|
||||
// simply return null — the card renders without the program row.
|
||||
private readonly liveChannelLookupKeys = computed(() => {
|
||||
const heroLiveCard = this.heroLiveCard();
|
||||
return buildLiveEpgLookupKeys([
|
||||
...(heroLiveCard ? [heroLiveCard] : []),
|
||||
...(this.dashboardRails().liveFavorites
|
||||
? this.liveFavoriteCards()
|
||||
: []),
|
||||
...(this.dashboardRails().recentlyWatchedLive
|
||||
? this.recentLiveCards()
|
||||
: []),
|
||||
]);
|
||||
return buildLiveEpgLookupKeys(
|
||||
buildLiveEpgCardsForEnabledRails(
|
||||
this.dashboardRails(),
|
||||
heroLiveCard,
|
||||
this.liveFavoriteCards(),
|
||||
this.recentLiveCards()
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
private readonly playbackPositionReloadKey = computed(() =>
|
||||
|
||||
Reference in new issue
Block a user