mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
feat(xtream): Xtream playlist refresh workflow
a dedicated refresh button and workflow for Xtream in the recent playlists UI. Show a refresh icon only when the playlist has a serverUrl. For Xtream playlists, prompt the user with a confirmation dialog, delete locally cached categories and streams, show a toast that the refresh started, and navigate to the playlist to trigger re-import. Keep existing refresh IPC logic for non-Xtream (M3U/local) playlists. Also add English translations for the Xtream refresh tooltip and dialog messages, and surface errors via snackbar on failure. This enables safe re-importing of remote Xtream playlists while preserving favorites and recent items.
This commit is contained in:
1 parent
0a934d2ac6
commit
461e2e2e7e
8 files changed
+160
-60
No files matched your search
@@ -85,6 +85,7 @@ ipcMain.handle(
|
||||
username?: string;
|
||||
password?: string;
|
||||
serverUrl?: string;
|
||||
lastUpdated?: string;
|
||||
}
|
||||
) => {
|
||||
try {
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
effect,
|
||||
ElementRef,
|
||||
inject,
|
||||
Inject,
|
||||
Optional,
|
||||
@@ -55,7 +54,7 @@ interface SearchResultsData {
|
||||
styleUrls: ['./search-results.component.scss'],
|
||||
})
|
||||
export class SearchResultsComponent implements AfterViewInit {
|
||||
@ViewChild('searchInput') searchInput!: ElementRef;
|
||||
@ViewChild(SearchFormComponent) searchFormComponent!: SearchFormComponent;
|
||||
readonly xtreamStore = inject(XtreamStore);
|
||||
readonly router = inject(Router);
|
||||
readonly activatedRoute = inject(ActivatedRoute);
|
||||
@@ -105,7 +104,7 @@ export class SearchResultsComponent implements AfterViewInit {
|
||||
ngAfterViewInit() {
|
||||
this.xtreamStore.setSelectedContentType(undefined);
|
||||
setTimeout(() => {
|
||||
this.searchInput.nativeElement.focus();
|
||||
this.searchFormComponent?.focusSearchInput();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -568,51 +568,57 @@ export const XtreamStore = signalStore(
|
||||
try {
|
||||
console.log('Starting content initialization...');
|
||||
|
||||
console.log('Fetching live categories...');
|
||||
await lastValueFrom(
|
||||
fetchCategories(
|
||||
XtreamCodeActions.GetLiveCategories,
|
||||
'liveCategories',
|
||||
'live'
|
||||
)
|
||||
);
|
||||
await lastValueFrom(
|
||||
fetchCategories(
|
||||
XtreamCodeActions.GetVodCategories,
|
||||
'vodCategories',
|
||||
'movies'
|
||||
)
|
||||
);
|
||||
await lastValueFrom(
|
||||
fetchCategories(
|
||||
XtreamCodeActions.GetSeriesCategories,
|
||||
'serialCategories',
|
||||
'series'
|
||||
)
|
||||
);
|
||||
// Fetch all categories in parallel
|
||||
console.log('Fetching categories...');
|
||||
await Promise.all([
|
||||
lastValueFrom(
|
||||
fetchCategories(
|
||||
XtreamCodeActions.GetLiveCategories,
|
||||
'liveCategories',
|
||||
'live'
|
||||
)
|
||||
),
|
||||
lastValueFrom(
|
||||
fetchCategories(
|
||||
XtreamCodeActions.GetVodCategories,
|
||||
'vodCategories',
|
||||
'movies'
|
||||
)
|
||||
),
|
||||
lastValueFrom(
|
||||
fetchCategories(
|
||||
XtreamCodeActions.GetSeriesCategories,
|
||||
'serialCategories',
|
||||
'series'
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
||||
// Fetch all content in parallel
|
||||
console.log('Fetching content...');
|
||||
await lastValueFrom(
|
||||
fetchStreams(
|
||||
XtreamCodeActions.GetLiveStreams,
|
||||
'liveStreams',
|
||||
'live'
|
||||
)
|
||||
);
|
||||
await lastValueFrom(
|
||||
fetchStreams(
|
||||
XtreamCodeActions.GetVodStreams,
|
||||
'vodStreams',
|
||||
'movie'
|
||||
)
|
||||
);
|
||||
await lastValueFrom(
|
||||
fetchStreams(
|
||||
XtreamCodeActions.GetSeries,
|
||||
'serialStreams',
|
||||
'series'
|
||||
)
|
||||
);
|
||||
await Promise.all([
|
||||
lastValueFrom(
|
||||
fetchStreams(
|
||||
XtreamCodeActions.GetLiveStreams,
|
||||
'liveStreams',
|
||||
'live'
|
||||
)
|
||||
),
|
||||
lastValueFrom(
|
||||
fetchStreams(
|
||||
XtreamCodeActions.GetVodStreams,
|
||||
'vodStreams',
|
||||
'movie'
|
||||
)
|
||||
),
|
||||
lastValueFrom(
|
||||
fetchStreams(
|
||||
XtreamCodeActions.GetSeries,
|
||||
'serialStreams',
|
||||
'series'
|
||||
)
|
||||
),
|
||||
]);
|
||||
console.log('Content initialization completed');
|
||||
} catch (error) {
|
||||
console.error(
|
||||
@@ -627,7 +633,7 @@ export const XtreamStore = signalStore(
|
||||
const checkPortalStatus = rxMethod<void>(
|
||||
pipe(
|
||||
combineLatestWith(oldStore.select(selectActivePlaylist)),
|
||||
switchMap(([_, playlist]) => {
|
||||
switchMap(([, playlist]) => {
|
||||
if (!playlist) return EMPTY;
|
||||
|
||||
return from(
|
||||
|
||||
@@ -54,6 +54,13 @@
|
||||
},
|
||||
"UPDATED": "Updated",
|
||||
"REFRESH": "Refresh playlist",
|
||||
"REFRESH_XTREAM": "Refresh Xtream playlist from remote",
|
||||
"REFRESH_XTREAM_DIALOG": {
|
||||
"TITLE": "Refresh Xtream Playlist",
|
||||
"MESSAGE": "This will delete locally cached categories and streams and re-import everything from the remote server. Your favorites and recently viewed items will be preserved. Are you sure you want to continue?",
|
||||
"STARTED": "Refresh started. Navigating to playlist...",
|
||||
"ERROR": "Failed to refresh playlist. Please try again."
|
||||
},
|
||||
"GLOBAL_FAVORITES": "Global favorites",
|
||||
"GLOBAL_FAVORITES_DESCRIPTION": "Auto-generated playlist with aggregated favorites from all playlists",
|
||||
"MY_PLAYLISTS": "My playlists",
|
||||
|
||||
@@ -92,18 +92,24 @@ export class DatabaseService {
|
||||
*/
|
||||
async updateXtreamPlaylistDetails(playlist: {
|
||||
id: string;
|
||||
title: string;
|
||||
title?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
serverUrl?: string;
|
||||
updateDate?: number;
|
||||
}): Promise<boolean> {
|
||||
try {
|
||||
await window.electron.dbUpdatePlaylist(playlist.id, {
|
||||
name: playlist.title,
|
||||
username: playlist.username,
|
||||
password: playlist.password,
|
||||
serverUrl: playlist.serverUrl,
|
||||
});
|
||||
const updates: any = {};
|
||||
if (playlist.title) updates.name = playlist.title;
|
||||
if (playlist.username) updates.username = playlist.username;
|
||||
if (playlist.password) updates.password = playlist.password;
|
||||
if (playlist.serverUrl) updates.serverUrl = playlist.serverUrl;
|
||||
if (playlist.updateDate !== undefined)
|
||||
updates.lastUpdated = new Date(
|
||||
playlist.updateDate
|
||||
).toISOString();
|
||||
|
||||
await window.electron.dbUpdatePlaylist(playlist.id, updates);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error updating playlist details:', error);
|
||||
|
||||
@@ -97,6 +97,9 @@ export class PlaylistsService {
|
||||
...(updatedPlaylist.password !== null
|
||||
? { password: updatedPlaylist.password }
|
||||
: {}),
|
||||
...(updatedPlaylist.updateDate !== undefined
|
||||
? { updateDate: updatedPlaylist.updateDate }
|
||||
: {}),
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
+16
-2
@@ -69,8 +69,12 @@
|
||||
<ng-container *ngIf="item.importDate?.length > 3">
|
||||
| {{ 'HOME.PLAYLISTS.ADDED' | translate }}:
|
||||
{{ item.importDate | date }}
|
||||
</ng-container></ng-template
|
||||
>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="item.updateDate">
|
||||
| {{ 'HOME.PLAYLISTS.UPDATED' | translate }}:
|
||||
{{ item.updateDate | date: 'MMMM d, yyyy, HH:mm' }}
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<span matListItemMeta class="action-buttons" *ngIf="showActions">
|
||||
@@ -84,6 +88,16 @@
|
||||
>
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="item.serverUrl"
|
||||
mat-icon-button
|
||||
class="refresh-btn"
|
||||
color="accent"
|
||||
(click)="$event.stopPropagation(); refreshClicked.emit(item)"
|
||||
[matTooltip]="'HOME.PLAYLISTS.REFRESH_XTREAM' | translate"
|
||||
>
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
color="accent"
|
||||
|
||||
@@ -249,10 +249,74 @@ export class RecentPlaylistsComponent implements OnInit {
|
||||
* @param item playlist to update
|
||||
*/
|
||||
refreshPlaylist(item: PlaylistMeta) {
|
||||
this.dataService.sendIpcEvent(PLAYLIST_UPDATE, {
|
||||
id: item._id,
|
||||
title: item.title,
|
||||
...(item.url ? { url: item.url } : { filePath: item.filePath }),
|
||||
if (item.serverUrl) {
|
||||
// For Xtream playlists, delete and re-import
|
||||
this.refreshXtreamPlaylist(item);
|
||||
} else {
|
||||
// For M3U playlists, use existing refresh logic
|
||||
this.dataService.sendIpcEvent(PLAYLIST_UPDATE, {
|
||||
id: item._id,
|
||||
title: item.title,
|
||||
...(item.url ? { url: item.url } : { filePath: item.filePath }),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh Xtream playlist by deleting all data and re-importing from remote
|
||||
* @param item Xtream playlist to refresh
|
||||
*/
|
||||
async refreshXtreamPlaylist(item: PlaylistMeta) {
|
||||
this.dialogService.openConfirmDialog({
|
||||
title: this.translate.instant(
|
||||
'HOME.PLAYLISTS.REFRESH_XTREAM_DIALOG.TITLE'
|
||||
),
|
||||
message: this.translate.instant(
|
||||
'HOME.PLAYLISTS.REFRESH_XTREAM_DIALOG.MESSAGE'
|
||||
),
|
||||
onConfirm: async () => {
|
||||
try {
|
||||
// Delete all content and categories for this playlist
|
||||
await this.databaseService.deleteXtreamPlaylistContent(
|
||||
item._id
|
||||
);
|
||||
|
||||
const updateDate = Date.now();
|
||||
|
||||
// Update the updateDate timestamp in database (Electron)
|
||||
await this.databaseService.updateXtreamPlaylistDetails({
|
||||
id: item._id,
|
||||
updateDate,
|
||||
});
|
||||
|
||||
// Update the updateDate timestamp in IndexedDB
|
||||
this.store.dispatch(
|
||||
PlaylistActions.updatePlaylistMeta({
|
||||
playlist: { ...item, updateDate },
|
||||
})
|
||||
);
|
||||
|
||||
this.snackBar.open(
|
||||
this.translate.instant(
|
||||
'HOME.PLAYLISTS.REFRESH_XTREAM_DIALOG.STARTED'
|
||||
),
|
||||
undefined,
|
||||
{ duration: 2000 }
|
||||
);
|
||||
|
||||
// Navigate to the playlist to trigger re-import
|
||||
this.router.navigate(['xtreams', item._id]);
|
||||
} catch (error) {
|
||||
console.error('Error refreshing Xtream playlist:', error);
|
||||
this.snackBar.open(
|
||||
this.translate.instant(
|
||||
'HOME.PLAYLISTS.REFRESH_XTREAM_DIALOG.ERROR'
|
||||
),
|
||||
undefined,
|
||||
{ duration: 3000 }
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user