mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 04:26:16 -08:00
* docs(downloads): specify manager MVP redesign * docs(downloads): plan manager MVP implementation * docs(downloads): tighten manager validation plan * fix(downloads): keep renderer download state global * fix(downloads): make active count accessible * feat(downloads): derive queue and library view model * test(downloads): close view model coverage gaps * fix(downloads): stabilize malformed view model data * refactor(downloads): isolate library navigation * fix(downloads): report library navigation failures * feat(downloads): add ready-to-watch library * feat(downloads): add active download queue * feat(downloads): finish manager MVP * docs(downloads): clarify detail-first offline behavior * docs(downloads): plan detail navigation follow-up * fix(downloads): open completed movies in details * test(downloads): cover pending series navigation * fix(downloads): honor the global cover size * fix(downloads): prefer local playback in shared details * fix(downloads): preserve external launch priority * fix(downloads): prefer local playback in Xtream details * test(downloads): cover offline detail journey * docs(downloads): document offline detail behavior * docs(downloads): format detail navigation plan * fix(downloads): open Stalker items in provider details * docs(downloads): clarify Stalker navigation fallback * fix(xtream): isolate reused detail identities * fix(xtream): ignore stale VOD positions * fix(downloads): keep offline Xtream playback available * docs(downloads): clarify provider playback availability * docs(downloads): design missing-file recovery * docs(downloads): plan missing-file recovery * feat(downloads): derive completed file availability * feat(downloads): recover missing completed files * feat(downloads): refresh missing local files * feat(downloads): separate missing files from ready media * feat(downloads): surface missing files for recovery * refactor(downloads): simplify ready cards * test(downloads): cover missing-file and series journeys * feat(downloads): finish missing-file recovery * docs(downloads): design offline detail views * docs(downloads): plan offline detail views * feat(downloads): persist offline metadata snapshots * fix(downloads): complete metadata snapshot bridge contract * feat(downloads): manage offline metadata snapshots * fix(downloads): harden metadata snapshot updates * fix(downloads): restrict snapshot artwork * fix(downloads): guard restart artwork URL * fix(downloads): refine artwork URL checks * feat(downloads): expose offline metadata updates * fix(downloads): keep metadata service change focused * fix(downloads): preserve metadata error conventions * feat(downloads): derive offline detail content * fix(downloads): preserve unknown episode coordinates * feat(downloads): add focused offline detail routes * fix(downloads): ignore fragments in shell route state * fix(downloads): normalize fragments before queries * feat(downloads): open ready cards in offline details * fix(downloads): use native disabled card styles * feat(downloads): enrich offline detail metadata * fix(downloads): harden offline metadata resolution * fix(downloads): preserve stalker provider titles * fix(downloads): distinguish stalker metadata seeds * fix(downloads): stabilize offline metadata refresh * fix(downloads): throttle sparse metadata refreshes * fix(downloads): type metadata language settings * feat(downloads): render offline movie and series details * fix(downloads): harden offline detail interactions * fix(downloads): close offline detail edge cases * feat(downloads): hand off to provider-only details * fix(downloads): preserve stalker provider handoff * feat(downloads): capture metadata at download time * fix(downloads): preserve snapshot source semantics * fix(downloads): preserve episode snapshot identity * docs(downloads): document offline details flow * docs(downloads): clarify stalker provider fallback * test(downloads): cover offline detail journeys * test(downloads): stabilize offline detail selectors * style(downloads): format changed files * docs(downloads): clean design spec formatting * fix(downloads): preserve offline library ownership * test(downloads): fix Windows workspace navigation * test(database): preserve Electron tsconfig resolution * perf(downloads): avoid blocking file availability probes
200 lines
6.9 KiB
HTML
200 lines
6.9 KiB
HTML
<!-- Hero Section with Backdrop -->
|
|
<section class="hero">
|
|
<!-- Back Button -->
|
|
<button
|
|
class="hero__back-button"
|
|
type="button"
|
|
[attr.aria-label]="backLabel() || ('BACK' | translate)"
|
|
(click)="onBack()"
|
|
>
|
|
<mat-icon aria-hidden="true">arrow_back</mat-icon>
|
|
</button>
|
|
@if (errorMessage()) {
|
|
<div class="hero__content hero__error">
|
|
<mat-icon class="error-icon">cloud_off</mat-icon>
|
|
<h2 class="error-title">{{ errorMessage() }}</h2>
|
|
<ng-content select="[hero-error-actions]"></ng-content>
|
|
</div>
|
|
} @else if (isLoading()) {
|
|
<div class="hero__content">
|
|
<!-- Skeleton Poster -->
|
|
<div class="poster">
|
|
<ngx-skeleton-loader
|
|
count="1"
|
|
[theme]="{
|
|
height: '100%',
|
|
width: '100%',
|
|
'border-radius': '8px',
|
|
'aspect-ratio': '2/3',
|
|
display: 'block',
|
|
}"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Skeleton Details -->
|
|
<div class="details">
|
|
<!-- Title -->
|
|
<ngx-skeleton-loader
|
|
count="1"
|
|
[theme]="{
|
|
height: '40px',
|
|
width: '60%',
|
|
'margin-bottom': '16px',
|
|
display: 'block',
|
|
}"
|
|
/>
|
|
|
|
<!-- Tags -->
|
|
<div style="display: flex; gap: 8px; margin-bottom: 16px">
|
|
<ngx-skeleton-loader
|
|
count="3"
|
|
[theme]="{
|
|
height: '24px',
|
|
width: '60px',
|
|
display: 'block',
|
|
}"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<ngx-skeleton-loader
|
|
count="3"
|
|
[theme]="{
|
|
height: '16px',
|
|
'margin-bottom': '8px',
|
|
width: '100%',
|
|
display: 'block',
|
|
}"
|
|
/>
|
|
|
|
<!-- Meta -->
|
|
<div style="margin-top: 20px; display: flex; gap: 32px">
|
|
<ngx-skeleton-loader
|
|
count="2"
|
|
[theme]="{
|
|
height: '40px',
|
|
width: '100px',
|
|
display: 'block',
|
|
}"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Buttons -->
|
|
<div style="margin-top: 24px; display: flex; gap: 12px">
|
|
<ngx-skeleton-loader
|
|
count="2"
|
|
[theme]="{
|
|
height: '48px',
|
|
width: '140px',
|
|
display: 'block',
|
|
}"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
} @else {
|
|
<!-- Backdrop Image -->
|
|
<div
|
|
class="hero__backdrop"
|
|
[class.hero__backdrop--blurred]="usesPosterBackdrop()"
|
|
[style.backgroundImage]="
|
|
backdropImageUrl() ? null : fallbackBackdropBackground()
|
|
"
|
|
>
|
|
@if (backdropImageUrl(); as backdropImage) {
|
|
<img
|
|
class="hero__backdrop-image"
|
|
[src]="backdropImage"
|
|
(error)="onBackdropError()"
|
|
alt=""
|
|
aria-hidden="true"
|
|
/>
|
|
}
|
|
</div>
|
|
|
|
<!-- Gradient Vignette Overlay -->
|
|
<div class="hero__vignette"></div>
|
|
|
|
<!-- Hero Content -->
|
|
<div class="hero__content">
|
|
<!-- Poster Card -->
|
|
<div class="poster">
|
|
@if (posterUrl() && !posterError()) {
|
|
<img
|
|
class="poster__image"
|
|
[src]="posterUrl()"
|
|
(error)="onPosterError()"
|
|
alt="Poster"
|
|
/>
|
|
} @else {
|
|
<div
|
|
class="poster__placeholder"
|
|
[style.backgroundImage]="fallbackPosterBackground()"
|
|
>
|
|
<mat-icon class="poster__placeholder-icon"
|
|
>movie</mat-icon
|
|
>
|
|
<span class="poster__placeholder-text">{{
|
|
formattedTitle()
|
|
}}</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="details">
|
|
<h1 class="details__title">{{ formattedTitle() }}</h1>
|
|
|
|
<!-- Tags Slot -->
|
|
<div class="details__tags">
|
|
<ng-content select="[hero-tags]"></ng-content>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
@if (description()) {
|
|
<p
|
|
#descriptionEl
|
|
class="details__description"
|
|
[class.details__description--expanded]="
|
|
isDescriptionExpanded()
|
|
"
|
|
>
|
|
{{ description() }}
|
|
</p>
|
|
@if (hasDescriptionOverflow() || isDescriptionExpanded()) {
|
|
<button
|
|
type="button"
|
|
class="details__description-toggle"
|
|
(click)="toggleDescription()"
|
|
[attr.aria-expanded]="isDescriptionExpanded()"
|
|
>
|
|
<span>{{
|
|
(isDescriptionExpanded()
|
|
? 'SHOW_LESS'
|
|
: 'SHOW_MORE'
|
|
) | translate
|
|
}}</span>
|
|
<mat-icon aria-hidden="true">
|
|
{{
|
|
isDescriptionExpanded()
|
|
? 'expand_less'
|
|
: 'expand_more'
|
|
}}
|
|
</mat-icon>
|
|
</button>
|
|
}
|
|
}
|
|
|
|
<!-- Metadata Grid Slot -->
|
|
<div class="details__meta">
|
|
<ng-content select="[hero-meta]"></ng-content>
|
|
</div>
|
|
|
|
<!-- Action Buttons Slot -->
|
|
<div class="action-buttons">
|
|
<ng-content select="[hero-actions]"></ng-content>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</section>
|