From 76191b415f0e7f3bac69c4973e2272aaabf352fe Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 12 Jan 2026 00:55:18 +0100 Subject: [PATCH] refactor(xtream): refine search result card layout --- .../search-result-item.component.ts | 83 +++++-- .../portal-channels-list.component.ts | 3 - .../search-results.component.html | 69 ++++-- .../search-results.component.scss | 209 +++++++++++------- .../search-results.component.ts | 6 +- apps/web/src/assets/i18n/en.json | 5 + 6 files changed, 250 insertions(+), 125 deletions(-) diff --git a/apps/web/src/app/shared/components/search-result-item/search-result-item.component.ts b/apps/web/src/app/shared/components/search-result-item/search-result-item.component.ts index cae49b44e..9a914738c 100644 --- a/apps/web/src/app/shared/components/search-result-item/search-result-item.component.ts +++ b/apps/web/src/app/shared/components/search-result-item/search-result-item.component.ts @@ -25,9 +25,6 @@ import { MatIconModule } from '@angular/material/icon'; @if (description) {

{{ description }}

} - @if (type) { -
{{ type }}
- } @if (showPlaylistInfo && playlistName) {
playlist_play @@ -40,46 +37,92 @@ import { MatIconModule } from '@angular/material/icon'; ` :host { display: block; + height: 100%; } mat-card { cursor: pointer; - } - - h4 { - margin: 0; - padding: 8px; + height: 100%; + display: flex; + flex-direction: column; + position: relative; + overflow: hidden; + background: transparent !important; + box-shadow: none !important; + border: none !important; + padding: 0 !important; } .poster { width: 100%; + aspect-ratio: 2/3; object-fit: cover; + display: block; + border-radius: 8px; } .poster-placeholder { width: 100%; - height: 200px; + aspect-ratio: 2/3; display: flex; align-items: center; justify-content: center; + background: rgba(0, 0, 0, 0.05); + border-radius: 8px; + + mat-icon { + font-size: 48px; + width: 48px; + height: 48px; + opacity: 0.3; + } } - .type-badge { - position: absolute; - top: 8px; - right: 8px; - padding: 4px 8px; - border-radius: 4px; - font-size: 12px; - text-transform: uppercase; + h4 { + margin: 0; + padding: 10px 0 2px 0; + font-size: 0.9rem; + font-weight: 500; + line-height: 1.3em; + height: 2.6em; /* Fixed height for 2 lines */ + color: var(--text-color, #fff); + + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + + p { + margin: 0; + padding: 0 0 10px 0; + font-size: 0.75rem; + line-height: 1.2em; + height: 1.2em; /* Fixed height for 1 line */ + color: var(--text-color-secondary, rgba(255, 255, 255, 0.7)); + opacity: 0.9; /* Improved readability */ + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .playlist-badge { display: flex; align-items: center; gap: 4px; - margin-top: 8px; - font-size: 12px; + margin-top: auto; + padding: 0 0 10px 0; + font-size: 11px; + opacity: 0.8; + color: var(--text-color-secondary, inherit); + + mat-icon { + font-size: 14px; + width: 14px; + height: 14px; + } } `, ], @@ -93,4 +136,4 @@ export class SearchResultItemComponent { @Input() showPlaylistInfo = false; @Output() itemClick = new EventEmitter(); -} +} \ No newline at end of file diff --git a/apps/web/src/app/xtream-tauri/portal-channels-list/portal-channels-list.component.ts b/apps/web/src/app/xtream-tauri/portal-channels-list/portal-channels-list.component.ts index 2e3ac8e41..0729ff577 100644 --- a/apps/web/src/app/xtream-tauri/portal-channels-list/portal-channels-list.component.ts +++ b/apps/web/src/app/xtream-tauri/portal-channels-list/portal-channels-list.component.ts @@ -89,14 +89,11 @@ export class PortalChannelsListComponent implements AfterViewInit { this.favoritesService .getFavorites(playlist.id) .subscribe((favorites) => { - // Map using content.id instead of xtream_id favorites.forEach((fav: any) => { this.favorites.set(fav.xtream_id, true); }); - console.log(this.favorites); }); } - // Removed loadCurrentEpgData() call since we're using virtual scroll } ngAfterViewInit() { diff --git a/apps/web/src/app/xtream-tauri/search-results/search-results.component.html b/apps/web/src/app/xtream-tauri/search-results/search-results.component.html index acb1ede0d..8aa520953 100644 --- a/apps/web/src/app/xtream-tauri/search-results/search-results.component.html +++ b/apps/web/src/app/xtream-tauri/search-results/search-results.component.html @@ -1,24 +1,43 @@
-
-

{{ 'PORTALS.SIDEBAR.SEARCH' | translate }}

- @if (isGlobalSearch) { - - } -
+ diff --git a/apps/web/src/app/xtream-tauri/search-results/search-results.component.scss b/apps/web/src/app/xtream-tauri/search-results/search-results.component.scss index 13d6fc1b6..532921c11 100644 --- a/apps/web/src/app/xtream-tauri/search-results/search-results.component.scss +++ b/apps/web/src/app/xtream-tauri/search-results/search-results.component.scss @@ -7,31 +7,75 @@ } .search-page { - padding: 20px; + padding: 0 20px 20px 20px; } .header { - display: flex; - justify-content: space-between; - align-items: center; + position: sticky; + top: 0; + z-index: 100; + // Use theme background variable or fallback + background: var(--background, var(--mat-sidenav-content-background-color, #fff)); + padding: 20px 0 10px 0; margin-bottom: 20px; -} + // Subtle shadow or border for separation + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.02); + + display: flex; + flex-direction: column; + align-items: stretch; + gap: 16px; -.search-container { - flex-shrink: 0; - - .search-field { - width: 100%; + .header-top { + display: flex; + justify-content: space-between; + align-items: center; + gap: 20px; } - .type-filters { - mat-checkbox { - margin-right: 1rem; + .header-left { + display: flex; + align-items: center; + gap: 32px; + flex: 1; + flex-wrap: wrap; + + h2 { + margin: 0; + white-space: nowrap; + } + + .type-filters { + display: flex; + gap: 20px; + align-items: center; + + mat-checkbox { + margin: 0; + } } } - .results-count { - font-size: 0.9rem; + .header-right { + display: flex; + align-items: center; + gap: 16px; + + .results-count { + font-size: 0.85rem; + color: var(--text-color-secondary, rgba(0,0,0,0.7)); + background: rgba(0, 0, 0, 0.06); + padding: 6px 12px; + border-radius: 16px; + white-space: nowrap; + font-weight: 500; + display: flex; + align-items: center; + } + } + + .search-input-container { + margin-bottom: 0; } } @@ -50,54 +94,72 @@ mat-card { cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0,0,0,0.1); + } + .poster, .poster-placeholder { - border-radius: 13px; + border-radius: 8px; width: 100%; aspect-ratio: 2/3; object-fit: cover; - margin-bottom: 0.5rem; + margin-bottom: 0.75rem; } .poster-placeholder { display: flex; align-items: center; justify-content: center; + background: rgba(0,0,0,0.05); + mat-icon { font-size: 48px; width: 48px; height: 48px; + opacity: 0.3; } } h3 { margin: 0.5rem 0; font-size: 1rem; - font-weight: normal; + font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } p { - font-size: 0.8rem; - color: rgba(0, 0, 0, 0.6); - margin: 0.5rem 0; + font-size: 0.85rem; + color: var(--text-color-secondary, rgba(0, 0, 0, 0.6)); + margin: 0 0 0.75rem 0; + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; } .type-badge { display: inline-block; padding: 0.25rem 0.5rem; border-radius: 4px; - font-size: 0.8rem; + font-size: 0.75rem; + font-weight: 600; text-transform: uppercase; + letter-spacing: 0.5px; + &.live { - background: #e53935; - color: white; + background: rgba(229, 57, 53, 0.1); + color: #e53935; } &.movie { - background: #1e88e5; - color: white; + background: rgba(30, 136, 229, 0.1); + color: #1e88e5; } &.series { - background: #43a047; - color: white; + background: rgba(67, 160, 71, 0.1); + color: #43a047; } } } @@ -109,18 +171,45 @@ flex-direction: column; align-items: center; justify-content: center; - padding: 2rem; + padding: 4rem 2rem; + text-align: center; mat-icon { - font-size: 48px; - width: 48px; - height: 48px; - margin-bottom: 1rem; + font-size: 64px; + width: 64px; + height: 64px; + margin-bottom: 1.5rem; + opacity: 0.5; } - p { - font-size: 1.1rem; - margin: 0; + .no-items-content { + h3 { + margin: 0 0 0.5rem; + font-size: 1.5rem; + font-weight: 500; + color: var(--text-color); + } + + p { + font-size: 1.1rem; + margin: 0; + opacity: 0.8; + } + + .search-term-note { + margin-top: 1rem; + font-size: 0.9rem; + font-style: italic; + opacity: 0.6; + } + } + + &.initial-state { + opacity: 0.8; + mat-icon { + color: var(--primary-color); + opacity: 0.6; + } } } @@ -130,6 +219,11 @@ height: 100vh; } + /* Ensure sticky header background matches overlay background */ + .header { + background: var(--background); + } + .results-grid { mat-card { position: relative; @@ -200,49 +294,6 @@ } :host-context(.global-search-overlay) { - .search-page { - background: var(--background); - height: 100vh; - } - - .results-grid { - mat-card { - position: relative; - - .poster, - .poster-placeholder { - margin-top: 24px; - } - - .playlist-badge { - position: absolute; - top: 0; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.7); - padding: 6px 8px; - font-size: 12px; - color: white; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - border-top-left-radius: inherit; - border-top-right-radius: inherit; - backdrop-filter: blur(5px); - display: flex; - align-items: center; - gap: 4px; - - mat-icon { - font-size: 14px; - width: 14px; - height: 14px; - flex-shrink: 0; - } - } - } - } - .playlist-group { .playlist-title { background: var(--surface); diff --git a/apps/web/src/app/xtream-tauri/search-results/search-results.component.ts b/apps/web/src/app/xtream-tauri/search-results/search-results.component.ts index b2a9e35e2..b24d965e8 100644 --- a/apps/web/src/app/xtream-tauri/search-results/search-results.component.ts +++ b/apps/web/src/app/xtream-tauri/search-results/search-results.component.ts @@ -51,9 +51,7 @@ interface SearchResultsData { SearchResultItemComponent, TranslatePipe, ], - providers: [ - XtreamStore - ], + providers: [], templateUrl: './search-results.component.html', styleUrls: ['./search-results.component.scss'], }) @@ -112,7 +110,7 @@ export class SearchResultsComponent implements AfterViewInit { }); } - private executeSearch() { + executeSearch() { const types = Object.entries(this.filters) .filter(([_, enabled]) => enabled) .map(([type]) => type); diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json index 0a5b99de1..a593b21a1 100644 --- a/apps/web/src/assets/i18n/en.json +++ b/apps/web/src/assets/i18n/en.json @@ -384,6 +384,7 @@ "LIBRARY": "Library", "SEARCH": "Search", "RECENT": "Recent", + "RECENTLY_ADDED": "Recently added", "FAVORITES": "Favorites", "HOME": "Home", "PLAYLIST_INFO": "Playlist info", @@ -422,6 +423,10 @@ "TITLE": "No results found", "NO_SEARCH_RESULTS": "Nothing found, try to change you search request" }, + "SEARCH_VIEW": { + "INITIAL_TITLE": "Search for content", + "INITIAL_DESCRIPTION": "Enter at least 3 characters to search across live TV, movies and series." + }, "STREAM_URL_COPIED": "Stream URL copied to clipboard", "COPY_STREAM_URL": "Copy Stream URL", "SELECT_CATEGORY": "Select a category",