feat(search): add workspace global search with M3U support

Moves global search into a routed workspace view, adds M3U live/radio results, lazy pagination, and DB-backed matching improvements.
This commit is contained in:
4gray authored and GitHub committed 2026-06-20 23:05:48 +02:00
1 parent ffc07146fe
commit 89916af9d8
65 files changed
+3010 -391

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ export * from './lib/epg-item.interface';
export * from './lib/epg-program.model';
export * from './lib/external-player-arguments.utils';
export * from './lib/external-player-session.interface';
export * from './lib/global-search-result.interface';
export * from './lib/indexed-db.config';
export * from './lib/ipc-command.class';
export * from './lib/ipc-commands';
@@ -7,6 +7,11 @@ import {
import { EpgChannelMetadata } from './epg-channel-metadata.model';
import { EpgProgram } from './epg-program.model';
import { ExternalPlayerSession } from './external-player-session.interface';
import {
GlobalSearchPaginationOptions,
GlobalSearchResult,
GlobalSearchResultSource,
} from './global-search-result.interface';
import { PlaybackPositionData } from './playback-position.interface';
import {
XtreamBackupFavoriteItem,
@@ -322,10 +327,7 @@ export type ElectronBridgeXtreamContentStream =
}
| Record<string, unknown>;
export interface ElectronBridgeGlobalSearchResult extends ElectronBridgeXtreamContent {
playlist_id: string;
playlist_name: string;
}
export type ElectronBridgeGlobalSearchResult = GlobalSearchResult;
export interface ElectronBridgeGlobalRecentItem extends ElectronBridgeXtreamContent {
playlist_id: string;
@@ -630,7 +632,9 @@ export interface ElectronBridgeApi {
dbGlobalSearch: (
searchTerm: string,
types: string[],
excludeHidden?: boolean
excludeHidden?: boolean,
sources?: GlobalSearchResultSource[],
options?: GlobalSearchPaginationOptions
) => Promise<ElectronBridgeGlobalSearchResult[]>;
dbGetGlobalRecentlyAdded: (
kind: ElectronBridgeGlobalRecentlyAddedKind,
@@ -0,0 +1,72 @@
import { Channel } from './channel.interface';
export const GLOBAL_SEARCH_RESULT_SOURCES = {
Xtream: 'xtream',
M3u: 'm3u',
} as const;
export type GlobalSearchResultSource =
(typeof GLOBAL_SEARCH_RESULT_SOURCES)[keyof typeof GLOBAL_SEARCH_RESULT_SOURCES];
export interface GlobalSearchPaginationOptions {
limit?: number;
offset?: number;
}
export const GLOBAL_SEARCH_CONTENT_TYPES = {
Live: 'live',
Movie: 'movie',
Series: 'series',
} as const;
export type GlobalSearchContentType =
(typeof GLOBAL_SEARCH_CONTENT_TYPES)[keyof typeof GLOBAL_SEARCH_CONTENT_TYPES];
export interface GlobalSearchBaseResult {
source_type: GlobalSearchResultSource;
content_type: GlobalSearchContentType;
id: number | string;
category_id: number | string;
title: string;
rating: string | null;
added: string | null;
poster_url: string | null;
xtream_id: number;
type: GlobalSearchContentType;
playlist_id: string;
playlist_name: string;
}
export interface XtreamGlobalSearchResult extends GlobalSearchBaseResult {
source_type: typeof GLOBAL_SEARCH_RESULT_SOURCES.Xtream;
description?: string;
backdrop_url?: string | null;
epg_channel_id?: string | null;
tv_archive?: number | null;
tv_archive_duration?: number | null;
direct_source?: string | null;
added_at?: string;
viewed_at?: string;
position?: number | null;
}
export interface M3uGlobalSearchResult extends GlobalSearchBaseResult {
source_type: typeof GLOBAL_SEARCH_RESULT_SOURCES.M3u;
content_type: typeof GLOBAL_SEARCH_CONTENT_TYPES.Live;
type: typeof GLOBAL_SEARCH_CONTENT_TYPES.Live;
channel_id: string;
stream_url: string;
group_title: string;
radio: string;
channel: Channel;
}
export type GlobalSearchResult =
| XtreamGlobalSearchResult
| M3uGlobalSearchResult;
export function isM3uGlobalSearchResult(
result: GlobalSearchResult
): result is M3uGlobalSearchResult {
return result.source_type === GLOBAL_SEARCH_RESULT_SOURCES.M3u;
}