mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-17 06:36:15 -08:00
fix(ui): keep dialog action rows on one line (#1762)
* fix(ui): keep dialog action rows on one line Settings "Unsaved changes" dialog: - Cancel / Discard / Save replace the phrase labels in all 19 locales, and the dismiss now comes first; the shared CANCEL key replaces the unused UNSAVED_DIALOG_STAY. - At the 640px phone breakpoint the actions stack one per row, full width, in DOM order. EPG programme dialog: - mat-dialog-title gives the dialog an accessible name. - The footer Close is the only dismiss; it comes first and the primary action last. - The archive copy/download tools move under their notice, so the footer stays on one row. - Channel rows now open it through EpgProgrammeDialogService, which owns the 540px config and a panel class scoping the surface overrides. Adds a web-e2e layout spec (en, de, ru, fr, hu, ar on one row; de and ru stacked on a phone), extends the Electron EPG spec to all three openers, and documents the dialog contract in the UI guidelines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(epg): stack programme dialog actions on phones Below the 640px phone breakpoint the viewport caps the programme dialog, and a long translated primary label ("Regarder depuis le début") no longer fit beside Close. The footer had no wrap, and the dialog hides overflow, so the label was clipped. - At the phone breakpoint, the archive tools and the footer now stack one full-width button per row, in DOM order. - Buttons grow to fit their label, so a long label wraps inside its button instead of being clipped. - On desktop the footer can wrap again as a last resort. The new Electron test opens a past programme in French at a 360px viewport and measures both rows. It fails against the previous stylesheet (the footer buttons are 44px narrower than the row). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d4118ad442
commit
cb317bad4c
31 files changed
+853
-187
No files matched your search
+35
-8
@@ -2,18 +2,18 @@ import { DatePipe } from '@angular/common';
|
||||
import { signal } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SettingsStore } from '@iptvnator/services';
|
||||
import { EpgProgram } from '@iptvnator/shared/interfaces';
|
||||
import { EpgProgrammeDialogService } from '@iptvnator/ui/epg';
|
||||
import { ChannelListItemComponent } from './channel-list-item.component';
|
||||
|
||||
describe('ChannelListItemComponent', () => {
|
||||
let fixture: ComponentFixture<ChannelListItemComponent>;
|
||||
let dialog: { open: jest.Mock };
|
||||
let programmeDialog: { open: jest.Mock };
|
||||
|
||||
beforeEach(async () => {
|
||||
dialog = {
|
||||
programmeDialog = {
|
||||
open: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -25,8 +25,8 @@ describe('ChannelListItemComponent', () => {
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: MatDialog,
|
||||
useValue: dialog,
|
||||
provide: EpgProgrammeDialogService,
|
||||
useValue: programmeDialog,
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
@@ -140,6 +140,27 @@ describe('ChannelListItemComponent', () => {
|
||||
expect(buttons[1]).not.toContain('slot-empty');
|
||||
});
|
||||
|
||||
it('opens the programme through the shared dialog service', () => {
|
||||
const program: EpgProgram = {
|
||||
start: '2026-04-05 05:30:00',
|
||||
stop: '2026-04-05 06:00:00',
|
||||
channel: 'channel-1',
|
||||
title: 'Current Show',
|
||||
desc: 'Current description',
|
||||
category: null,
|
||||
};
|
||||
fixture.componentRef.setInput('name', 'Info Channel');
|
||||
fixture.componentRef.setInput('epgProgram', program);
|
||||
fixture.detectChanges();
|
||||
|
||||
// The service owns the open config, so the row opens the dialog at
|
||||
// the same width as the timeline, list and guide.
|
||||
fixture.nativeElement.querySelector('.program-info-button').click();
|
||||
|
||||
expect(programmeDialog.open).toHaveBeenCalledTimes(1);
|
||||
expect(programmeDialog.open).toHaveBeenCalledWith(program);
|
||||
});
|
||||
|
||||
it('keeps an inert info slot while the row has no programme', () => {
|
||||
fixture.componentRef.setInput('name', 'No EPG Yet');
|
||||
fixture.componentRef.setInput('showFavoriteButton', true);
|
||||
@@ -155,7 +176,7 @@ describe('ChannelListItemComponent', () => {
|
||||
expect(slot.disabled).toBe(true);
|
||||
|
||||
slot.click();
|
||||
expect(dialog.open).not.toHaveBeenCalled();
|
||||
expect(programmeDialog.open).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders the catch-up badge only when catch-up is available', () => {
|
||||
@@ -271,7 +292,10 @@ describe('ChannelListItemComponent', () => {
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
{ provide: MatDialog, useValue: dialog },
|
||||
{
|
||||
provide: EpgProgrammeDialogService,
|
||||
useValue: programmeDialog,
|
||||
},
|
||||
{
|
||||
provide: SettingsStore,
|
||||
useValue: {
|
||||
@@ -307,7 +331,10 @@ describe('ChannelListItemComponent with an EPG display offset', () => {
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
{ provide: MatDialog, useValue: { open: jest.fn() } },
|
||||
{
|
||||
provide: EpgProgrammeDialogService,
|
||||
useValue: { open: jest.fn() },
|
||||
},
|
||||
{
|
||||
provide: SettingsStore,
|
||||
useValue: {
|
||||
|
||||
+3
-9
@@ -11,14 +11,10 @@ import {
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import {
|
||||
EpgItemDescriptionComponent,
|
||||
getProgramTimeMs,
|
||||
} from '@iptvnator/ui/epg';
|
||||
import { EpgProgrammeDialogService, getProgramTimeMs } from '@iptvnator/ui/epg';
|
||||
import { EpgProgram } from '@iptvnator/shared/interfaces';
|
||||
import { SettingsStore } from '@iptvnator/services';
|
||||
import { applyChannelNameStrip } from '@iptvnator/shared/m3u-utils';
|
||||
@@ -39,7 +35,7 @@ import { applyChannelNameStrip } from '@iptvnator/shared/m3u-utils';
|
||||
],
|
||||
})
|
||||
export class ChannelListItemComponent {
|
||||
private readonly dialog = inject(MatDialog);
|
||||
private readonly programmeDialog = inject(EpgProgrammeDialogService);
|
||||
private readonly logoFailed = signal(false);
|
||||
private readonly settingsStore = inject(SettingsStore);
|
||||
readonly epgOffsetMinutes = this.settingsStore.resolvedEpgOffsetMinutes;
|
||||
@@ -99,9 +95,7 @@ export class ChannelListItemComponent {
|
||||
*/
|
||||
showProgramDescription(program: EpgProgram, event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
this.dialog.open(EpgItemDescriptionComponent, {
|
||||
data: program,
|
||||
});
|
||||
this.programmeDialog.open(program);
|
||||
}
|
||||
|
||||
/** Guarded click for the reserved (possibly still empty) info slot. */
|
||||
|
||||
Reference in new issue
Block a user