mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
style(playlists): merge "add playlist" dialogs to one entrypoint dialog
Entire-Checkpoint: 84693b066142
This commit is contained in:
1 parent
3a66bf21a3
commit
69bf106999
29 files changed
+238
-219
No files matched your search
@@ -1,10 +1,10 @@
|
||||
import { inject, Injectable, Provider } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { PlaylistType } from '@iptvnator/playlist/shared/ui';
|
||||
import {
|
||||
WORKSPACE_SHELL_ACTIONS,
|
||||
WorkspaceAccountInfoData,
|
||||
WorkspacePlaylistType,
|
||||
WorkspaceShellActions,
|
||||
} from '@iptvnator/workspace/shell/util';
|
||||
|
||||
@@ -13,12 +13,12 @@ export class AppWorkspaceShellActionsService implements WorkspaceShellActions {
|
||||
private readonly dialog = inject(MatDialog);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
openAddPlaylistDialog(type: PlaylistType): void {
|
||||
openAddPlaylistDialog(type?: WorkspacePlaylistType): void {
|
||||
void import('@iptvnator/playlist/import/feature').then(
|
||||
({ AddPlaylistDialogComponent }) => {
|
||||
this.dialog.open(AddPlaylistDialogComponent, {
|
||||
width: '600px',
|
||||
data: { type },
|
||||
data: type ? { type } : {},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
+118
-15
@@ -1,20 +1,123 @@
|
||||
<h2 mat-dialog-title>
|
||||
{{ 'HOME.URL_UPLOAD.ADD_PLAYLIST' | translate }}: {{ playlistType }}
|
||||
{{ 'HOME.URL_UPLOAD.ADD_PLAYLIST' | translate }}
|
||||
</h2>
|
||||
<mat-dialog-content class="mat-typography">
|
||||
@if (playlistType === 'file') {
|
||||
<app-file-upload
|
||||
(fileRejected)="rejectFile($event)"
|
||||
(fileSelected)="handlePlaylist($event)"
|
||||
(closeDialog)="closeDialog()"
|
||||
/>
|
||||
} @else if (playlistType === 'url') {
|
||||
<app-url-upload (urlAdded)="sendPlaylistsUrl($event)" />
|
||||
} @else if (playlistType === 'text') {
|
||||
<app-text-import (textAdded)="uploadAsText($event)" />
|
||||
} @else if (playlistType === 'stalker') {
|
||||
<app-stalker-portal-import (addClicked)="closeDialog()" />
|
||||
} @else if (playlistType === 'xtream') {
|
||||
<app-xtream-code-import (addClicked)="closeDialog()" />
|
||||
<mat-button-toggle-group
|
||||
[value]="category()"
|
||||
(change)="category.set($event.value)"
|
||||
class="category-toggle"
|
||||
hideSingleSelectionIndicator
|
||||
>
|
||||
<mat-button-toggle value="m3u">M3U</mat-button-toggle>
|
||||
<mat-button-toggle value="xtream">Xtream</mat-button-toggle>
|
||||
<mat-button-toggle value="stalker">Stalker</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
|
||||
@if (category() === 'm3u') {
|
||||
<mat-button-toggle-group
|
||||
[value]="m3uSubType()"
|
||||
(change)="m3uSubType.set($event.value)"
|
||||
class="subtype-toggle"
|
||||
hideSingleSelectionIndicator
|
||||
>
|
||||
<mat-button-toggle value="url">
|
||||
{{ 'HOME.TABS.URL_UPLOAD' | translate }}
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle value="file">
|
||||
{{ 'HOME.TABS.FILE_UPLOAD' | translate }}
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle value="text">
|
||||
{{ 'HOME.TABS.TEXT_IMPORT' | translate }}
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
}
|
||||
|
||||
<div class="form-area">
|
||||
@switch (playlistType()) {
|
||||
@case ('file') {
|
||||
<app-file-upload
|
||||
(fileRejected)="rejectFile($event)"
|
||||
(fileSelected)="handlePlaylist($event)"
|
||||
(closeDialog)="closeDialog()"
|
||||
/>
|
||||
}
|
||||
@case ('url') {
|
||||
<app-url-upload (urlAdded)="sendPlaylistsUrl($event)" />
|
||||
}
|
||||
@case ('text') {
|
||||
<app-text-import (textAdded)="uploadAsText($event)" />
|
||||
}
|
||||
@case ('stalker') {
|
||||
<app-stalker-portal-import (addClicked)="closeDialog()" />
|
||||
}
|
||||
@case ('xtream') {
|
||||
<app-xtream-code-import (addClicked)="closeDialog()" />
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions align="end">
|
||||
@switch (playlistType()) {
|
||||
@case ('url') {
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
[disabled]="!urlUpload()?.form?.valid"
|
||||
(click)="urlUpload()?.urlAdded?.emit(urlUpload()?.form?.value?.playlistUrl)"
|
||||
>
|
||||
{{ 'HOME.URL_UPLOAD.ADD_PLAYLIST' | translate }}
|
||||
</button>
|
||||
}
|
||||
@case ('text') {
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
[disabled]="!textImport()?.textForm?.valid"
|
||||
(click)="textImport()?.textAdded?.emit(textImport()?.textForm?.value?.text)"
|
||||
>
|
||||
{{ 'HOME.TEXT_IMPORT.BUTTON_LABEL' | translate }}
|
||||
</button>
|
||||
}
|
||||
@case ('xtream') {
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
[disabled]="!xtreamImport()?.form?.valid || xtreamImport()?.isTestingConnection"
|
||||
(click)="xtreamImport()?.testConnection()"
|
||||
>
|
||||
{{ xtreamImport()?.isTestingConnection ? 'Testing...' : 'Test Connection' }}
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
(click)="xtreamImport()?.clearForm()"
|
||||
>
|
||||
{{ 'CLEAR' | translate }}
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
[disabled]="!xtreamImport()?.form?.valid"
|
||||
(click)="xtreamImport()?.addPlaylist()"
|
||||
>
|
||||
{{ 'HOME.XTREAM_PLAYLIST.ADD' | translate }}
|
||||
</button>
|
||||
}
|
||||
@case ('stalker') {
|
||||
@if (stalkerImport()?.isLoading()) {
|
||||
<button mat-button type="button" disabled>
|
||||
{{ 'HOME.STALKER_PORTAL.VALIDATING' | translate }}...
|
||||
</button>
|
||||
} @else {
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
[disabled]="!stalkerImport()?.form?.valid"
|
||||
(click)="stalkerImport()?.addPlaylist()"
|
||||
>
|
||||
{{ 'HOME.STALKER_PORTAL.ADD' | translate }}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
</mat-dialog-actions>
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
// Compact density themes scoped to this dialog only
|
||||
// Scale -1 for form fields: 52px height, labels still visible
|
||||
$form-field-theme: mat.define-theme((
|
||||
density: (scale: -1),
|
||||
));
|
||||
|
||||
// Scale -3 for button toggles: 36px height
|
||||
$toggle-theme: mat.define-theme((
|
||||
density: (scale: -3),
|
||||
));
|
||||
|
||||
:host {
|
||||
@include mat.form-field-density($form-field-theme);
|
||||
@include mat.button-toggle-density($toggle-theme);
|
||||
}
|
||||
|
||||
// ─── Dialog title ────────────────────────────────────────────────────────────
|
||||
h2[mat-dialog-title] {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
// ─── Toggle groups ───────────────────────────────────────────────────────────
|
||||
.category-toggle,
|
||||
.subtype-toggle {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
|
||||
mat-button-toggle {
|
||||
flex: 1;
|
||||
--mat-standard-button-toggle-label-text-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.subtype-toggle mat-button-toggle {
|
||||
--mat-standard-button-toggle-label-text-size: 12px;
|
||||
}
|
||||
|
||||
// ─── Form area — stable height across all views ─────────────────────────────
|
||||
.form-area {
|
||||
min-height: 0;
|
||||
}
|
||||
+39
-4
@@ -1,5 +1,6 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Component, computed, inject, signal, viewChild } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogModule,
|
||||
@@ -9,6 +10,10 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { PlaylistType } from '@iptvnator/playlist/shared/ui';
|
||||
import {
|
||||
M3uSubType,
|
||||
PlaylistCategory,
|
||||
} from '@iptvnator/workspace/shell/util';
|
||||
import { PlaylistActions } from 'm3u-state';
|
||||
import { getFilenameFromUrl } from 'm3u-utils';
|
||||
import { DataService } from 'services';
|
||||
@@ -23,6 +28,7 @@ import { XtreamCodeImportComponent } from '../xtream-code-import/xtream-code-imp
|
||||
imports: [
|
||||
FileUploadComponent,
|
||||
MatButtonModule,
|
||||
MatButtonToggleModule,
|
||||
MatDialogModule,
|
||||
StalkerPortalImportComponent,
|
||||
TextImportComponent,
|
||||
@@ -32,6 +38,7 @@ import { XtreamCodeImportComponent } from '../xtream-code-import/xtream-code-imp
|
||||
],
|
||||
selector: 'app-add-playlist',
|
||||
templateUrl: './add-playlist-dialog.component.html',
|
||||
styleUrl: './add-playlist-dialog.component.scss',
|
||||
})
|
||||
export class AddPlaylistDialogComponent {
|
||||
private dataService = inject(DataService);
|
||||
@@ -39,12 +46,40 @@ export class AddPlaylistDialogComponent {
|
||||
private store = inject(Store);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
private translateService = inject(TranslateService);
|
||||
readonly data = inject<{ type: PlaylistType }>(MAT_DIALOG_DATA);
|
||||
private data = inject<{ type?: PlaylistType } | null>(MAT_DIALOG_DATA, {
|
||||
optional: true,
|
||||
});
|
||||
|
||||
readonly playlistType!: PlaylistType;
|
||||
readonly urlUpload = viewChild(UrlUploadComponent);
|
||||
readonly textImport = viewChild(TextImportComponent);
|
||||
readonly xtreamImport = viewChild(XtreamCodeImportComponent);
|
||||
readonly stalkerImport = viewChild(StalkerPortalImportComponent);
|
||||
|
||||
readonly category = signal<PlaylistCategory>('m3u');
|
||||
readonly m3uSubType = signal<M3uSubType>('url');
|
||||
|
||||
readonly playlistType = computed<PlaylistType>(() => {
|
||||
const cat = this.category();
|
||||
if (cat === 'xtream') return 'xtream';
|
||||
if (cat === 'stalker') return 'stalker';
|
||||
return this.m3uSubType();
|
||||
});
|
||||
|
||||
constructor() {
|
||||
this.playlistType = this.data.type;
|
||||
if (this.data?.type) {
|
||||
this.initFromType(this.data.type);
|
||||
}
|
||||
}
|
||||
|
||||
private initFromType(type: PlaylistType): void {
|
||||
if (type === 'xtream') {
|
||||
this.category.set('xtream');
|
||||
} else if (type === 'stalker') {
|
||||
this.category.set('stalker');
|
||||
} else {
|
||||
this.category.set('m3u');
|
||||
this.m3uSubType.set(type as M3uSubType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-15
@@ -118,19 +118,4 @@
|
||||
}}</mat-label>
|
||||
<input matInput type="text" id="password" formControlName="password" />
|
||||
</mat-form-field> -->
|
||||
@if (isLoading()) {
|
||||
<div class="loading-container">
|
||||
<mat-spinner diameter="20"></mat-spinner>
|
||||
<span>{{ 'HOME.STALKER_PORTAL.VALIDATING' | translate }}</span>
|
||||
</div>
|
||||
} @else {
|
||||
<button
|
||||
mat-button
|
||||
type="submit"
|
||||
[disabled]="!form.valid"
|
||||
(click)="addPlaylist()"
|
||||
>
|
||||
{{ 'HOME.STALKER_PORTAL.ADD' | translate }}
|
||||
</button>
|
||||
}
|
||||
</form>
|
||||
-4
@@ -6,10 +6,8 @@ import {
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
|
||||
@@ -24,10 +22,8 @@ import { v4 as uuid } from 'uuid';
|
||||
@Component({
|
||||
imports: [
|
||||
FormsModule,
|
||||
MatButtonModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
ReactiveFormsModule,
|
||||
TranslatePipe,
|
||||
],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<form (ngSubmit)="textAdded.emit(textForm.value.text)" [formGroup]="textForm">
|
||||
<form [formGroup]="textForm">
|
||||
<mat-form-field class="w-full" appearance="fill">
|
||||
<mat-label>{{ 'HOME.TEXT_IMPORT.LABEL' | translate }}</mat-label>
|
||||
<textarea
|
||||
@@ -7,12 +7,4 @@
|
||||
placeholder="#EXTM3U url-tvg=..."
|
||||
></textarea>
|
||||
</mat-form-field>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
[disabled]="!textForm.valid"
|
||||
>
|
||||
{{ 'HOME.TEXT_IMPORT.BUTTON_LABEL' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
|
||||
@@ -13,7 +12,7 @@ import { TranslatePipe } from '@ngx-translate/core';
|
||||
selector: 'app-text-import',
|
||||
templateUrl: './text-import.component.html',
|
||||
styleUrls: ['./text-import.component.scss'],
|
||||
imports: [MatButton, MatInputModule, ReactiveFormsModule, TranslatePipe],
|
||||
imports: [MatInputModule, ReactiveFormsModule, TranslatePipe],
|
||||
})
|
||||
export class TextImportComponent {
|
||||
@Output() textAdded = new EventEmitter<string>();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<form (ngSubmit)="urlAdded.emit(form.value.playlistUrl)" [formGroup]="form">
|
||||
<form [formGroup]="form">
|
||||
<mat-form-field class="w-full">
|
||||
<input
|
||||
type="url"
|
||||
@@ -12,13 +12,4 @@
|
||||
{{ 'HOME.URL_UPLOAD.CORS_NOTE' | translate }}
|
||||
</p>
|
||||
}
|
||||
<button
|
||||
class="add-button"
|
||||
mat-flat-button
|
||||
[disabled]="!form.valid"
|
||||
color="primary"
|
||||
type="submit"
|
||||
>
|
||||
{{ 'HOME.URL_UPLOAD.ADD_PLAYLIST' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
@@ -5,14 +5,13 @@ import {
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-url-upload',
|
||||
templateUrl: './url-upload.component.html',
|
||||
imports: [MatButton, MatInputModule, ReactiveFormsModule, TranslatePipe],
|
||||
imports: [MatInputModule, ReactiveFormsModule, TranslatePipe],
|
||||
})
|
||||
export class UrlUploadComponent implements OnInit {
|
||||
private readonly fb = inject(FormBuilder);
|
||||
|
||||
-28
@@ -34,34 +34,6 @@
|
||||
<input matInput type="text" id="password" formControlName="password" />
|
||||
</mat-form-field>
|
||||
|
||||
<div class="button-row">
|
||||
<div>
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
[disabled]="!form.valid || isTestingConnection"
|
||||
(click)="testConnection()"
|
||||
>
|
||||
{{ isTestingConnection ? 'Testing...' : 'Test Connection' }}
|
||||
</button>
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
(click)="clearForm()"
|
||||
>
|
||||
{{ 'CLEAR' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
mat-button
|
||||
type="submit"
|
||||
[disabled]="!form.valid"
|
||||
(click)="addPlaylist()"
|
||||
>
|
||||
{{ 'HOME.XTREAM_PLAYLIST.ADD' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (connectionStatus) {
|
||||
<div class="connection-status">
|
||||
<mat-icon [class]="getStatusClass()">
|
||||
|
||||
-6
@@ -6,7 +6,6 @@ import {
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
@@ -20,7 +19,6 @@ import { v4 as uuid } from 'uuid';
|
||||
@Component({
|
||||
imports: [
|
||||
FormsModule,
|
||||
MatButton,
|
||||
MatFormFieldModule,
|
||||
MatIcon,
|
||||
MatInputModule,
|
||||
@@ -64,10 +62,6 @@ import { v4 as uuid } from 'uuid';
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './lib/add-playlist-menu/add-playlist-menu.component';
|
||||
export * from './lib/add-playlist-menu/playlist-type';
|
||||
export * from './lib/filter-sort-menu/filter-sort-menu.component';
|
||||
export * from './lib/playlist-switcher/playlist-switcher.component';
|
||||
export * from './lib/recent-playlists/playlist-info/playlist-info.component';
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<mat-menu #addPlaylistMenu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
[attr.aria-label]="'HOME.TABS.FILE_UPLOAD' | translate"
|
||||
(click)="onPlaylistTypeClick('file')"
|
||||
>
|
||||
<mat-icon>attach_file</mat-icon>
|
||||
<span>{{ 'HOME.TABS.FILE_UPLOAD' | translate }}</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[attr.aria-label]="'HOME.TABS.URL_UPLOAD' | translate"
|
||||
(click)="onPlaylistTypeClick('url')"
|
||||
>
|
||||
<mat-icon>link</mat-icon>
|
||||
<span>{{ 'HOME.TABS.URL_UPLOAD' | translate }}</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[attr.aria-label]="'HOME.TABS.TEXT_IMPORT' | translate"
|
||||
(click)="onPlaylistTypeClick('text')"
|
||||
>
|
||||
<mat-icon>text_format</mat-icon>
|
||||
<span>{{ 'HOME.TABS.TEXT_IMPORT' | translate }}</span>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
<button
|
||||
mat-menu-item
|
||||
[attr.aria-label]="'HOME.TABS.XTREME_IMPORT' | translate"
|
||||
(click)="onPlaylistTypeClick('xtream')"
|
||||
>
|
||||
<mat-icon>video_library</mat-icon>
|
||||
<span>{{ 'HOME.TABS.XTREME_IMPORT' | translate }}</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[attr.aria-label]="'HOME.TABS.STALKER_PORTAL_IMPORT' | translate"
|
||||
(click)="onPlaylistTypeClick('stalker')"
|
||||
>
|
||||
<mat-icon>dashboard</mat-icon>
|
||||
<span>{{ 'HOME.TABS.STALKER_PORTAL_IMPORT' | translate }}</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Component, output, viewChild } from '@angular/core';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenu, MatMenuModule } from '@angular/material/menu';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
export type PlaylistType = 'xtream' | 'url' | 'text' | 'file' | 'stalker';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-playlist-menu',
|
||||
templateUrl: './add-playlist-menu.component.html',
|
||||
imports: [MatDividerModule, MatIconModule, MatMenuModule, TranslateModule],
|
||||
})
|
||||
export class AddPlaylistMenuComponent {
|
||||
readonly menu = viewChild.required<MatMenu>('addPlaylistMenu');
|
||||
|
||||
readonly playlistTypeSelected = output<PlaylistType>();
|
||||
|
||||
onPlaylistTypeClick(type: PlaylistType): void {
|
||||
this.playlistTypeSelected.emit(type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export type PlaylistType = 'xtream' | 'url' | 'text' | 'file' | 'stalker';
|
||||
+1
-4
@@ -38,14 +38,11 @@
|
||||
<button
|
||||
mat-raised-button
|
||||
class="primary-button"
|
||||
[matMenuTriggerFor]="addPlaylistMenuComponent().menu()"
|
||||
(click)="onAddPlaylist()"
|
||||
>
|
||||
<mat-icon>add_circle</mat-icon>
|
||||
{{ 'HOME.PLAYLISTS.ADD_YOUR_FIRST' | translate }}
|
||||
</button>
|
||||
<app-add-playlist-menu
|
||||
(playlistTypeSelected)="onAddPlaylist($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="features">
|
||||
|
||||
+5
-8
@@ -1,9 +1,7 @@
|
||||
import { Component, input, output, viewChild } from '@angular/core';
|
||||
import { Component, input, output } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { AddPlaylistMenuComponent, PlaylistType } from '../../add-playlist-menu/add-playlist-menu.component';
|
||||
|
||||
export type EmptyStateType = 'welcome' | 'no-results';
|
||||
|
||||
@@ -11,14 +9,13 @@ export type EmptyStateType = 'welcome' | 'no-results';
|
||||
selector: 'app-empty-state',
|
||||
templateUrl: './empty-state.component.html',
|
||||
styleUrls: ['./empty-state.component.scss'],
|
||||
imports: [AddPlaylistMenuComponent, MatButtonModule, MatIcon, MatMenuModule, TranslatePipe],
|
||||
imports: [MatButtonModule, MatIcon, TranslatePipe],
|
||||
})
|
||||
export class EmptyStateComponent {
|
||||
readonly addPlaylistMenuComponent = viewChild.required(AddPlaylistMenuComponent);
|
||||
readonly type = input.required<EmptyStateType>();
|
||||
readonly addPlaylistClicked = output<PlaylistType>();
|
||||
readonly addPlaylistClicked = output<void>();
|
||||
|
||||
onAddPlaylist(playlistType: PlaylistType): void {
|
||||
this.addPlaylistClicked.emit(playlistType);
|
||||
onAddPlaylist(): void {
|
||||
this.addPlaylistClicked.emit();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<!-- Welcome state: no playlists at all -->
|
||||
<app-empty-state
|
||||
[type]="'welcome'"
|
||||
(addPlaylistClicked)="onAddPlaylist($event)"
|
||||
(addPlaylistClicked)="onAddPlaylist()"
|
||||
/>
|
||||
} @else {
|
||||
<!-- No results state: search/filter returned nothing -->
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
SortService,
|
||||
} from 'services';
|
||||
import { PLAYLIST_UPDATE, PlaylistMeta } from 'shared-interfaces';
|
||||
import { PlaylistType } from '../add-playlist-menu/add-playlist-menu.component';
|
||||
|
||||
import { EmptyStateComponent } from './empty-state/empty-state.component';
|
||||
import { PlaylistInfoComponent } from './playlist-info/playlist-info.component';
|
||||
import { PlaylistItemComponent } from './playlist-item/playlist-item.component';
|
||||
@@ -71,7 +71,7 @@ export class RecentPlaylistsComponent {
|
||||
readonly sidebarMode = input(false);
|
||||
readonly searchQueryInput = input<string>('');
|
||||
readonly playlistClicked = output<string>();
|
||||
readonly addPlaylistClicked = output<PlaylistType>();
|
||||
readonly addPlaylistClicked = output<void>();
|
||||
|
||||
readonly allPlaylistsLoaded = this.store.selectSignal(
|
||||
selectPlaylistsLoadingFlag
|
||||
@@ -181,8 +181,8 @@ export class RecentPlaylistsComponent {
|
||||
);
|
||||
}
|
||||
|
||||
onAddPlaylist(playlistType: PlaylistType) {
|
||||
this.addPlaylistClicked.emit(playlistType);
|
||||
onAddPlaylist() {
|
||||
this.addPlaylistClicked.emit();
|
||||
}
|
||||
|
||||
getPlaylist(playlistMeta: PlaylistMeta): void {
|
||||
|
||||
+1
-4
@@ -51,15 +51,12 @@
|
||||
<button
|
||||
type="button"
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="addPlaylistMenu().menu()"
|
||||
(click)="onAddPlaylistRequested()"
|
||||
[matTooltip]="'WORKSPACE.SHELL.ADD_PLAYLIST_ALL' | translate"
|
||||
[attr.aria-label]="'WORKSPACE.SHELL.ADD_PLAYLIST' | translate"
|
||||
>
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
<app-add-playlist-menu
|
||||
(playlistTypeSelected)="onAddPlaylistRequested($event)"
|
||||
/>
|
||||
<span class="header-actions-divider" aria-hidden="true"></span>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
-4
@@ -7,12 +7,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { of } from 'rxjs';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { AddPlaylistMenuComponent } from '@iptvnator/playlist/shared/ui';
|
||||
import { WorkspaceShellHeaderComponent } from './workspace-shell-header.component';
|
||||
|
||||
@Component({
|
||||
@@ -55,10 +53,8 @@ describe('WorkspaceShellHeaderComponent', () => {
|
||||
.overrideComponent(WorkspaceShellHeaderComponent, {
|
||||
set: {
|
||||
imports: [
|
||||
AddPlaylistMenuComponent,
|
||||
MatIcon,
|
||||
MatIconButton,
|
||||
MatMenuModule,
|
||||
MatTooltip,
|
||||
MockPlaylistSwitcherComponent,
|
||||
TranslatePipe,
|
||||
|
||||
+4
-14
@@ -3,27 +3,19 @@ import {
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import {
|
||||
AddPlaylistMenuComponent,
|
||||
PlaylistSwitcherComponent,
|
||||
PlaylistType,
|
||||
} from '@iptvnator/playlist/shared/ui';
|
||||
import { PlaylistSwitcherComponent } from '@iptvnator/playlist/shared/ui';
|
||||
import { WorkspaceHeaderAction } from '@iptvnator/portal/shared/util';
|
||||
|
||||
@Component({
|
||||
selector: 'app-workspace-shell-header',
|
||||
imports: [
|
||||
AddPlaylistMenuComponent,
|
||||
MatIcon,
|
||||
MatIconButton,
|
||||
MatMenuModule,
|
||||
MatTooltip,
|
||||
PlaylistSwitcherComponent,
|
||||
TranslatePipe,
|
||||
@@ -33,8 +25,6 @@ import { WorkspaceHeaderAction } from '@iptvnator/portal/shared/util';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class WorkspaceShellHeaderComponent {
|
||||
readonly addPlaylistMenu = viewChild.required(AddPlaylistMenuComponent);
|
||||
|
||||
readonly playlistTitle = input('');
|
||||
readonly playlistSubtitle = input('');
|
||||
readonly canOpenPlaylistInfo = input(false);
|
||||
@@ -52,7 +42,7 @@ export class WorkspaceShellHeaderComponent {
|
||||
readonly searchChanged = output<string>();
|
||||
readonly searchSubmitted = output<string>();
|
||||
readonly commandPaletteRequested = output<void>();
|
||||
readonly addPlaylistRequested = output<PlaylistType>();
|
||||
readonly addPlaylistRequested = output<void>();
|
||||
readonly globalFavoritesRequested = output<void>();
|
||||
readonly headerShortcutRequested = output<void>();
|
||||
readonly downloadsRequested = output<void>();
|
||||
@@ -77,8 +67,8 @@ export class WorkspaceShellHeaderComponent {
|
||||
this.accountInfoRequested.emit();
|
||||
}
|
||||
|
||||
onAddPlaylistRequested(type: PlaylistType): void {
|
||||
this.addPlaylistRequested.emit(type);
|
||||
onAddPlaylistRequested(): void {
|
||||
this.addPlaylistRequested.emit();
|
||||
}
|
||||
|
||||
onCommandPaletteRequested(): void {
|
||||
|
||||
+3
-6
@@ -12,10 +12,7 @@ import { NavigationEnd, Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { filter, firstValueFrom, startWith } from 'rxjs';
|
||||
import {
|
||||
PlaylistInfoComponent,
|
||||
PlaylistType,
|
||||
} from '@iptvnator/playlist/shared/ui';
|
||||
import { PlaylistInfoComponent } from '@iptvnator/playlist/shared/ui';
|
||||
import { PlaylistContextFacade } from '@iptvnator/playlist/shared/util';
|
||||
import {
|
||||
buildPortalRailLinks,
|
||||
@@ -669,8 +666,8 @@ export class WorkspaceShellFacade {
|
||||
this.applySearchQuery(trimmedValue);
|
||||
}
|
||||
|
||||
openAddPlaylistDialog(type: PlaylistType): void {
|
||||
this.workspaceActions.openAddPlaylistDialog(type);
|
||||
openAddPlaylistDialog(): void {
|
||||
this.workspaceActions.openAddPlaylistDialog();
|
||||
}
|
||||
|
||||
openCommandPalette(): void {
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
(searchChanged)="facade.onSearchInput($event)"
|
||||
(searchSubmitted)="facade.onSearchEnter($event)"
|
||||
(commandPaletteRequested)="facade.openCommandPalette()"
|
||||
(addPlaylistRequested)="facade.openAddPlaylistDialog($event)"
|
||||
(addPlaylistRequested)="facade.openAddPlaylistDialog()"
|
||||
(globalFavoritesRequested)="facade.navigateToGlobalFavorites()"
|
||||
(headerShortcutRequested)="facade.runHeaderShortcut()"
|
||||
(downloadsRequested)="facade.openDownloadsShortcut()"
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ class MockWorkspaceShellHeaderComponent {
|
||||
readonly searchChanged = output<string>();
|
||||
readonly searchSubmitted = output<string>();
|
||||
readonly commandPaletteRequested = output<void>();
|
||||
readonly addPlaylistRequested = output<string>();
|
||||
readonly addPlaylistRequested = output<void>();
|
||||
readonly globalFavoritesRequested = output<void>();
|
||||
readonly headerShortcutRequested = output<void>();
|
||||
readonly downloadsRequested = output<void>();
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<div class="sources-content">
|
||||
<app-recent-playlists
|
||||
[searchQueryInput]="searchQuery()"
|
||||
(addPlaylistClicked)="onAddPlaylist($event)"
|
||||
(addPlaylistClicked)="onAddPlaylist()"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
+3
-6
@@ -2,10 +2,7 @@ import { Component, computed, inject } from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import {
|
||||
PlaylistType,
|
||||
RecentPlaylistsComponent,
|
||||
} from '@iptvnator/playlist/shared/ui';
|
||||
import { RecentPlaylistsComponent } from '@iptvnator/playlist/shared/ui';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { selectActiveTypeFilters, selectAllPlaylistsMeta } from 'm3u-state';
|
||||
import { map, startWith } from 'rxjs';
|
||||
@@ -100,8 +97,8 @@ export class WorkspaceSourcesComponent {
|
||||
});
|
||||
});
|
||||
|
||||
onAddPlaylist(playlistType: PlaylistType): void {
|
||||
this.workspaceActions.openAddPlaylistDialog(playlistType);
|
||||
onAddPlaylist(): void {
|
||||
this.workspaceActions.openAddPlaylistDialog();
|
||||
}
|
||||
|
||||
private translateText(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
export type WorkspacePlaylistType = 'xtream' | 'url' | 'text' | 'file' | 'stalker';
|
||||
export type PlaylistCategory = 'm3u' | 'xtream' | 'stalker';
|
||||
export type M3uSubType = 'url' | 'file' | 'text';
|
||||
|
||||
export interface WorkspaceAccountInfoData {
|
||||
vodStreamsCount: number;
|
||||
@@ -9,7 +11,7 @@ export interface WorkspaceAccountInfoData {
|
||||
}
|
||||
|
||||
export interface WorkspaceShellActions {
|
||||
openAddPlaylistDialog(type: WorkspacePlaylistType): void;
|
||||
openAddPlaylistDialog(type?: WorkspacePlaylistType): void;
|
||||
openGlobalSearch(initialQuery?: string): void;
|
||||
openGlobalRecent(): void;
|
||||
openAccountInfo(data: WorkspaceAccountInfoData): void;
|
||||
|
||||
Reference in new issue
Block a user