mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
fix(portals): hydrate the offline catalog and hide results while matching
- `toCachedContentScope` returned null for the actor and discover routes, so an expired, inactive or offline portal skipped hydration entirely and both pages answered "not in your library" from an empty catalog even though a full imported catalog sat in SQLite. Both map to the aggregate `search` scope now — neither reads a single content type. - The results grid stayed rendered under the matching spinner. Until the matches land every card reads as unavailable, so a click during the worker request opened the portal search for a title the next tick would have resolved in another playlist. The grid is hidden while matching. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a7a7ba8b0
commit
11744dd389
2 files changed
+78
-65
No files matched your search
@@ -92,7 +92,7 @@ function toContentInitBlockReason(
|
||||
}
|
||||
|
||||
function toCachedContentScope(
|
||||
section: PortalRailSection | null
|
||||
section: string | null
|
||||
): XtreamCachedContentScope | null {
|
||||
switch (section) {
|
||||
case 'live':
|
||||
@@ -101,6 +101,13 @@ function toCachedContentScope(
|
||||
case 'search':
|
||||
case 'recently-added':
|
||||
return section;
|
||||
// Neither reads one content type: both match TMDB titles against
|
||||
// the whole catalog, so they take the aggregate scope search uses.
|
||||
// Without it an expired or offline portal skips hydration and the
|
||||
// page answers "not in your library" from an empty catalog.
|
||||
case 'actor':
|
||||
case 'discover':
|
||||
return 'search';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -59,69 +59,75 @@
|
||||
<mat-icon>search_off</mat-icon>
|
||||
<p>{{ emptyMessageKey() | translate }}</p>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Hidden while matching on purpose: until the matches land every
|
||||
card still reads as unavailable, and clicking one would open
|
||||
the portal search for a title the next tick will resolve. -->
|
||||
<div class="title-results__grid">
|
||||
@for (
|
||||
item of visibleItems();
|
||||
track item.mediaType + '-' + item.tmdbId
|
||||
) {
|
||||
<button
|
||||
type="button"
|
||||
class="title-results__card"
|
||||
[class.title-results__card--unavailable]="!item.available"
|
||||
[title]="
|
||||
item.character
|
||||
? item.title + ' — ' + item.character
|
||||
: item.title
|
||||
"
|
||||
(click)="itemClicked.emit(item)"
|
||||
>
|
||||
@if (item.posterUrl) {
|
||||
<img
|
||||
class="title-results__poster"
|
||||
[src]="item.posterUrl"
|
||||
[alt]="item.title"
|
||||
loading="lazy"
|
||||
/>
|
||||
} @else {
|
||||
<span
|
||||
class="title-results__poster title-results__poster--fallback"
|
||||
>
|
||||
{{ item.title.charAt(0) }}
|
||||
</span>
|
||||
}
|
||||
@if (item.available && item.availableIn) {
|
||||
<span class="title-results__badge">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
{{ item.availableIn }}
|
||||
</span>
|
||||
} @else if (
|
||||
item.available && filterMode() !== 'available'
|
||||
) {
|
||||
<span class="title-results__badge">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
{{ 'XTREAM.ACTOR_IN_LIBRARY' | translate }}
|
||||
</span>
|
||||
} @else if (!item.available) {
|
||||
<span
|
||||
class="title-results__badge title-results__badge--search"
|
||||
>
|
||||
<mat-icon>search</mat-icon>
|
||||
{{ 'XTREAM.ACTOR_SEARCH_IN_PORTAL' | translate }}
|
||||
</span>
|
||||
}
|
||||
<span class="title-results__card-title">
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<span class="title-results__card-sub">
|
||||
@if (item.year) {
|
||||
{{ item.year }}
|
||||
}
|
||||
@if (item.character) {
|
||||
· {{ item.character }}
|
||||
} @else if (item.crewJob) {
|
||||
· {{ crewJobKey(item.crewJob) | translate }}
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="title-results__grid">
|
||||
@for (
|
||||
item of visibleItems();
|
||||
track item.mediaType + '-' + item.tmdbId
|
||||
) {
|
||||
<button
|
||||
type="button"
|
||||
class="title-results__card"
|
||||
[class.title-results__card--unavailable]="!item.available"
|
||||
[title]="
|
||||
item.character
|
||||
? item.title + ' — ' + item.character
|
||||
: item.title
|
||||
"
|
||||
(click)="itemClicked.emit(item)"
|
||||
>
|
||||
@if (item.posterUrl) {
|
||||
<img
|
||||
class="title-results__poster"
|
||||
[src]="item.posterUrl"
|
||||
[alt]="item.title"
|
||||
loading="lazy"
|
||||
/>
|
||||
} @else {
|
||||
<span
|
||||
class="title-results__poster title-results__poster--fallback"
|
||||
>
|
||||
{{ item.title.charAt(0) }}
|
||||
</span>
|
||||
}
|
||||
@if (item.available && item.availableIn) {
|
||||
<span class="title-results__badge">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
{{ item.availableIn }}
|
||||
</span>
|
||||
} @else if (item.available && filterMode() !== 'available') {
|
||||
<span class="title-results__badge">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
{{ 'XTREAM.ACTOR_IN_LIBRARY' | translate }}
|
||||
</span>
|
||||
} @else if (!item.available) {
|
||||
<span
|
||||
class="title-results__badge title-results__badge--search"
|
||||
>
|
||||
<mat-icon>search</mat-icon>
|
||||
{{ 'XTREAM.ACTOR_SEARCH_IN_PORTAL' | translate }}
|
||||
</span>
|
||||
}
|
||||
<span class="title-results__card-title">
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<span class="title-results__card-sub">
|
||||
@if (item.year) {
|
||||
{{ item.year }}
|
||||
}
|
||||
@if (item.character) {
|
||||
· {{ item.character }}
|
||||
} @else if (item.crewJob) {
|
||||
· {{ crewJobKey(item.crewJob) | translate }}
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
Reference in new issue
Block a user