From dd58e4a529c395118cf2d9842050af28bd694e9f Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 11 Apr 2026 10:42:10 +0200 Subject: [PATCH] style: enhance channel details dialog with improved layout and additional stats Entire-Checkpoint: db35a98d337f --- .../channel-details-dialog.component.html | 312 +++++++++---- .../channel-details-dialog.component.scss | 414 +++++++++++++++--- .../channel-details-dialog.component.spec.ts | 14 +- .../channel-details-dialog.component.ts | 211 ++++----- 4 files changed, 671 insertions(+), 280 deletions(-) diff --git a/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.html b/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.html index b91963b58..427674563 100644 --- a/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.html +++ b/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.html @@ -1,109 +1,233 @@ -

{{ 'CHANNELS.DETAILS_DIALOG.TITLE' | translate }}

+
+
+
+ + {{ channel.group?.title || 'M3U' }} +
- -

- {{ 'CHANNELS.DETAILS_DIALOG.DESCRIPTION' | translate }} -

+
+
+ @if (channel.tvg?.logo) { + + } +
+

{{ channel.name }}

+

+ {{ 'CHANNELS.DETAILS_DIALOG.DESCRIPTION' | translate }} +

+
+
-
-

- {{ 'CHANNELS.DETAILS_DIALOG.SUMMARY' | translate }} -

