mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 13:46:16 -08:00
fix(ui): destructive confirmations, verb labels and provider icons
Confirmations: ConfirmDialogData.confirmLabel is required, so no dialog can
fall back to "Yes"/"No"; the dismiss defaults to "Cancel" and
`tone: 'destructive'` styles the confirm with .app-destructive-button. Every
caller names its action ("Remove playlist", "Clear", "Refresh playlist",
"Cancel download" with a "Close" dismiss). The confirm button has the
confirm-dialog-confirm test id and drops its no-op color="primary".
The no-op `warn` color input becomes .app-destructive-button on the EPG
mapping, playlist item, error view, EPG/reset settings, delete-all and source
cleanup buttons, and on the unsaved-changes dialog's Discard.
Provider icons come from SOURCE_TYPE_ICONS in shared/interfaces (Xtream
cloud, Stalker cast, M3U playlist_play / link / description / subject) in the
add dialog, auto-import, empty state, playlist switcher, playlist rows,
dashboard source rail, command palette, Sources filters and both reset
summaries. Stalker no longer borrows the Dashboard icon, and Xtream no longer
shares a glyph with M3U URL playlists.
The playlist error view removed a playlist through the stale
PlaylistActions.removePlaylist: it dropped the playlist from state before the
delete ran, swallowed failures, skipped the source activity guard and showed
no toast. It now uses PlaylistDeleteActionService like every other removal,
commits only a completed delete, toasts and goes home. The unused action and
its effect are removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
572034f3be
commit
082c56608b
50 files changed
+584
-103
No files matched your search
+5
-1
@@ -59,7 +59,11 @@
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
@if (currentMapping()) {
|
||||
<button mat-button color="warn" (click)="removeMapping()">
|
||||
<button
|
||||
mat-button
|
||||
class="app-destructive-button"
|
||||
(click)="removeMapping()"
|
||||
>
|
||||
{{ 'EPG_MAPPING_DIALOG.REMOVE' | translate }}
|
||||
</button>
|
||||
}
|
||||
|
||||
@@ -52,3 +52,49 @@ describe('ConfirmDialogComponent actions', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('ConfirmDialogComponent labels and tone', () => {
|
||||
async function render(data: Record<string, unknown>) {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
ConfirmDialogComponent,
|
||||
NoopAnimationsModule,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: MAT_DIALOG_DATA,
|
||||
useValue: {
|
||||
title: 'Remove playlist',
|
||||
message: 'Delete it?',
|
||||
confirmLabel: 'Remove playlist',
|
||||
onConfirm: jest.fn(),
|
||||
...data,
|
||||
},
|
||||
},
|
||||
{ provide: MatDialogRef, useValue: { close: jest.fn() } },
|
||||
],
|
||||
}).compileComponents();
|
||||
const fixture = TestBed.createComponent(ConfirmDialogComponent);
|
||||
fixture.detectChanges();
|
||||
const buttons = fixture.nativeElement.querySelectorAll(
|
||||
'button'
|
||||
) as NodeListOf<HTMLButtonElement>;
|
||||
return { cancel: buttons[0], confirm: buttons[1] };
|
||||
}
|
||||
|
||||
it('names the action instead of answering "Yes", and cancels by default', async () => {
|
||||
const { cancel, confirm } = await render({});
|
||||
|
||||
expect(confirm.textContent?.trim()).toBe('Remove playlist');
|
||||
// No translations are loaded, so the key is the rendered text.
|
||||
expect(cancel.textContent?.trim()).toBe('CANCEL');
|
||||
expect(confirm.classList).not.toContain('app-destructive-button');
|
||||
});
|
||||
|
||||
it('styles destructive confirmations as destructive', async () => {
|
||||
const { confirm } = await render({ tone: 'destructive' });
|
||||
|
||||
expect(confirm.classList).toContain('app-destructive-button');
|
||||
});
|
||||
});
|
||||
@@ -3,12 +3,25 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* `destructive` renders the confirm action with the app-wide error styling
|
||||
* (`.app-destructive-button`). Use it whenever confirming removes or discards
|
||||
* user data; Material's `warn` color input has no effect with the M3 theme.
|
||||
*/
|
||||
export type ConfirmDialogTone = 'default' | 'destructive';
|
||||
|
||||
export interface ConfirmDialogData {
|
||||
title: string;
|
||||
message: string;
|
||||
width?: string;
|
||||
confirmLabel?: string;
|
||||
/**
|
||||
* Translated verb that names the action ("Remove playlist", "Clear").
|
||||
* Required so a confirmation never falls back to an ambiguous "Yes".
|
||||
*/
|
||||
confirmLabel: string;
|
||||
/** Translated dismiss label; defaults to "Cancel". */
|
||||
cancelLabel?: string;
|
||||
tone?: ConfirmDialogTone;
|
||||
/** Run the action in the dialog; only Close/backdrop/Escape dismiss it. */
|
||||
keepOpenOnConfirm?: boolean;
|
||||
onConfirm: () => void;
|
||||
@@ -27,23 +40,25 @@ export interface ConfirmDialogData {
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button mat-dialog-close cdkFocusInitial>
|
||||
{{ dialogData.cancelLabel || 'NO' | translate }}
|
||||
{{ dialogData.cancelLabel || ('CANCEL' | translate) }}
|
||||
</button>
|
||||
@if (dialogData.keepOpenOnConfirm) {
|
||||
<button
|
||||
mat-flat-button
|
||||
data-test-id="confirm-dialog-confirm"
|
||||
[class.app-destructive-button]="isDestructive"
|
||||
(click)="dialogData.onConfirm()"
|
||||
color="primary"
|
||||
>
|
||||
{{ dialogData.confirmLabel || 'YES' | translate }}
|
||||
{{ dialogData.confirmLabel }}
|
||||
</button>
|
||||
} @else {
|
||||
<button
|
||||
mat-flat-button
|
||||
data-test-id="confirm-dialog-confirm"
|
||||
[class.app-destructive-button]="isDestructive"
|
||||
[mat-dialog-close]="true"
|
||||
color="primary"
|
||||
>
|
||||
{{ dialogData.confirmLabel || 'YES' | translate }}
|
||||
{{ dialogData.confirmLabel }}
|
||||
</button>
|
||||
}
|
||||
</mat-dialog-actions>
|
||||
@@ -56,4 +71,8 @@ export class ConfirmDialogComponent {
|
||||
constructor() {
|
||||
this.dialogData = this.data;
|
||||
}
|
||||
|
||||
get isDestructive(): boolean {
|
||||
return this.dialogData.tone === 'destructive';
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user