Files
iptvnator/libs/ui/components/src/lib/vod-sources/vod-source-row.component.ts
T
4grayandClaude Fable 5 5b93392421 feat(portals): rework the VOD sources popup and action row
The Sources popup now anchors above its button and always fits on screen:
the overlay caps it to the space beside the chip, only the source list
scrolls so the header, search, filter chips and footer stay visible, and
it flips below the button when the space above is too small. Filter chips
(All / Available / HD+ / language) compose with the playlist search, and
"Available" runs check-all itself when nothing has been checked yet.

Expanded copy rows no longer repeat the playlist domain: each copy shows
its parsed language chip, the provider's raw stream title, and only the
tags that differ from the parent copy.

Availability checks run at most four at a time and settled verdicts are
remembered per movie and source for ten minutes, so reopening a movie no
longer re-contacts every foreign portal.

Favorites and Download become icon-only buttons with real state: a filled
heart when favorited, and a download icon that turns into a progress ring
and then a checkmark that reveals the finished file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 19:03:32 +02:00

87 lines
3.0 KiB
TypeScript

import {
ChangeDetectionStrategy,
Component,
computed,
input,
output,
} from '@angular/core';
import { MatIcon } from '@angular/material/icon';
import { MatTooltip } from '@angular/material/tooltip';
import { TranslatePipe } from '@ngx-translate/core';
import { VodSourceDescriptor } from '@iptvnator/shared/interfaces';
import {
buildVodSourceTags,
sourceMonogram,
sourceNeedsCheck,
} from './vod-source-tags';
import { VodSourceTagListComponent } from './vod-source-tag-list.component';
import { playlistDisplayLabel } from '@iptvnator/shared/interfaces';
/**
* One alternative source for the movie being viewed.
*
* Shared by the sources popover (details + player) and the playback error
* screen. It renders only what the descriptor can prove: unknown metadata
* produces no tag, just the actionable check chip.
*/
@Component({
selector: 'app-vod-source-row',
templateUrl: './vod-source-row.component.html',
styleUrls: ['./vod-source-row.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MatIcon, MatTooltip, TranslatePipe, VodSourceTagListComponent],
})
export class VodSourceRowComponent {
readonly source = input.required<VodSourceDescriptor>();
/** The error screen lists alternatives without offering the pin action. */
readonly showPin = input(true);
/**
* Whether a stream is on screen right now. The active row is the one a
* switch or Play would use, which is true from the moment the page opens —
* so without this the badge claims "Playing" before anything has started
* and again after the player is closed.
*/
readonly playbackLive = input(false);
readonly playRequested = output<string>();
readonly pinRequested = output<string>();
readonly checkRequested = output<string>();
/**
* Short, credential-safe label. Playlist names are routinely the pasted
* URL, which carries `username=`/`password=` — never render that raw.
*/
readonly displayLabel = computed(() =>
playlistDisplayLabel(
this.source().playlistName,
this.source().playlistId
)
);
// From the display label, not the raw name: every URL-named playlist would
// otherwise monogram to "HT" (from "http://") and be indistinguishable.
readonly monogram = computed(() => sourceMonogram(this.displayLabel()));
readonly tags = computed(() => buildVodSourceTags(this.source()));
readonly showCheckChip = computed(() => sourceNeedsCheck(this.source()));
readonly pinLabelKey = computed(() =>
this.source().isPinned
? 'PORTALS.MULTI_SOURCE.UNPIN_SOURCE'
: 'PORTALS.MULTI_SOURCE.PIN_SOURCE'
);
onPlay(event: Event): void {
event.stopPropagation();
this.playRequested.emit(this.source().id);
}
onPin(event: Event): void {
event.stopPropagation();
this.pinRequested.emit(this.source().id);
}
onCheck(event: Event): void {
event.stopPropagation();
this.checkRequested.emit(this.source().id);
}
}