refactor(m3u): add loading state for channel list and improve playlist handling

- Implemented a loading state for channel lists in various components, enhancing user experience during data fetching.
- Added `ChannelListLoadingStateComponent` to display loading skeletons for channels and groups.
- Updated `M3uWorkspaceRouteSession` to manage loading states more effectively when fetching playlists.
- Modified `VideoPlayerComponent`, `SidebarComponent`, and `ChannelListContainerComponent` to utilize the new loading state.
- Enhanced tests to cover new loading behaviors and ensure proper dispatching of loading actions.
This commit is contained in:
4gray committed 2026-04-11 13:20:22 +02:00
1 parent 7cc25380dd
commit 0f72efb749
33 files changed
+1104 -41

No files matched your search

+16
View File
@@ -49,6 +49,9 @@ interface PlaylistState {
// Active channel being played
active: Channel | undefined;
// Whether the current route is still resolving channel data
channelsLoading: boolean;
// All channels from current playlist
channels: Channel[];
@@ -85,6 +88,7 @@ interface PlaylistState {
```typescript
// Channel selectors
selectActive // Current playing channel
selectChannelsLoading // Channel list loading flag
selectChannels // All channels array
selectFavorites // Favorite channel URLs
@@ -165,6 +169,18 @@ channel-list-container/
ChannelActions.setActiveChannel
```
### Loading States
- `M3uWorkspaceRouteSession` owns route-driven channel loading for the player/sidebar routes: `all` and `groups`.
- The route session sets `channelsLoading` before `getPlaylist()` resolves and clears it when `ChannelActions.setChannels` lands.
- `ChannelListContainerComponent` now renders a dedicated skeleton state while `channelsLoading` is true.
- `ChannelListContainerComponent` no longer clears `channels` on destroy; route/session code is the single owner of shared list lifecycle during navigation.
- The dedicated `/workspace/playlists/:id/favorites` and `/workspace/playlists/:id/recent` collection routes do not drive the shared sidebar channel list; they default to the `playlist` scope so rail links always open the current playlist view, not the last persisted global scope.
- Empty playlists and empty search results are no longer conflated:
- loading: skeletons
- empty source: no channels in the playlist after loading completes
- empty search: no matches within an already loaded playlist
### EnrichedChannel Pattern
For performance optimization, channels are pre-enriched with EPG data: