Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"dependencies": {
"@angular/animations": "19.2.1",
"@angular/cdk": "^19.2.19",
"@angular/common": "19.2.1",
"@angular/compiler": "19.2.1",
"@angular/core": "19.2.1",
Expand Down
4 changes: 4 additions & 0 deletions src-angular/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ScrollingModule } from '@angular/cdk/scrolling'
import { CommonModule } from '@angular/common'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { NgModule } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
Expand Down Expand Up @@ -38,9 +40,11 @@ import { RemoveStyleTagsPipe } from './core/pipes/remove-style-tags.pipe'
],
bootstrap: [AppComponent], imports: [
BrowserModule,
CommonModule,
AppRoutingModule,
FormsModule,
ReactiveFormsModule,
ScrollingModule,
], providers: [provideHttpClient(withInterceptorsFromDi())],
})
export class AppModule { }
Original file line number Diff line number Diff line change
@@ -1,51 +1,53 @@
<div
#resultTableDiv
class="basis-2/3 flex-1 overflow-y-auto scrollbar scrollbar-w-2 scrollbar-h-2 scrollbar-track-base-300 scrollbar-thumb-neutral scrollbar-thumb-rounded-full"
(scroll)="tableScrolled()">
<table id="resultTable" class="table table-zebra table-pin-rows" [class.table-xs]="settingsService.isCompactTable">
<thead>
<tr>
<th class="collapsing" id="checkboxColumn">
<input type="checkbox" class="checkbox" [(ngModel)]="allSelected" />
</th>
<th [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('name')">
Name <i *ngIf="sortColumn === 'name'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('artist')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('artist')">
Artist
<i *ngIf="sortColumn === 'artist'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('album')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('album')">
Album <i *ngIf="sortColumn === 'album'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('genre')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('genre')">
Genre <i *ngIf="sortColumn === 'genre'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('year')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('year')">
Year <i *ngIf="sortColumn === 'year'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('charter')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('charter')">
Charter <i *ngIf="sortColumn === 'charter'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('length')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('length')">
Length (min) <i *ngIf="sortColumn === 'length'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('difficulty')" [ngClass]="sortDirection" class="cursor-pointer">Difficulty</th>
<th *ngIf="hasColumn('uploaded')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('modifiedTime')">
Upload Date <i *ngIf="sortColumn === 'modifiedTime'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
</tr>
</thead>
<tbody>
@for (song of songs; track song) {
<div class="flex-1 flex flex-col overflow-hidden">
<cdk-virtual-scroll-viewport
#viewport
class="flex-1 overflow-y-auto scrollbar scrollbar-w-2 scrollbar-h-2 scrollbar-track-base-300 scrollbar-thumb-neutral scrollbar-thumb-rounded-full"
[itemSize]="tableRowHeight"
(scroll)="onViewportScroll()">
<table class="table table-zebra w-full" [class.table-xs]="settingsService.isCompactTable">
<thead>
<tr [style.top]="tableHeaderPosition" class="sticky top-0 bg-base-100 z-10">
<th class="collapsing" id="checkboxColumn">
<input type="checkbox" class="checkbox" [(ngModel)]="allSelected" />
</th>
<th [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('name')">
Name <i *ngIf="sortColumn === 'name'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('artist')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('artist')">
Artist
<i *ngIf="sortColumn === 'artist'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('album')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('album')">
Album <i *ngIf="sortColumn === 'album'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('genre')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('genre')">
Genre <i *ngIf="sortColumn === 'genre'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('year')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('year')">
Year <i *ngIf="sortColumn === 'year'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('charter')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('charter')">
Charter <i *ngIf="sortColumn === 'charter'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('length')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('length')">
Length (min) <i *ngIf="sortColumn === 'length'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
<th *ngIf="hasColumn('difficulty')" [ngClass]="sortDirection" class="cursor-pointer">Difficulty</th>
<th *ngIf="hasColumn('uploaded')" [ngClass]="sortDirection" class="cursor-pointer" (click)="onColClicked('modifiedTime')">
Upload Date <i *ngIf="sortColumn === 'modifiedTime'" class="bi bi-caret-{{ sortDirection === 'asc' ? 'down' : 'up' }}-fill"></i>
</th>
</tr>
</thead>
<tbody>
<tr
*cdkVirtualFor="let song of songs; trackBy: trackByFn"
app-result-table-row
(click)="onRowClicked(song)"
(rowFocused)="onRowClicked(song)"
[class.!bg-neutral]="activeSong === song"
[class.!text-neutral-content]="activeSong === song"
[song]="song"></tr>
}
</tbody>
</table>
</tbody>
</table>
</cdk-virtual-scroll-viewport>
</div>
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import { Component, ElementRef, EventEmitter, HostBinding, HostListener, OnInit, Output, QueryList, ViewChild, ViewChildren } from '@angular/core'
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'
import { Component, EventEmitter, HostBinding, HostListener, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
import { Router } from '@angular/router'

import { Subscription } from 'rxjs'
import { SettingsService } from 'src-angular/app/core/services/settings.service'
import { ChartData } from 'src-shared/interfaces/search.interface'

import { SearchService } from '../../../core/services/search.service'
import { SelectionService } from '../../../core/services/selection.service'
import { ResultTableRowComponent } from './result-table-row/result-table-row.component'

@Component({
selector: 'app-result-table',
templateUrl: './result-table.component.html',
standalone: false,
})
export class ResultTableComponent implements OnInit {
export class ResultTableComponent implements OnInit, OnDestroy {
@HostBinding('class.contents') contents = true

@Output() rowClicked = new EventEmitter<ChartData[]>()

@ViewChild('resultTableDiv', { static: true }) resultTableDiv: ElementRef
@ViewChildren('tableRow') tableRows: QueryList<ResultTableRowComponent>
@ViewChild('viewport', { static: true }) viewport: CdkVirtualScrollViewport

activeSong: ChartData[] | null = null
sortDirection: 'asc' | 'desc' = 'asc'
sortColumn: 'name' | 'artist' | 'album' | 'genre' | 'year' | 'charter' | 'length' | 'modifiedTime' | null = null
isLoadingMore = false
songs: ChartData[][] = []
subscription: Subscription[] = []

constructor(
public searchService: SearchService,
Expand All @@ -33,18 +36,48 @@ export class ResultTableComponent implements OnInit {
) { }

ngOnInit() {
this.searchService.newSearch.subscribe(() => {
this.resultTableDiv.nativeElement.scrollTop = 0
this.activeSong = null
setTimeout(() => this.tableScrolled(), 0)
})
this.searchService.updateSearch.subscribe(() => {
setTimeout(() => this.tableScrolled(), 0)
})
this.subscription.push(
this.searchService.newSearch.subscribe(() => {
if (this.viewport) {
this.viewport.scrollToIndex(0)
}
this.activeSong = null
this.isLoadingMore = false
this.songs = [...this.searchService.groupedSongs]
})
)

this.subscription.push(
this.searchService.updateSearch.subscribe(() => {
this.isLoadingMore = false
this.songs = [...this.searchService.groupedSongs]
})
)
}

onViewportScroll(): void {
if (!this.viewport || this.router.url !== '/browse' || this.isLoadingMore) {
return
}

const viewportElement = this.viewport.elementRef.nativeElement
const scrollTop = viewportElement.scrollTop
const scrollHeight = viewportElement.scrollHeight
const clientHeight = viewportElement.clientHeight
const threshold = 100

if (scrollHeight - (scrollTop + clientHeight) < threshold) {
this.isLoadingMore = true
this.searchService.getNextSearchPage()
}
}

trackByFn(_: number, song: ChartData[]): number {
return song[0].groupId
}

get songs() {
return this.searchService.groupedSongs
get tableRowHeight(): number {
return this.settingsService.isCompactTable ? 32 : 48
}

hasColumn(column: string) {
Expand Down Expand Up @@ -86,15 +119,23 @@ export class ResultTableComponent implements OnInit {
}
}

public get tableHeaderPosition(): string {
if (!this.viewport) {
return '-0px'
}
const offset = this.viewport.getOffsetToRenderedContentStart()

return `-${offset}px`
}

@HostListener('window:resize', ['$event'])
onResize() {
this.tableScrolled()
}
tableScrolled(): void {
const table = this.resultTableDiv.nativeElement
if (this.router.url === '/browse' && table.scrollHeight - (table.scrollTop + table.clientHeight) < 100) {
// Scrolled near the bottom of the table
this.searchService.getNextSearchPage()
if (this.viewport) {
this.viewport.checkViewportSize()
}
}

ngOnDestroy(): void {
this.subscription.forEach(sub => sub.unsubscribe())
}
}
6 changes: 3 additions & 3 deletions src-angular/app/core/services/selection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class SelectionService {

public selections: { [groupId: number]: boolean | undefined } = {}

constructor(searchService: SearchService) {
constructor(private searchService: SearchService) {
searchService.newSearch.subscribe(() => {
this.selections = {}
this.deselectAll()
Expand All @@ -33,8 +33,8 @@ export class SelectionService {

selectAll() {
this.allSelected = true
for (const groupId in this.selections) {
this.selections[groupId] = true
for (const song of this.searchService.groupedSongs) {
this.selections[song[0].groupId] = true
}
this.selectAllChangedEmitter.emit(true)
}
Expand Down