-
- @for (field of summaryFields; track field.labelKey) { -
-
{{ field.labelKey | translate }}
-
+ @if (channel.radio === 'true') { + - @if (field.valueKey) { - {{ field.valueKey | translate: field.translateParams }} - } @else { - {{ field.value }} + {{ 'CHANNELS.DETAILS_DIALOG.RADIO' | translate }} + + } + @if (catchupAvailable) { + + {{ 'CHANNELS.DETAILS_DIALOG.CATCHUP_AVAILABLE' | translate }} + + } + @if (catchupPlaybackSupported) { + + {{ 'CHANNELS.DETAILS_DIALOG.PLAYBACK_SUPPORTED' | translate }} + + } +
+
+
+ +
+ @for (stat of heroStats; track stat.labelKey) { +
+
+ {{ stat.icon }} +
+
+ {{ stat.labelKey | translate }} +
+
+ @if (stat.valueKey) { + {{ stat.valueKey | translate: stat.translateParams }} + } @else { + {{ stat.value }} + } +
+
+ } +
+
+ + +
+
+
+

+ {{ 'CHANNELS.DETAILS_DIALOG.SUMMARY' | translate }} +

+

{{ 'CHANNELS.DETAILS_DIALOG.STREAM_URL' | translate }}

+
+ +
+ {{ channel.url }} + +
+ +
+ @for (field of streamFields; track field.labelKey) { +
+
{{ field.labelKey | translate }}
+
+ @if (field.valueKey) { + {{ + field.valueKey + | translate: field.translateParams + }} + } @else { + {{ field.value }} + } +
+
+ } +
+ + @if (hasTvgData) { +
+ +
+ @for (field of tvgFields; track field.labelKey) { +
+
{{ field.labelKey | translate }}
+
+ @if (field.valueKey) { + {{ + field.valueKey + | translate + : field.translateParams + }} + } @else { + {{ field.value }} + } +
+
} - +
} - -
+ -
-

- {{ 'CHANNELS.DETAILS_DIALOG.TVG' | translate }} -

-
- @for (field of tvgFields; track field.labelKey) { -
-
{{ field.labelKey | translate }}
-
- @if (field.valueKey) { - {{ field.valueKey | translate: field.translateParams }} - } @else { - {{ field.value }} +
+
+

+ {{ 'CHANNELS.DETAILS_DIALOG.CATCHUP' | translate }} +

+

+ {{ channel.catchup?.type || ('CHANNELS.DETAILS_DIALOG.NOT_AVAILABLE' | translate) }} +

+
+ +
+ @for (field of catchupFields; track field.labelKey) { +
+
{{ field.labelKey | translate }}
+
+ @if (field.valueKey) { + {{ + field.valueKey + | translate: field.translateParams + }} + } @else { + {{ field.value }} + } +
+
+ } +
+ + @if (hasHttpData) { +
+ +
+ @for (field of httpFields; track field.labelKey) { +
+
{{ field.labelKey | translate }}
+
+ @if (field.valueKey) { + {{ + field.valueKey + | translate + : field.translateParams + }} + } @else { + {{ field.value }} + } +
+
} -
+
} - - - -
-

- {{ 'CHANNELS.DETAILS_DIALOG.CATCHUP' | translate }} -

-
- @for (field of catchupFields; track field.labelKey) { -
-
{{ field.labelKey | translate }}
-
- @if (field.valueKey) { - {{ field.valueKey | translate: field.translateParams }} - } @else { - {{ field.value }} - } -
-
- } -
-
- -
-

- {{ 'CHANNELS.DETAILS_DIALOG.HTTP' | translate }} -

-
- @for (field of httpFields; track field.labelKey) { -
-
{{ field.labelKey | translate }}
-
- @if (field.valueKey) { - {{ field.valueKey | translate: field.translateParams }} - } @else { - {{ field.value }} - } -
-
- } -
-
+ +
- - diff --git a/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.scss b/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.scss index 445fbb956..7b53595d4 100644 --- a/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.scss +++ b/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.scss @@ -1,80 +1,390 @@ :host { display: block; + color: var(--app-body-color, var(--mat-sys-on-surface)); } -.channel-details-dialog { - max-height: min(78vh, 720px); -} - -.dialog-description { - margin: 0 0 20px; - color: var(--mat-sys-on-surface-variant); -} - -.details-section + .details-section { - margin-top: 24px; - padding-top: 20px; - border-top: 1px solid var(--mat-sys-outline-variant); -} - -.section-title { - margin: 0 0 12px; - font-size: 0.92rem; - font-weight: 600; - line-height: 1.3; -} - -.details-grid { +// ─── Hero header ──────────────────────────────────────────────────────────── +.channel-dialog__hero { + margin: 0; + padding: 20px 20px 18px; display: grid; - gap: 12px; - margin: 0; + gap: 18px; + background: + var(--app-accent-gradient), + var(--app-widget-header-bg, var(--mat-sys-surface-container-high)); + border-bottom: 1px solid var(--app-widget-header-border, rgba(255, 255, 255, 0.05)); } -.detail-row { +.channel-dialog__hero-copy { display: grid; - grid-template-columns: minmax(140px, 180px) minmax(0, 1fr); - gap: 6px 16px; - align-items: start; + gap: 10px; } -dt { +.channel-dialog__eyebrow { + display: inline-flex; + align-items: center; + gap: 8px; margin: 0; - color: var(--mat-sys-on-surface-variant); + color: var(--app-eyebrow-color, var(--mat-sys-on-surface-variant)); + font-size: 0.72rem; font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; } -dd { - margin: 0; +.channel-dialog__eyebrow-mark { + width: 8px; + height: 8px; + border-radius: 999px; + background: var(--mat-sys-primary); + box-shadow: 0 0 0 4px color-mix(in srgb, var(--mat-sys-primary) 14%, transparent); +} + +.channel-dialog__headline-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 14px; +} + +.channel-dialog__headline-text { + display: flex; + align-items: center; + gap: 14px; min-width: 0; - line-height: 1.45; +} - &.empty { - color: var(--mat-sys-on-surface-variant); +.channel-dialog__logo { + width: 48px; + height: 48px; + border-radius: 12px; + object-fit: contain; + background: var(--mat-sys-surface-container); + border: 1px solid var(--app-separator); + flex-shrink: 0; +} + +.channel-dialog__title { + margin: 0; + font-size: 1.3rem; + line-height: 1.2; + font-weight: 650; + letter-spacing: -0.02em; + color: var(--app-heading-color, var(--mat-sys-on-surface)); +} + +.channel-dialog__subtitle { + margin: 4px 0 0; + color: var(--app-muted-color, var(--mat-sys-on-surface-variant)); + font-size: 0.88rem; +} + +// ─── Status pills ─────────────────────────────────────────────────────────── +.channel-dialog__status-cluster { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 6px; + flex-shrink: 0; +} + +.channel-dialog__status-pill { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 26px; + padding: 0 10px; + border-radius: 999px; + border: 1px solid var(--app-separator); + background: var(--mat-sys-surface-container); + color: var(--app-body-color, var(--mat-sys-on-surface)); + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.channel-dialog__status-pill--active { + border-color: color-mix(in srgb, #34d399 30%, transparent); + background: color-mix(in srgb, #34d399 12%, transparent); + color: #2db36f; +} + +.channel-dialog__status-pill--radio { + border-color: color-mix(in srgb, var(--mat-sys-tertiary) 30%, transparent); + background: color-mix(in srgb, var(--mat-sys-tertiary) 12%, transparent); + color: var(--mat-sys-tertiary); +} + +.channel-dialog__status-pill--playback { + border-color: color-mix(in srgb, var(--mat-sys-primary) 30%, transparent); + background: color-mix(in srgb, var(--mat-sys-primary) 12%, transparent); + color: var(--mat-sys-primary); +} + +// ─── Hero stat cards ──────────────────────────────────────────────────────── +.channel-dialog__hero-stats { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; +} + +.channel-stat { + display: grid; + gap: 4px; + padding: 12px 14px; + border-radius: 12px; + border: 1px solid var(--app-widget-border, transparent); + background: var(--app-widget-bg, var(--mat-sys-surface-container)); + box-shadow: var(--app-widget-shadow, none); + outline: 1px solid rgba(255, 255, 255, 0.04); + outline-offset: -1px; +} + +.channel-stat__icon { + width: 32px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 10px; + background: color-mix(in srgb, var(--mat-sys-primary) 12%, transparent); + color: var(--mat-sys-primary); + + mat-icon { + font-size: 18px; + width: 18px; + height: 18px; + } +} + +.channel-stat__label { + color: var(--app-muted-color, var(--mat-sys-on-surface-variant)); + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.channel-stat__value { + font-size: 1.1rem; + font-weight: 650; + letter-spacing: -0.02em; + color: var(--app-heading-color, var(--mat-sys-on-surface)); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &--mono { + font-family: 'JetBrains Mono', 'Roboto Mono', monospace; + font-size: 0.92rem; + } + + &--empty { + color: var(--app-muted-color, var(--mat-sys-on-surface-variant)); font-style: italic; + font-weight: 500; + font-size: 0.88rem; } +} - &.monospace { - font-family: - ui-monospace, - SFMono-Regular, - Menlo, - Monaco, - Consolas, - 'Liberation Mono', - 'Courier New', - monospace; - font-size: 0.92em; - } +// ─── Content area ─────────────────────────────────────────────────────────── +.channel-dialog__content { + margin: 0; + padding: 20px; + background: var(--app-widget-bg, var(--mat-sys-surface)); +} - &.wrap { +.channel-dialog__grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + +// ─── URL block ────────────────────────────────────────────────────────────── +.channel-dialog__url-block { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 10px 10px 10px 14px; + border-radius: 10px; + border: 1px solid var(--app-separator); + background: var(--mat-sys-surface-container); + + code { + flex: 1; + min-width: 0; + font-family: 'JetBrains Mono', 'Roboto Mono', monospace; + font-size: 0.82rem; + line-height: 1.5; + color: var(--app-body-color, var(--mat-sys-on-surface)); overflow-wrap: anywhere; word-break: break-word; } } -@media (max-width: 640px) { - .detail-row { - grid-template-columns: minmax(0, 1fr); +.channel-dialog__copy-btn { + flex-shrink: 0; + width: 32px; + height: 32px; + --mdc-icon-button-state-layer-size: 32px; + --mdc-icon-button-icon-size: 18px; + color: var(--app-muted-color, var(--mat-sys-on-surface-variant)); +} + +// ─── Detail panels ────────────────────────────────────────────────────────── +.channel-panel { + display: grid; + gap: 14px; + padding: 16px; + border-radius: 16px; + border: 1px solid var(--app-widget-border, transparent); + background: var(--app-widget-bg, var(--mat-sys-surface-container-low)); + box-shadow: var(--app-widget-shadow, none); + outline: 1px solid rgba(255, 255, 255, 0.04); + outline-offset: -1px; + align-content: start; +} + +.channel-panel__header { + display: grid; + gap: 4px; +} + +.channel-panel__header h3 { + margin: 0; + font-size: 1rem; + font-weight: 600; + letter-spacing: -0.01em; + color: var(--app-heading-color, var(--mat-sys-on-surface)); +} + +.channel-panel__eyebrow { + margin: 0; + color: var(--app-eyebrow-color, var(--mat-sys-on-surface-variant)); + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +// ─── Detail rows ──────────────────────────────────────────────────────────── +.channel-detail-list { + display: grid; + gap: 0; + margin: 0; +} + +.channel-detail-row { + display: grid; + grid-template-columns: minmax(100px, 130px) minmax(0, 1fr); + gap: 12px; + align-items: baseline; + padding: 8px 0; + border-bottom: 1px solid var(--app-separator); +} + +.channel-detail-row:last-child { + border-bottom: 0; + padding-bottom: 0; +} + +.channel-detail-row dt { + margin: 0; + color: var(--app-muted-color, var(--mat-sys-on-surface-variant)); + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.channel-detail-row dd { + margin: 0; + color: var(--app-body-color, var(--mat-sys-on-surface)); + font-size: 0.88rem; + font-weight: 500; + word-break: break-word; + overflow-wrap: anywhere; +} + +.channel-detail-row__value--mono { + font-family: 'JetBrains Mono', 'Roboto Mono', monospace; + font-size: 0.82rem; +} + +.channel-detail-row__value--empty { + color: var(--app-muted-color, var(--mat-sys-on-surface-variant)); + font-style: italic; +} + +// ─── Chip sections (sub-group headers) ────────────────────────────────────── +.channel-chip-section { + display: grid; + gap: 10px; + padding-top: 4px; +} + +.channel-chip-section__label { + margin: 0; + color: var(--app-eyebrow-color, var(--mat-sys-on-surface-variant)); + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.1em; + text-transform: uppercase; + padding-top: 4px; + border-top: 1px solid var(--app-separator); +} + +// ─── Footer actions ───────────────────────────────────────────────────────── +.channel-dialog__actions { + padding: 0 20px 20px; + display: flex; + justify-content: flex-end; + gap: 10px; + border-top: 1px solid var(--app-widget-header-border, rgba(255, 255, 255, 0.05)); + background: var(--app-widget-bg, var(--mat-sys-surface)); +} + +.channel-dialog__close { + min-width: 100px; + border-radius: 999px; +} + +// ─── Responsive breakpoints ───────────────────────────────────────────────── +@media (max-width: 720px) { + .channel-dialog__hero, + .channel-dialog__content, + .channel-dialog__actions { + padding-left: 16px; + padding-right: 16px; + } + + .channel-dialog__headline-row { + flex-direction: column; + align-items: flex-start; + } + + .channel-dialog__status-cluster { + justify-content: flex-start; + } + + .channel-dialog__hero-stats, + .channel-dialog__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .channel-detail-row { + grid-template-columns: 1fr; gap: 4px; } } + +@media (max-width: 480px) { + .channel-dialog__hero-stats { + grid-template-columns: 1fr; + } + + .channel-dialog__grid { + grid-template-columns: 1fr; + } +} diff --git a/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.spec.ts b/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.spec.ts index cc6b51cbc..61b5584b2 100644 --- a/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.spec.ts +++ b/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.spec.ts @@ -62,23 +62,13 @@ describe('ChannelDetailsDialogComponent', () => { expect(component.archiveDays).toBe(5); expect(component.catchupAvailable).toBe(true); expect(component.catchupPlaybackSupported).toBe(true); - expect(component.summaryFields).toEqual( + expect(component.heroStats).toEqual( expect.arrayContaining([ - expect.objectContaining({ - labelKey: 'CHANNELS.DETAILS_DIALOG.CATCHUP_AVAILABLE', - valueKey: 'YES', - }), expect.objectContaining({ labelKey: 'CHANNELS.DETAILS_DIALOG.WINDOW', - translateParams: { - count: 5, - }, + translateParams: { count: 5 }, valueKey: 'CHANNELS.DETAILS_DIALOG.DAYS_OTHER', }), - expect.objectContaining({ - labelKey: 'CHANNELS.DETAILS_DIALOG.PLAYBACK_SUPPORTED', - valueKey: 'YES', - }), ]) ); }); diff --git a/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.ts b/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.ts index 321a5c0ea..99301f3a7 100644 --- a/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.ts +++ b/libs/ui/components/src/lib/channel-list-container/channel-details-dialog/channel-details-dialog.component.ts @@ -1,6 +1,8 @@ +import { ClipboardModule } from '@angular/cdk/clipboard'; import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { MatIconModule } from '@angular/material/icon'; import { TranslatePipe } from '@ngx-translate/core'; import { getM3uArchiveDays, isM3uCatchupPlaybackSupported } from 'm3u-utils'; import { Channel } from 'shared-interfaces'; @@ -12,7 +14,16 @@ interface ChannelDetailField { readonly translateParams?: Record; readonly value?: string; readonly valueKey?: string; - readonly wrap?: boolean; +} + +interface HeroStat { + readonly empty?: boolean; + readonly icon: string; + readonly labelKey: string; + readonly mono?: boolean; + readonly translateParams?: Record; + readonly value?: string; + readonly valueKey?: string; } @Component({ @@ -20,7 +31,7 @@ interface ChannelDetailField { templateUrl: './channel-details-dialog.component.html', styleUrls: ['./channel-details-dialog.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, - imports: [MatButtonModule, MatDialogModule, TranslatePipe], + imports: [ClipboardModule, MatButtonModule, MatDialogModule, MatIconModule, TranslatePipe], }) export class ChannelDetailsDialogComponent { readonly channel = inject(MAT_DIALOG_DATA); @@ -31,151 +42,121 @@ export class ChannelDetailsDialogComponent { this.channel ); - readonly summaryFields: ChannelDetailField[] = [ - this.createTextField( - 'CHANNELS.DETAILS_DIALOG.NAME', - this.channel.name - ), - this.createTextField( - 'CHANNELS.DETAILS_DIALOG.CHANNEL_ID', - this.channel.id, - { - monospace: true, - } - ), - this.createTextField( - 'CHANNELS.DETAILS_DIALOG.STREAM_URL', - this.channel.url, - { - monospace: true, - wrap: true, - } - ), - this.createTextField( - 'CHANNELS.DETAILS_DIALOG.GROUP', - this.channel.group?.title - ), - this.createBooleanField( - 'CHANNELS.DETAILS_DIALOG.RADIO', - this.channel.radio === 'true' - ), - this.createBooleanField( - 'CHANNELS.DETAILS_DIALOG.CATCHUP_AVAILABLE', - this.catchupAvailable - ), - this.createArchiveWindowField(), - this.createBooleanField( - 'CHANNELS.DETAILS_DIALOG.PLAYBACK_SUPPORTED', - this.catchupPlaybackSupported - ), - this.createTextField( + logoError = false; + + readonly hasTvgData = !!( + this.channel.tvg?.id || + this.channel.tvg?.name || + this.channel.tvg?.url || + this.channel.tvg?.rec + ); + + readonly hasHttpData = !!( + this.channel.http?.origin || + this.channel.http?.referrer || + this.channel.http?.['user-agent'] + ); + + readonly heroStats: HeroStat[] = [ + { + icon: 'tag', + labelKey: 'CHANNELS.DETAILS_DIALOG.CHANNEL_ID', + value: this.channel.id?.trim() || undefined, + valueKey: this.channel.id?.trim() + ? undefined + : 'CHANNELS.DETAILS_DIALOG.EMPTY', + mono: true, + empty: !this.channel.id?.trim(), + }, + { + icon: 'folder', + labelKey: 'CHANNELS.DETAILS_DIALOG.GROUP', + value: this.channel.group?.title?.trim() || undefined, + valueKey: this.channel.group?.title?.trim() + ? undefined + : 'CHANNELS.DETAILS_DIALOG.EMPTY', + empty: !this.channel.group?.title?.trim(), + }, + this.createArchiveHeroStat(), + { + icon: 'schedule', + labelKey: 'CHANNELS.DETAILS_DIALOG.TIMESHIFT', + value: this.channel.timeshift?.trim() || undefined, + valueKey: this.channel.timeshift?.trim() + ? undefined + : 'CHANNELS.DETAILS_DIALOG.NOT_AVAILABLE', + empty: !this.channel.timeshift?.trim(), + }, + ]; + + readonly streamFields: ChannelDetailField[] = [ + this.field( 'CHANNELS.DETAILS_DIALOG.EPG_PARAMS', this.channel.epgParams, - { - monospace: true, - wrap: true, - } + true ), - this.createTextField( - 'CHANNELS.DETAILS_DIALOG.TIMESHIFT', - this.channel.timeshift + this.field( + 'CHANNELS.DETAILS_DIALOG.TVG_LOGO', + this.channel.tvg?.logo, + true ), ]; readonly tvgFields: ChannelDetailField[] = [ - this.createTextField( - 'CHANNELS.DETAILS_DIALOG.TVG_ID', - this.channel.tvg?.id, - { - monospace: true, - } - ), - this.createTextField( - 'CHANNELS.DETAILS_DIALOG.TVG_NAME', - this.channel.tvg?.name - ), - this.createTextField( + this.field('CHANNELS.DETAILS_DIALOG.TVG_ID', this.channel.tvg?.id, true), + this.field('CHANNELS.DETAILS_DIALOG.TVG_NAME', this.channel.tvg?.name), + this.field( 'CHANNELS.DETAILS_DIALOG.TVG_URL', this.channel.tvg?.url, - { - monospace: true, - wrap: true, - } - ), - this.createTextField( - 'CHANNELS.DETAILS_DIALOG.TVG_LOGO', - this.channel.tvg?.logo, - { - monospace: true, - wrap: true, - } - ), - this.createTextField( - 'CHANNELS.DETAILS_DIALOG.TVG_REC', - this.channel.tvg?.rec + true ), + this.field('CHANNELS.DETAILS_DIALOG.TVG_REC', this.channel.tvg?.rec), ]; readonly catchupFields: ChannelDetailField[] = [ - this.createTextField( - 'CHANNELS.DETAILS_DIALOG.CATCHUP_TYPE', - this.channel.catchup?.type - ), - this.createTextField( + this.field( 'CHANNELS.DETAILS_DIALOG.CATCHUP_SOURCE', this.channel.catchup?.source, - { - monospace: true, - wrap: true, - } + true ), - this.createTextField( + this.field( 'CHANNELS.DETAILS_DIALOG.CATCHUP_DAYS', this.channel.catchup?.days ), ]; readonly httpFields: ChannelDetailField[] = [ - this.createTextField( + this.field( 'CHANNELS.DETAILS_DIALOG.HTTP_ORIGIN', this.channel.http?.origin, - { - monospace: true, - wrap: true, - } + true ), - this.createTextField( + this.field( 'CHANNELS.DETAILS_DIALOG.HTTP_REFERRER', this.channel.http?.referrer, - { - monospace: true, - wrap: true, - } + true ), - this.createTextField( + this.field( 'CHANNELS.DETAILS_DIALOG.HTTP_USER_AGENT', this.channel.http?.['user-agent'], - { - monospace: true, - wrap: true, - } + true ), ]; - private createArchiveWindowField(): ChannelDetailField { + private createArchiveHeroStat(): HeroStat { if (!this.catchupAvailable) { return { empty: true, + icon: 'history', labelKey: 'CHANNELS.DETAILS_DIALOG.WINDOW', valueKey: 'CHANNELS.DETAILS_DIALOG.NOT_AVAILABLE', }; } return { + icon: 'history', labelKey: 'CHANNELS.DETAILS_DIALOG.WINDOW', - translateParams: { - count: this.archiveDays, - }, + translateParams: { count: this.archiveDays }, valueKey: this.archiveDays === 1 ? 'CHANNELS.DETAILS_DIALOG.DAYS_ONE' @@ -183,36 +164,22 @@ export class ChannelDetailsDialogComponent { }; } - private createBooleanField( - labelKey: string, - value: boolean - ): ChannelDetailField { - return { - labelKey, - valueKey: value ? 'YES' : 'NO', - }; - } - - private createTextField( + private field( labelKey: string, value: string | null | undefined, - options: Pick = {} + monospace = false ): ChannelDetailField { const normalized = value?.trim() ?? ''; if (!normalized) { return { - ...options, empty: true, labelKey, + monospace, valueKey: 'CHANNELS.DETAILS_DIALOG.EMPTY', }; } - return { - ...options, - labelKey, - value: normalized, - }; + return { labelKey, monospace, value: normalized }; } }