mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 13:46:16 -08:00
feat(epg): add vertical list view for the live EPG panel
Add an EPG list view — a vertical, single-day programme list — as an alternative rendering of the live EPG panel, selectable via a new Settings → EPG → "Guide view" toggle (epgViewMode: 'timeline' | 'list', default 'timeline' so existing users see no change). - New EpgListViewComponent (app-epg-list-view) mirrors EpgTimelineComponent's input/output contract 1:1, so all four live hosts (M3U player, unified live tab, Xtream, Stalker) swap the panel with a plain @if and identical bindings. - Reuses the shared view-agnostic EPG modules (classifyTimelineWhen, hasProgramsForDateKey, epg-archive.util catch-up gating, epg-summary.util collapsed-summary maths, epg-date helpers, EpgProgrammeDialogService, app-epg-timeline-empty-state) — no duplicated logic. - Rows show time range, title, optional description, live progress on the on-air row, catch-up "Watch" on past rows when archive playback is available, and a details dialog; keyboard activation guards nested buttons (target === currentTarget). - Auto-focuses the on-air row on channel select, restores it across collapse/expand remounts, and shows a sticky in-flow "On now" strip (never overlaying rows) when the current programme is scrolled away; all scroll maths is rect-based relative to the scroller. - List mode raises only the inline panel height via an epg--list modifier (--epg-inline-height clamp); timeline and collapsed heights are unchanged. - Setting flows end-to-end (Settings interface → DEFAULT_SETTINGS → SettingsStore/StorageMap → segmented control in the EPG section); Electron-only UI, PWA stays on the timeline default. i18n keys added to all 18 locales. - Tests: new component/row/utils/scroll-controller specs, settings persistence spec, swap tests in all four host specs, and Electron E2E for the settings round-trip and the rendered list view. Docs updated (m3u-playlist-module.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
401012eac4
commit
c4890748e0
57 files changed
+2981
-90
No files matched your search
@@ -231,6 +231,38 @@ test.describe('Electron Settings', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('@settings @persistence @electron persists the EPG view mode across app restart', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
const firstLaunch = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await openSettings(firstLaunch.mainWindow);
|
||||
const listToggle = firstLaunch.mainWindow.locator(
|
||||
'[data-test-id="epg-view-mode-list"]'
|
||||
);
|
||||
await expect(listToggle).toBeVisible();
|
||||
await listToggle.click();
|
||||
await expect(listToggle).toHaveAttribute('aria-checked', 'true');
|
||||
await saveSettings(firstLaunch.mainWindow);
|
||||
} finally {
|
||||
await closeElectronApp(firstLaunch);
|
||||
}
|
||||
|
||||
const secondLaunch = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await openSettings(secondLaunch.mainWindow);
|
||||
await expect(
|
||||
secondLaunch.mainWindow.locator(
|
||||
'[data-test-id="epg-view-mode-list"]'
|
||||
)
|
||||
).toHaveAttribute('aria-checked', 'true');
|
||||
} finally {
|
||||
await closeElectronApp(secondLaunch);
|
||||
}
|
||||
});
|
||||
|
||||
test('@settings @electron starts on sources when dashboard is disabled', async ({ dataDir }) => {
|
||||
const firstLaunch = await launchElectronApp(dataDir);
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ import {
|
||||
clickCategoryByNameExact,
|
||||
closeElectronApp,
|
||||
expect,
|
||||
goToDashboard,
|
||||
launchElectronApp,
|
||||
openSettings,
|
||||
openWorkspaceSection,
|
||||
resetMockServers,
|
||||
saveSettings,
|
||||
test,
|
||||
waitForXtreamWorkspaceReady,
|
||||
} from './electron-test-fixtures';
|
||||
@@ -121,6 +124,64 @@ for (const timeZone of ['UTC', 'Europe/Berlin'] as const) {
|
||||
});
|
||||
}
|
||||
|
||||
test('@epg @xtream @electron renders the vertical list view when the setting is "list"', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['xtream']);
|
||||
const fixture = await fetchXtreamEpgFixture(request, epgCredentials);
|
||||
const currentProgram = fixture.shortEpg[0];
|
||||
if (!currentProgram) {
|
||||
throw new Error(
|
||||
'Expected the Xtream EPG fixture to include a current program.'
|
||||
);
|
||||
}
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
// Opt into the list view first (from the fresh workspace) so the portal
|
||||
// → Live TV → channel flow afterwards mirrors the timeline test exactly.
|
||||
await openSettings(app.mainWindow);
|
||||
await app.mainWindow
|
||||
.locator('[data-test-id="epg-view-mode-list"]')
|
||||
.click();
|
||||
await saveSettings(app.mainWindow);
|
||||
await goToDashboard(app.mainWindow);
|
||||
|
||||
await addXtreamPortal(app.mainWindow, {
|
||||
name: `${epgPortalName} List`,
|
||||
username: epgCredentials.username,
|
||||
password: epgCredentials.password,
|
||||
});
|
||||
await waitForXtreamWorkspaceReady(app.mainWindow);
|
||||
await openWorkspaceSection(app.mainWindow, 'Live TV');
|
||||
await clickCategoryByNameExact(app.mainWindow, fixture.categoryName);
|
||||
const channelRow = channelItemByTitle(
|
||||
app.mainWindow,
|
||||
fixture.stream.name ?? ''
|
||||
).first();
|
||||
await expect(channelRow).toBeVisible({ timeout: 20000 });
|
||||
await channelRow.click();
|
||||
|
||||
// The list view renders instead of the timeline.
|
||||
await expect(app.mainWindow.locator('app-epg-list-view')).toBeVisible({
|
||||
timeout: 20000,
|
||||
});
|
||||
await expect(
|
||||
app.mainWindow.locator('app-epg-timeline')
|
||||
).toHaveCount(0);
|
||||
|
||||
// The on-air programme is the highlighted "now" row.
|
||||
await expect(
|
||||
app.mainWindow
|
||||
.locator('app-epg-list-view .g-row[data-when="now"] .title')
|
||||
.first()
|
||||
).toHaveText(currentProgram.title);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
function formatTimeInZone(timestampSeconds: number, timeZone: string): string {
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
timeZone,
|
||||
|
||||
@@ -14,6 +14,40 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="setting-item setting-item--full-control">
|
||||
<div class="setting-item__meta">
|
||||
<h4>{{ 'SETTINGS.EPG_VIEW_MODE' | translate }}</h4>
|
||||
<p>{{ 'SETTINGS.EPG_VIEW_MODE_DESCRIPTION' | translate }}</p>
|
||||
</div>
|
||||
<div class="setting-item__control">
|
||||
<div
|
||||
class="theme-switcher"
|
||||
role="radiogroup"
|
||||
[attr.aria-label]="'SETTINGS.EPG_VIEW_MODE' | translate"
|
||||
data-test-id="select-epg-view-mode"
|
||||
>
|
||||
@for (option of epgViewModeOptions(); track option.value) {
|
||||
<button
|
||||
type="button"
|
||||
class="theme-switcher__option"
|
||||
[class.theme-switcher__option--selected]="
|
||||
form().value.epgViewMode === option.value
|
||||
"
|
||||
[attr.aria-checked]="
|
||||
form().value.epgViewMode === option.value
|
||||
"
|
||||
[attr.data-test-id]="'epg-view-mode-' + option.value"
|
||||
role="radio"
|
||||
(click)="selectEpgViewMode.emit(option.value)"
|
||||
>
|
||||
<mat-icon>{{ option.icon }}</mat-icon>
|
||||
<span>{{ option.labelKey | translate }}</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item setting-item--full-control">
|
||||
<div class="setting-item__control">
|
||||
<ng-container formArrayName="epgUrl">
|
||||
|
||||
@@ -7,8 +7,10 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { EpgViewMode } from '@iptvnator/shared/interfaces';
|
||||
import { EpgSourceStatusComponent } from '@iptvnator/ui/epg';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { EpgViewModeOption } from './settings.models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-epg-section',
|
||||
@@ -33,10 +35,12 @@ export class SettingsEpgSectionComponent {
|
||||
readonly activeSection = input.required<string>();
|
||||
readonly epgUrl = input.required<FormArray>();
|
||||
readonly isClearingEpgData = input(false);
|
||||
readonly epgViewModeOptions = input.required<EpgViewModeOption[]>();
|
||||
|
||||
readonly refreshEpg = output<string>();
|
||||
readonly removeEpgSource = output<number>();
|
||||
readonly addEpgSource = output<void>();
|
||||
readonly refreshAllEpg = output<void>();
|
||||
readonly clearEpgData = output<void>();
|
||||
readonly selectEpgViewMode = output<EpgViewMode>();
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import {
|
||||
CoverSize,
|
||||
DEFAULT_DASHBOARD_RAILS_SETTINGS,
|
||||
EpgViewMode,
|
||||
Language,
|
||||
normalizeDashboardRailsSettings,
|
||||
normalizeExternalPlayerArguments,
|
||||
@@ -70,7 +71,12 @@ export function createSettingsForm(
|
||||
],
|
||||
recordingFolder: '',
|
||||
coverSize: 'medium' as CoverSize,
|
||||
...(supportsEpg ? { preferUploadedEpgOverXtream: false } : {}),
|
||||
...(supportsEpg
|
||||
? {
|
||||
preferUploadedEpgOverXtream: false,
|
||||
epgViewMode: 'timeline' as EpgViewMode,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -129,6 +135,8 @@ export function createSettingsFromFormValue(
|
||||
value.preferUploadedEpgOverXtream ??
|
||||
currentSettings.preferUploadedEpgOverXtream ??
|
||||
false,
|
||||
epgViewMode:
|
||||
value.epgViewMode ?? currentSettings.epgViewMode ?? 'timeline',
|
||||
trustedPrivateNetworkEpgUrls:
|
||||
currentSettings.trustedPrivateNetworkEpgUrls ?? [],
|
||||
trustedInsecureTlsHosts: currentSettings.trustedInsecureTlsHosts ?? [],
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
CoverSize,
|
||||
EpgViewMode,
|
||||
StartupBehavior,
|
||||
Theme,
|
||||
VideoPlayer,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import {
|
||||
CoverSizeOption,
|
||||
EpgViewModeOption,
|
||||
SettingsPlayerOption,
|
||||
SettingsSection,
|
||||
StartupBehaviorOption,
|
||||
@@ -48,6 +50,19 @@ export const SETTINGS_COVER_SIZE_OPTIONS: CoverSizeOption[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const SETTINGS_EPG_VIEW_MODE_OPTIONS: EpgViewModeOption[] = [
|
||||
{
|
||||
value: 'timeline' satisfies EpgViewMode,
|
||||
icon: 'view_timeline',
|
||||
labelKey: 'SETTINGS.EPG_VIEW_MODE_TIMELINE',
|
||||
},
|
||||
{
|
||||
value: 'list' satisfies EpgViewMode,
|
||||
icon: 'view_list',
|
||||
labelKey: 'SETTINGS.EPG_VIEW_MODE_LIST',
|
||||
},
|
||||
];
|
||||
|
||||
export const SETTINGS_STARTUP_BEHAVIOR_OPTIONS: StartupBehaviorOption[] = [
|
||||
{
|
||||
value: StartupBehavior.FirstView,
|
||||
|
||||
@@ -59,11 +59,13 @@
|
||||
[activeSection]="activeSection()"
|
||||
[epgUrl]="epgUrl"
|
||||
[isClearingEpgData]="isClearingEpgData()"
|
||||
[epgViewModeOptions]="epgViewModeOptions"
|
||||
(refreshEpg)="refreshEpg($event)"
|
||||
(removeEpgSource)="removeEpgSource($event)"
|
||||
(addEpgSource)="addEpgSource()"
|
||||
(refreshAllEpg)="refreshAllEpg()"
|
||||
(clearEpgData)="clearEpgData()"
|
||||
(selectEpgViewMode)="selectEpgViewMode($event)"
|
||||
/>
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ const DEFAULT_SETTINGS = {
|
||||
coverSize: 'medium',
|
||||
dashboardRails: DEFAULT_DASHBOARD_RAILS,
|
||||
preferUploadedEpgOverXtream: false,
|
||||
epgViewMode: 'timeline',
|
||||
};
|
||||
|
||||
const DEFAULT_APP_UPDATE_STATUS: ElectronBridgeAppUpdateStatus = {
|
||||
@@ -1295,6 +1296,23 @@ describe('SettingsComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('updates the EPG view mode through the epg section output', () => {
|
||||
const mockStore = settingsStore as unknown as MockSettingsStore;
|
||||
const listButton = (
|
||||
fixture.nativeElement as HTMLElement
|
||||
).querySelector(
|
||||
'[data-test-id="epg-view-mode-list"]'
|
||||
) as HTMLButtonElement;
|
||||
|
||||
listButton.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.settingsForm.value.epgViewMode).toBe('list');
|
||||
expect(mockStore.updateSettings).toHaveBeenCalledWith({
|
||||
epgViewMode: 'list',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders dashboard controls with the expected defaults', () => {
|
||||
const nativeElement = fixture.nativeElement as HTMLElement;
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
CoverSize,
|
||||
ELECTRON_BRIDGE_APP_UPDATE_STATUSES,
|
||||
ElectronBridgeAppUpdateStatus,
|
||||
EpgViewMode,
|
||||
Language,
|
||||
StreamFormat,
|
||||
Theme,
|
||||
@@ -69,6 +70,7 @@ import {
|
||||
buildSettingsSectionNavItems,
|
||||
SETTINGS_COVER_SIZE_OPTIONS,
|
||||
SETTINGS_EMBEDDED_PLAYER_OPTIONS,
|
||||
SETTINGS_EPG_VIEW_MODE_OPTIONS,
|
||||
SETTINGS_OS_PLAYER_OPTIONS,
|
||||
SETTINGS_STARTUP_BEHAVIOR_OPTIONS,
|
||||
SETTINGS_THEME_OPTIONS,
|
||||
@@ -203,6 +205,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
readonly themeOptions = SETTINGS_THEME_OPTIONS;
|
||||
readonly coverSizeOptions = SETTINGS_COVER_SIZE_OPTIONS;
|
||||
readonly startupBehaviorOptions = SETTINGS_STARTUP_BEHAVIOR_OPTIONS;
|
||||
readonly epgViewModeOptions = SETTINGS_EPG_VIEW_MODE_OPTIONS;
|
||||
|
||||
/** Settings form object */
|
||||
settingsForm = createSettingsForm(this.formBuilder, this.supportsEpg);
|
||||
@@ -507,6 +510,17 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
this.settingsStore.updateSettings({ coverSize: size });
|
||||
}
|
||||
|
||||
selectEpgViewMode(mode: EpgViewMode): void {
|
||||
if (this.settingsForm.value.epgViewMode === mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.settingsForm.patchValue({ epgViewMode: mode });
|
||||
this.settingsForm.get('epgViewMode')?.markAsDirty();
|
||||
this.settingsForm.markAsDirty();
|
||||
this.settingsStore.updateSettings({ epgViewMode: mode });
|
||||
}
|
||||
|
||||
async selectRecordingFolder(): Promise<void> {
|
||||
if (
|
||||
!this.isDesktop ||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
CoverSize,
|
||||
EpgViewMode,
|
||||
StartupBehavior,
|
||||
Theme,
|
||||
VideoPlayer,
|
||||
@@ -34,6 +35,12 @@ export interface CoverSizeOption {
|
||||
labelKey: string;
|
||||
}
|
||||
|
||||
export interface EpgViewModeOption {
|
||||
value: EpgViewMode;
|
||||
icon: string;
|
||||
labelKey: string;
|
||||
}
|
||||
|
||||
export interface SettingsPlayerOption {
|
||||
id: VideoPlayer;
|
||||
labelKey: string;
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "صغير",
|
||||
"COVER_SIZE_MEDIUM": "متوسط",
|
||||
"COVER_SIZE_LARGE": "كبير",
|
||||
"EPG_VIEW_MODE": "عرض الدليل",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "اختر طريقة عرض دليل البرامج أسفل المشغل: شريط جدول زمني أفقي، أو قائمة رأسية ليوم واحد.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "الجدول الزمني",
|
||||
"EPG_VIEW_MODE_LIST": "قائمة",
|
||||
"STARTUP_BEHAVIOR": "عرض البدء",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "اختر ما إذا كان IPTVnator يفتح أول عرض متاح في مساحة العمل أو يستعيد آخر عرض على مستوى القسم. العرض الأول يعني لوحة التحكم عند تفعيلها، وإلا فالمصادر.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "أول عرض متاح",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "صغير",
|
||||
"COVER_SIZE_MEDIUM": "متوسط",
|
||||
"COVER_SIZE_LARGE": "كبير",
|
||||
"EPG_VIEW_MODE": "عرض الدليل",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "اختار كيفاش يبان دليل البرامج تحت المشغل: شريط زمني أفقي ولا لائحة عمودية ديال نهار واحد.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "الخط الزمني",
|
||||
"EPG_VIEW_MODE_LIST": "لائحة",
|
||||
"STARTUP_BEHAVIOR": "العرض عند البدء",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "اختار واش IPTVnator يحل أول عرض متوفر في مساحة العمل ولا يسترجع آخر عرض على مستوى القسم. أول عرض كيعني لوحة التحكم إلى كانت مفعلة، إلا فالا فهي Sources.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "أول عرض متوفر",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Малы",
|
||||
"COVER_SIZE_MEDIUM": "Сярэдні",
|
||||
"COVER_SIZE_LARGE": "Вялікі",
|
||||
"EPG_VIEW_MODE": "Выгляд праграмы перадач",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Выберыце, як паказваць праграму перадач пад прайгравальнікам: гарызантальнай стужкай часу або вертыкальным спісам на адзін дзень.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Стужка часу",
|
||||
"EPG_VIEW_MODE_LIST": "Спіс",
|
||||
"STARTUP_BEHAVIOR": "Старт праграмы",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Выберыце, ці адкрывае IPTVnator першы даступны выгляд працоўнай прасторы, ці аднаўляе ваш апошні выгляд на ўзроўні раздзела. Першы выгляд азначае «Панэль», калі ўключана, інакш «Крыніцы».",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Першы даступны выгляд",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Klein",
|
||||
"COVER_SIZE_MEDIUM": "Mittel",
|
||||
"COVER_SIZE_LARGE": "Groß",
|
||||
"EPG_VIEW_MODE": "Programmführer-Ansicht",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Wählen Sie, wie der Programmführer unter dem Player angezeigt wird: als horizontales Zeitleisten-Band oder als vertikale Tagesliste.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Zeitleiste",
|
||||
"EPG_VIEW_MODE_LIST": "Liste",
|
||||
"STARTUP_BEHAVIOR": "Startansicht",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Legen Sie fest, ob IPTVnator die erste verfügbare Workspace-Ansicht oder Ihre zuletzt geöffnete Abschnittsansicht startet. Erste Ansicht bedeutet Dashboard, wenn aktiviert, sonst Quellen.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Erste verfügbare Ansicht",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Μικρό",
|
||||
"COVER_SIZE_MEDIUM": "Μεσαίο",
|
||||
"COVER_SIZE_LARGE": "Μεγάλο",
|
||||
"EPG_VIEW_MODE": "Προβολή οδηγού",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Επιλέξτε πώς εμφανίζεται ο οδηγός προγράμματος κάτω από το πρόγραμμα αναπαραγωγής: ως οριζόντια λωρίδα χρονολογίου ή ως κατακόρυφη λίστα μίας ημέρας.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Χρονολόγιο",
|
||||
"EPG_VIEW_MODE_LIST": "Λίστα",
|
||||
"STARTUP_BEHAVIOR": "Προβολή εκκίνησης",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Επιλέξτε αν το IPTVnator ανοίγει την πρώτη διαθέσιμη προβολή του χώρου εργασίας ή επαναφέρει την τελευταία προβολή σας. Πρώτη προβολή σημαίνει ο πίνακας ελέγχου, όταν είναι ενεργοποιημένος, διαφορετικά οι Πηγές.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Πρώτη διαθέσιμη προβολή",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Small",
|
||||
"COVER_SIZE_MEDIUM": "Medium",
|
||||
"COVER_SIZE_LARGE": "Large",
|
||||
"EPG_VIEW_MODE": "Guide view",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Choose how the programme guide under the player is shown: a horizontal timeline ribbon or a vertical, single-day list.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Timeline",
|
||||
"EPG_VIEW_MODE_LIST": "List",
|
||||
"STARTUP_BEHAVIOR": "Startup view",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Choose whether IPTVnator opens the first available workspace view or restores your last section-level view. First view means Dashboard when enabled, otherwise Sources.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "First available view",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Pequeño",
|
||||
"COVER_SIZE_MEDIUM": "Mediano",
|
||||
"COVER_SIZE_LARGE": "Grande",
|
||||
"EPG_VIEW_MODE": "Vista de la guía",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Elige cómo se muestra la guía de programas bajo el reproductor: una cinta de cronología horizontal o una lista vertical de un solo día.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Cronología",
|
||||
"EPG_VIEW_MODE_LIST": "Lista",
|
||||
"STARTUP_BEHAVIOR": "Vista al iniciar",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Elige si IPTVnator abre la primera vista disponible del espacio de trabajo o restaura la última vista a nivel de sección. Primera vista significa Panel cuando está activado, de lo contrario Fuentes.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Primera vista disponible",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Petite",
|
||||
"COVER_SIZE_MEDIUM": "Moyenne",
|
||||
"COVER_SIZE_LARGE": "Grande",
|
||||
"EPG_VIEW_MODE": "Vue du guide",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Choisissez comment le guide des programmes sous le lecteur est affiché : une frise chronologique horizontale ou une liste verticale d'une seule journée.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Chronologie",
|
||||
"EPG_VIEW_MODE_LIST": "Liste",
|
||||
"STARTUP_BEHAVIOR": "Vue de démarrage",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Choisissez si IPTVnator ouvre la première vue disponible de l'espace de travail ou restaure votre dernière vue de section. Première vue signifie Tableau de bord lorsqu'il est activé, sinon Sources.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Première vue disponible",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Piccola",
|
||||
"COVER_SIZE_MEDIUM": "Media",
|
||||
"COVER_SIZE_LARGE": "Grande",
|
||||
"EPG_VIEW_MODE": "Vista guida",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Scegli come mostrare la guida programmi sotto il lettore: una fascia con timeline orizzontale o un elenco verticale di un singolo giorno.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Timeline",
|
||||
"EPG_VIEW_MODE_LIST": "Elenco",
|
||||
"STARTUP_BEHAVIOR": "Vista all'avvio",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Scegli se IPTVnator apre la prima vista dell'area di lavoro disponibile o ripristina l'ultima vista a livello di sezione. La prima vista è la dashboard se attiva, altrimenti Sorgenti.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Prima vista disponibile",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "小",
|
||||
"COVER_SIZE_MEDIUM": "中",
|
||||
"COVER_SIZE_LARGE": "大",
|
||||
"EPG_VIEW_MODE": "番組表の表示",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "プレーヤー下部の番組表の表示方法を選択します。横方向のタイムライン形式か、縦方向の1日分のリスト形式かを選べます。",
|
||||
"EPG_VIEW_MODE_TIMELINE": "タイムライン",
|
||||
"EPG_VIEW_MODE_LIST": "リスト",
|
||||
"STARTUP_BEHAVIOR": "起動時の表示",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "IPTVnatorを起動したときに最初に利用可能なワークスペースビューを開くか、最後に表示していたセクションを復元するかを選択します。「最初のビュー」は、ダッシュボードが有効な場合はダッシュボード、無効な場合はソースを意味します。",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "最初に利用可能なビュー",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "작게",
|
||||
"COVER_SIZE_MEDIUM": "중간",
|
||||
"COVER_SIZE_LARGE": "크게",
|
||||
"EPG_VIEW_MODE": "가이드 보기",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "플레이어 아래 프로그램 가이드를 표시하는 방식을 선택합니다: 가로 타임라인 리본 또는 하루 단위의 세로 목록입니다.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "타임라인",
|
||||
"EPG_VIEW_MODE_LIST": "목록",
|
||||
"STARTUP_BEHAVIOR": "시작 화면",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "IPTVnator를 처음 시작할 때 첫 번째로 사용 가능한 작업 공간 화면을 열지, 마지막으로 본 섹션 화면을 복원할지 선택합니다. 첫 번째 화면은 활성화된 경우 대시보드, 그렇지 않으면 소스를 의미합니다.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "첫 번째로 사용 가능한 화면",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Klein",
|
||||
"COVER_SIZE_MEDIUM": "Gemiddeld",
|
||||
"COVER_SIZE_LARGE": "Groot",
|
||||
"EPG_VIEW_MODE": "Gidsweergave",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Kies hoe de programmagids onder de speler wordt getoond: een horizontale tijdlijn of een verticale lijst voor één dag.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Tijdlijn",
|
||||
"EPG_VIEW_MODE_LIST": "Lijst",
|
||||
"STARTUP_BEHAVIOR": "Opstartweergave",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Kies of IPTVnator de eerste beschikbare werkruimteweergave opent of je laatste sectieweergave herstelt. Eerste weergave betekent het dashboard wanneer ingeschakeld, anders Bronnen.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Eerste beschikbare weergave",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Mały",
|
||||
"COVER_SIZE_MEDIUM": "Średni",
|
||||
"COVER_SIZE_LARGE": "Duży",
|
||||
"EPG_VIEW_MODE": "Widok przewodnika",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Wybierz sposób wyświetlania przewodnika po programach pod odtwarzaczem: pozioma oś czasu lub pionowa lista dla jednego dnia.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Oś czasu",
|
||||
"EPG_VIEW_MODE_LIST": "Lista",
|
||||
"STARTUP_BEHAVIOR": "Widok startowy",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Wybierz, czy IPTVnator otwiera pierwszy dostępny widok obszaru roboczego, czy przywraca ostatni widok na poziomie sekcji. Pierwszy widok oznacza pulpit, jeśli jest włączony, w przeciwnym razie Źródła.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Pierwszy dostępny widok",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Pequeno",
|
||||
"COVER_SIZE_MEDIUM": "Médio",
|
||||
"COVER_SIZE_LARGE": "Grande",
|
||||
"EPG_VIEW_MODE": "Visualização do guia",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Escolha como o guia de programação abaixo do reprodutor é exibido: uma faixa horizontal em linha do tempo ou uma lista vertical de um único dia.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Linha do tempo",
|
||||
"EPG_VIEW_MODE_LIST": "Lista",
|
||||
"STARTUP_BEHAVIOR": "Visualização inicial",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Escolha se o IPTVnator abre a primeira visualização disponível do espaço de trabalho ou restaura sua última visualização. Primeira visualização significa Dashboard quando ativada, caso contrário, Fontes.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Primeira visualização disponível",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Маленький",
|
||||
"COVER_SIZE_MEDIUM": "Средний",
|
||||
"COVER_SIZE_LARGE": "Большой",
|
||||
"EPG_VIEW_MODE": "Вид программы передач",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Выберите, как отображается программа передач под плеером: горизонтальная лента-таймлайн или вертикальный список на один день.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Таймлайн",
|
||||
"EPG_VIEW_MODE_LIST": "Список",
|
||||
"STARTUP_BEHAVIOR": "Начальный экран",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "Выберите, должен ли IPTVnator открывать первый доступный экран рабочего пространства или восстанавливать последний экран уровня раздела. Первый экран означает Dashboard, если он включён, иначе Источники.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "Первый доступный экран",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "Küçük",
|
||||
"COVER_SIZE_MEDIUM": "Orta",
|
||||
"COVER_SIZE_LARGE": "Büyük",
|
||||
"EPG_VIEW_MODE": "Rehber görünümü",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "Oynatıcının altındaki program rehberinin nasıl gösterileceğini seçin: yatay bir zaman çizelgesi şeridi veya dikey, tek günlük bir liste.",
|
||||
"EPG_VIEW_MODE_TIMELINE": "Zaman çizelgesi",
|
||||
"EPG_VIEW_MODE_LIST": "Liste",
|
||||
"STARTUP_BEHAVIOR": "Başlangıç görünümü",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "IPTVnator'un ilk kullanılabilir çalışma alanı görünümünü mü açacağını yoksa bölüm düzeyindeki son görünümünüzü mü geri yükleyeceğini seçin. İlk görünüm, Pano etkinleştirildiğinde Pano, aksi takdirde Kaynaklar anlamına gelir.",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "İlk kullanılabilir görünüm",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "小",
|
||||
"COVER_SIZE_MEDIUM": "中",
|
||||
"COVER_SIZE_LARGE": "大",
|
||||
"EPG_VIEW_MODE": "节目指南视图",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "选择播放器下方的节目指南如何显示:横向时间轴条,或纵向的单日列表。",
|
||||
"EPG_VIEW_MODE_TIMELINE": "时间轴",
|
||||
"EPG_VIEW_MODE_LIST": "列表",
|
||||
"STARTUP_BEHAVIOR": "启动视图",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "选择 IPTVnator 启动时是打开第一个可用的工作区视图,还是恢复您上次的章节级视图。第一个视图在启用时为仪表盘,否则为「源」。",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "首个可用视图",
|
||||
|
||||
@@ -292,6 +292,10 @@
|
||||
"COVER_SIZE_SMALL": "小",
|
||||
"COVER_SIZE_MEDIUM": "中",
|
||||
"COVER_SIZE_LARGE": "大",
|
||||
"EPG_VIEW_MODE": "節目表檢視方式",
|
||||
"EPG_VIEW_MODE_DESCRIPTION": "選擇播放器下方節目表的顯示方式:橫向時間軸條,或縱向的單日清單。",
|
||||
"EPG_VIEW_MODE_TIMELINE": "時間軸",
|
||||
"EPG_VIEW_MODE_LIST": "清單",
|
||||
"STARTUP_BEHAVIOR": "啟動畫面",
|
||||
"STARTUP_BEHAVIOR_DESCRIPTION": "選擇 IPTVnator 啟動時要開啟第一個可用的工作區畫面,或還原您上次瀏覽的區段層級畫面。第一個畫面在啟用儀表板時為儀表板,否則為「來源」。",
|
||||
"STARTUP_BEHAVIOR_FIRST_VIEW": "第一個可用畫面",
|
||||
|
||||
Reference in new issue
Block a user