diff --git a/src/app/xtream-tauri/recently-viewed/recently-viewed.component.html b/src/app/xtream-tauri/recently-viewed/recently-viewed.component.html index f5ccc4e63..4e41bf804 100644 --- a/src/app/xtream-tauri/recently-viewed/recently-viewed.component.html +++ b/src/app/xtream-tauri/recently-viewed/recently-viewed.component.html @@ -2,49 +2,119 @@
- {{ item.type | titlecase }} -
-- {{ item.viewed_at | date: 'd MMMM, y, hh:mm' }} -
-+ {{ item.type | titlecase }} +
++ {{ + item.viewed_at + | date: 'd MMMM, y, HH:mm' + }} +
++ {{ item.type | titlecase }} +
++ {{ item.viewed_at | date: 'd MMMM, y, hh:mm' }} +
+{{ item.description }}
-{{ item.description }}
+{{ item.description }}
+No results found for "{{ searchTerm }}"
diff --git a/src/app/xtream-tauri/search-results/search-results.component.scss b/src/app/xtream-tauri/search-results/search-results.component.scss index cef58105f..13d6fc1b6 100644 --- a/src/app/xtream-tauri/search-results/search-results.component.scss +++ b/src/app/xtream-tauri/search-results/search-results.component.scss @@ -25,14 +25,12 @@ } .type-filters { - margin-top: 1rem; mat-checkbox { margin-right: 1rem; } } .results-count { - margin-top: 1rem; font-size: 0.9rem; } } @@ -40,7 +38,6 @@ .results-container { flex: 1; overflow-y: auto; - padding: 0 1rem; .results-grid { display: grid; @@ -125,4 +122,153 @@ font-size: 1.1rem; margin: 0; } +} + +:host-context(.global-search-overlay) { + .search-page { + background: var(--background); + height: 100vh; + } + + .results-grid { + mat-card { + position: relative; + + .poster, + .poster-placeholder { + margin-top: 24px; // Add space for the playlist badge + } + + .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 { + margin-bottom: 2rem; + + .playlist-title { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 1rem 0; + padding: 8px; + background: rgba(0, 0, 0, 0.05); + border-radius: 4px; + font-size: 1.1rem; + font-weight: 500; + + mat-icon { + opacity: 0.7; + } + + .item-count { + margin-left: auto; + font-size: 0.9rem; + opacity: 0.7; + } + } + + .results-grid { + margin-bottom: 0; + } +} + +: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); + } + } + + .results-grid { + mat-card { + .playlist-badge { + display: none; + } + + .poster, + .poster-placeholder { + margin-top: 0; + } + } + } +} + +.playlist-section { + .section-title { + margin: 0 0 1.5rem; + font-size: 1.2rem; + font-weight: normal; + color: var(--text-color-secondary); + padding: 0 8px; + } } \ No newline at end of file diff --git a/src/app/xtream-tauri/search-results/search-results.component.ts b/src/app/xtream-tauri/search-results/search-results.component.ts index 4af0a22fa..af088f0cd 100644 --- a/src/app/xtream-tauri/search-results/search-results.component.ts +++ b/src/app/xtream-tauri/search-results/search-results.component.ts @@ -1,45 +1,83 @@ +import { KeyValuePipe } from '@angular/common'; import { AfterViewInit, Component, ElementRef, inject, + Inject, + OnDestroy, + Optional, ViewChild, } from '@angular/core'; import { FormsModule } from '@angular/forms'; +import { MatIconButton } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatIconModule } from '@angular/material/icon'; +import { MatIcon } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { ActivatedRoute, Router } from '@angular/router'; +import groupBy from 'lodash/groupBy'; +import { debounceTime, Subject } from 'rxjs'; import { XtreamItem } from '../../../../shared/xtream-item.interface'; +import { DatabaseService } from '../../services/database.service'; import { XtreamStore } from '../xtream.store'; +interface SearchResultsData { + isGlobalSearch: boolean; +} + @Component({ selector: 'app-search-results', standalone: true, imports: [ + MatIconButton, MatCardModule, MatFormFieldModule, MatInputModule, - MatIconModule, + MatIcon, MatCheckboxModule, FormsModule, + MatDialogModule, + KeyValuePipe, ], + providers: [], templateUrl: './search-results.component.html', styleUrls: ['./search-results.component.scss'], }) -export class SearchResultsComponent implements AfterViewInit { +export class SearchResultsComponent implements AfterViewInit, OnDestroy { @ViewChild('searchInput') searchInput!: ElementRef; readonly xtreamStore = inject(XtreamStore); readonly router = inject(Router); readonly activatedRoute = inject(ActivatedRoute); + readonly databaseService = inject(DatabaseService); searchTerm = ''; filters = { live: true, movie: true, series: true, }; + isGlobalSearch = false; + private searchSubject = new Subject